org.simpleframework.xml.stream.NodeWriter Maven / Gradle / Ivy
Show all versions of simple-xml Show documentation
/*
* NodeWriter.java July 2006
*
* Copyright (C) 2006, Niall Gallagher
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.simpleframework.xml.stream;
import java.util.HashSet;
import java.util.Set;
import java.io.Writer;
/**
* The NodeWriter
object is used to create a writer that
* will write well formed indented XML for a given output node. This
* is used in the serialization process to convert an object into an
* XML document.
*
* This keeps a stack of all the active output nodes so that if an
* output node has been committed it cannot write any further data to
* the XML document. This allows all output nodes to be independent
* of each other as the node write organizes the write access.
*
* @author Niall Gallagher
*/
class NodeWriter {
/**
* Represents the stack of output nodes that are not yet ended.
*/
private final OutputStack stack;
/**
* Formatter used to indent the XML elements and escape text.
*/
private final Formatter writer;
/**
* Contains the set of as yet uncommitted elements blocks.
*/
private final Set active;
/**
* This determines if we expand the namespace prefixes.
*/
private final boolean verbose;
/**
* Constructor for the NodeWriter
object. This will
* create the object that is used to control an output elements
* access to the generated XML document. This keeps a stack of
* active and uncommitted elements.
*
* @param result this is the output for the resulting document
*/
public NodeWriter(Writer result) {
this(result, new Format());
}
/**
* Constructor for the NodeWriter
object. This will
* create the object that is used to control an output elements
* access to the generated XML document. This keeps a stack of
* active and uncommitted elements.
*
* @param result this is the output for the resulting document
* @param format this is used to format the generated document
*/
public NodeWriter(Writer result, Format format) {
this(result, format, false);
}
/**
* Constructor for the NodeWriter
object. This will
* create the object that is used to control an output elements
* access to the generated XML document. This keeps a stack of
* active and uncommitted elements.
*
* @param result this is the output for the resulting document
* @param format this is used to format the generated document
* @param verbose this determines if we expand the namespaces
*/
private NodeWriter(Writer result, Format format, boolean verbose) {
this.writer = new Formatter(result, format);
this.active = new HashSet();
this.stack = new OutputStack(active);
this.verbose = verbose;
}
/**
* This is used to acquire the root output node for the document.
* This will create an empty node that can be used to generate
* the root document element as a child to the document.
*
* Depending on whether or not an encoding has been specified
* this method will write a prolog to the generated XML document.
* Each prolog written uses an XML version of "1.0".
*
* @return this returns an output element for the document
*/
public OutputNode writeRoot() throws Exception {
OutputDocument root = new OutputDocument(this, stack);
if(stack.isEmpty()) {
writer.writeProlog();
}
return root;
}
/**
* This method is used to determine if the node is the root
* node for the XML document. The root node is the first node
* in the document and has no sibling nodes. This is false
* if the node has a parent node or a sibling node.
*
* @param node this is the node that is check as the root
*
* @return true if the node is the root node for the document
*/
public boolean isRoot(OutputNode node) {
return stack.bottom() == node;
}
/**
* This is used to determine if the specified node has been
* committed. If this returns tre then the node is committed
* and cannot be used to add further child elements.
*
* @param node this is the node to check for commit status
*
* @return this returns true if the node has been committed
*/
public boolean isCommitted(OutputNode node) {
return !active.contains(node);
}
/**
* This method is used to commit all nodes on the stack up to and
* including the specified node. This will effectively create end
* tags for any nodes that are currently open up to the specified
* element. Once committed the output node can no longer be used
* to create child elements, nor can any of its child elements.
*
* @param parent this is the node that is to be committed
*/
public void commit(OutputNode parent) throws Exception {
if(stack.contains(parent)) {
OutputNode top = stack.top();
if(!isCommitted(top)) {
writeStart(top);
}
while(stack.top() != parent) {
writeEnd(stack.pop());
}
writeEnd(parent);
stack.pop();
}
}
/**
* This method is used to remove the output node from the output
* buffer if that node has not yet been committed. This allows a
* node that has been created to be deleted, ensuring that it
* will not affect the resulting XML document structure.
*
* @param node this is the output node that is to be removed
*/
public void remove(OutputNode node) throws Exception {
if(stack.top() != node) {
throw new NodeException("Cannot remove node");
}
stack.pop();
}
/**
* This is used to create a new element under the specified node.
* This will effectively commit all nodes that are open until this
* node is encountered. Once the specified node is encountered on
* the stack a new element is created with the specified name.
*
* @param parent this is the node that is to be committed
* @param name this is the name of the start element to create
*
* @return this will return a child node for the given parent
*/
public OutputNode writeElement(OutputNode parent, String name) throws Exception {
if(stack.isEmpty()) {
return writeStart(parent, name);
}
if(stack.contains(parent)) {
OutputNode top = stack.top();
if(!isCommitted(top)) {
writeStart(top);
}
while(stack.top() != parent) {
writeEnd(stack.pop());
}
if(!stack.isEmpty()) {
writeValue(parent);
}
return writeStart(parent, name);
}
return null;
}
/**
* This is used to begin writing on a new XML element. This is
* typically done by writing any comments required. This will
* create an output node of the specified name before writing
* the comment, if any exists. Once the comment has been written
* the node is pushed on to the head of the output node stack.
*
* @param parent this is the parent node to the next output node
* @param name this is the name of the node that is to be created
*
* @return this returns an output node used for writing content
*/
private OutputNode writeStart(OutputNode parent, String name) throws Exception {
OutputNode node = new OutputElement(parent, this, name);
return stack.push(node);
}
/**
* This is used to write the XML element to the underlying buffer.
* The element is written in the order of element prefix and name
* followed by the attributes an finally the namespaces for the
* element. Once this is finished the element is committed to
*
* @param node this is the node that is to be fully written
*/
private void writeStart(OutputNode node) throws Exception {
writeComment(node);
writeName(node);
writeAttributes(node);
writeNamespaces(node);
}
/**
* This is used to write a comment to the document. Comments
* appear just before the element name, this allows an logical
* association between the comment and the node to be made.
*
* @param node this is the node that is to have its name written
*/
private void writeComment(OutputNode node) throws Exception {
String comment = node.getComment();
if(comment != null) {
writer.writeComment(comment);
}
}
/**
* This is used to write a new start element to the resulting XML
* document. This will create an output node of the specified
* name before writing the start tag. Once the tag is written
* the node is pushed on to the head of the output node stack.
*
* @param node this is the node that is to have its name written
*/
private void writeName(OutputNode node) throws Exception {
String prefix = node.getPrefix(verbose);
String name = node.getName();
// Don't indent if it is disabled on the parent.
boolean indent = node.getParent() == null || node.getParent()
.getIndentationMode() == IndentationMode.ENABLED;
if(name != null) {
writer.writeStart(name, prefix, indent);
}
}
/**
* This is used to write an element value to the resulting XML
* document. This will search the nodes parents for the write
* mode, if the mode is CDATA then that is what is used to write
* the data, otherwise the value is written as plain text.
*
* One side effect of this method is that it clears the value
* of the output node once it has been written to the XML. This
* is needed, it can however cause confusion within the API.
*
* @param node this is the node to write the value of
*/
private void writeValue(OutputNode node) throws Exception {
Mode mode = node.getMode();
String value = node.getValue();
if(value != null) {
for(OutputNode next : stack) {
if(mode != Mode.INHERIT) {
break;
}
mode = next.getMode();
}
writer.writeText(value, mode);
}
node.setValue(null);
}
/**
* This is used to write a new end element to the resulting XML
* document. This will acquire the name and value of the given
* node, if the node has a value that is written. Finally a new
* end tag is written to the document and the output is flushed.
*
* @param node this is the node that is to have an end tag
*/
private void writeEnd(OutputNode node) throws Exception {
String name = node.getName();
String prefix = node.getPrefix(verbose);
String value = node.getValue();
if(value != null) {
writeValue(node);
}
if(name != null) {
writer.writeEnd(name, prefix, node.getIndentationMode() == IndentationMode.ENABLED);
writer.flush();
}
}
/**
* This is used to write the attributes of the specified node to
* the output. This will iterate over each node entered on to
* the node. Once written the node is considered inactive.
*
* @param node this is the node to have is attributes written
*/
private void writeAttributes(OutputNode node) throws Exception {
NodeMap map = node.getAttributes();
for(String name : map) {
OutputNode entry = map.get(name);
String value = entry.getValue();
String prefix = entry.getPrefix(verbose);
writer.writeAttribute(name, value, prefix);
}
active.remove(node);
}
/**
* This is used to write the namespaces of the specified node to
* the output. This will iterate over each namespace entered on
* to the node. Once written the node is considered qualified.
*
* @param node this is the node to have is attributes written
*/
private void writeNamespaces(OutputNode node) throws Exception {
NamespaceMap map = node.getNamespaces();
for(String name : map) {
String prefix = map.getPrefix(name);
writer.writeNamespace(name, prefix);
}
}
}