org.simpleframework.xml.stream.DocumentReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of simple-xml Show documentation
Show all versions of simple-xml Show documentation
Simple is a high performance XML serialization and configuration framework for Java
/*
* DocumentReader.java January 2010
*
* Copyright (C) 2010, 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 static org.w3c.dom.Node.ELEMENT_NODE;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
* The DocumentReader
object provides an implementation
* for reading XML events using DOM. This reader flattens a document
* in to a series of nodes, and provides these nodes as events as
* they are encountered. Essentially what this does is adapt the
* document approach to navigating the XML and provides a streaming
* approach. Having an implementation based on DOM ensures that the
* library can be used on a wider variety of platforms.
*
* @author Niall Gallagher
*
* @see org.simpleframework.xml.stream.DocumentProvider
*/
class DocumentReader implements EventReader {
/**
* Any attribute beginning with this string has been reserved.
*/
private static final String RESERVED = "xml";
/**
* This is used to extract the nodes from the provided document.
*/
private NodeExtractor queue;
/**
* This is used to keep track of which elements are in context.
*/
private NodeStack stack;
/**
* This is used to keep track of any events that were peeked.
*/
private EventNode peek;
/**
* Constructor for the DocumentReader
object. This
* makes use of a DOM document to extract events and provide them
* to the core framework. All nodes will be extracted from the
* document and queued for extraction as they are requested. This
* will ignore any comment nodes as they should not be considered.
*
* @param document this is the document that is to be read
*/
public DocumentReader(Document document) {
this.queue = new NodeExtractor(document);
this.stack = new NodeStack();
this.stack.push(document);
}
/**
* This is used to peek at the node from the document. This will
* scan through the document, ignoring any comments to find the
* next relevant XML event to acquire. Typically events will be
* the start and end of an element, as well as any text nodes.
*
* @return this returns the next event taken from the document
*/
public EventNode peek() throws Exception {
if(peek == null) {
peek = next();
}
return peek;
}
/**
* This is used to take the next node from the document. This will
* scan through the document, ignoring any comments to find the
* next relevant XML event to acquire. Typically events will be
* the start and end of an element, as well as any text nodes.
*
* @return this returns the next event taken from the document
*/
public EventNode next() throws Exception {
EventNode next = peek;
if(next == null) {
next = read();
} else {
peek = null;
}
return next;
}
/**
* This is used to read the next node from the document. This will
* scan through the document, ignoring any comments to find the
* next relevant XML event to acquire. Typically events will be
* the start and end of an element, as well as any text nodes.
*
* @return this returns the next event taken from the document
*/
private EventNode read() throws Exception {
Node node = queue.peek();
if(node == null) {
return end();
}
return read(node);
}
/**
* This is used to read the next node from the document. This will
* scan through the document, ignoring any comments to find the
* next relevant XML event to acquire. Typically events will be
* the start and end of an element, as well as any text nodes.
*
* @param node this is the XML node that has been read
*
* @return this returns the next event taken from the document
*/
private EventNode read(Node node) throws Exception {
Node parent = node.getParentNode();
Node top = stack.top();
if(parent != top) {
if(top != null) {
stack.pop();
}
return end();
}
if(node != null) {
queue.poll();
}
return convert(node);
}
/**
* This is used to convert the provided node in to an event. The
* conversion process ensures the node can be digested by the core
* reader and used to provide an InputNode
that can
* be used to represent the XML elements or attributes. If the
* provided node is not an element then it is considered text.
*
* @param node the node that is to be converted to an event
*
* @return this returns an event created from the given node
*/
private EventNode convert(Node node) throws Exception{
short type = node.getNodeType();
if(type == ELEMENT_NODE) {
if(node != null) {
stack.push(node);
}
return start(node);
}
return text(node);
}
/**
* This is used to convert the provided node to a start event. The
* conversion process ensures the node can be digested by the core
* reader and used to provide an InputNode
that can
* be used to represent an XML elements within the source document.
*
* @param node the node that is to be converted to a start event
*
* @return this returns a start event created from the given node
*/
private Start start(Node node) {
Start event = new Start(node);
if(event.isEmpty()) {
return build(event);
}
return event;
}
/**
* This is used to build the attributes that are to be used to
* populate the start event. Populating the start event with the
* attributes it contains is required so that each element will
* contain its associated attributes. Only attributes that are
* not reserved will be added to the start event.
*
* @param event this is the start event that is to be populated
*
* @return this returns a start event with its attributes
*/
private Start build(Start event) {
NamedNodeMap list = event.getAttributes();
int length = list.getLength();
for (int i = 0; i < length; i++) {
Node node = list.item(i);
Attribute value = attribute(node);
if(!value.isReserved()) {
event.add(value);
}
}
return event;
}
/**
* This is used to convert the provided node to an attribute. The
* conversion process ensures the node can be digested by the core
* reader and used to provide an InputNode
that can
* be used to represent an XML attribute within the source document.
*
* @param node the node that is to be converted to an attribute
*
* @return this returns an attribute created from the given node
*/
private Entry attribute(Node node) {
return new Entry(node);
}
/**
* This is used to convert the provided node to a text event. The
* conversion process ensures the node can be digested by the core
* reader and used to provide an InputNode
that can
* be used to represent an XML attribute within the source document.
*
* @param node the node that is to be converted to a text event
*
* @return this returns the text event created from the given node
*/
private Text text(Node node) {
return new Text(node);
}
/**
* This is used to create a node event to signify that an element
* has just ended. End events are important as they allow the core
* reader to determine if a node is still in context. This provides
* a more convenient way to use InputNode
objects as
* they should only ever be able to extract their children.
*
* @return this returns an end event to signify an element close
*/
private End end() {
return new End();
}
/**
* The Entry
object is used to represent an attribute
* within a start element. This holds the name and value of the
* attribute as well as the namespace prefix and reference. These
* details can be used to represent the attribute so that should
* the core reader require these details they can be acquired.
*
* @author Niall Gallagher
*/
private static class Entry extends EventAttribute {
/**
* This is the node that is to be represented as an attribute.
*/
private final Node node;
/**
* Constructor for the Entry
object. This creates
* an attribute object that is used to extract the name, value
* namespace prefix, and namespace reference from the provided
* node. This is used to populate any start events created.
*
* @param node this is the node that represents the attribute
*/
public Entry(Node node) {
this.node = node;
}
/**
* This provides the name of the attribute. This will be the
* name of the XML attribute without any namespace prefix. If
* the name begins with "xml" then this attribute is reserved.
* according to the namespaces for XML 1.0 specification.
*
* @return this returns the name of this attribute object
*/
public String getName() {
return node.getLocalName();
}
/**
* This returns the value of the event. This will be the value
* that the attribute contains. If the attribute does not have
* a value then this returns null or an empty string.
*
* @return this returns the value represented by this object
*/
public String getValue() {
return node.getNodeValue();
}
/**
* This is used to acquire the namespace prefix associated with
* this attribute. A prefix is used to qualify the attribute
* within a namespace. So, if this has a prefix then it should
* have a reference associated with it.
*
* @return this returns the namespace prefix for the attribute
*/
public String getPrefix() {
return node.getPrefix();
}
/**
* This is used to acquire the namespace reference that this
* attribute is in. A namespace is normally associated with an
* attribute if that attribute is prefixed with a known token.
* If there is no prefix then this will return null.
*
* @return this provides the associated namespace reference
*/
public String getReference() {
return node.getNamespaceURI();
}
/**
* This returns true if the attribute is reserved. An attribute
* is considered reserved if it begins with "xml" according to
* the namespaces in XML 1.0 specification. Such attributes are
* used for namespaces and other such details.
*
* @return this returns true if the attribute is reserved
*/
public boolean isReserved() {
String prefix = getPrefix();
String name = getName();
if(prefix != null) {
return prefix.startsWith(RESERVED);
}
return name.startsWith(RESERVED);
}
/**
* This is used to return the node for the attribute. Because
* this represents a DOM attribute the DOM node is returned.
* Returning the node helps with certain debugging issues.
*
* @return this will return the source object for this
*/
public Object getSource() {
return node;
}
}
/**
* The Start
object is used to represent the start of
* an XML element. This will hold the attributes associated with
* the element and will provide the name, the namespace reference
* and the namespace prefix. For debugging purposes the source XML
* element is provided for this start event.
*
* @author Niall Gallagher
*/
private static class Start extends EventElement {
/**
* This is the element that is represented by this start event.
*/
private final Element element;
/**
* Constructor for the Start
object. This will
* wrap the provided node and expose the required details such
* as the name, namespace prefix and namespace reference. The
* provided element node can be acquired for debugging purposes.
*
* @param element this is the element being wrapped by this
*/
public Start(Node element) {
this.element = (Element)element;
}
/**
* This provides the name of the event. This will be the name
* of an XML element the event represents. If there is a prefix
* associated with the element, this extracts that prefix.
*
* @return this returns the name without the namespace prefix
*/
public String getName() {
return element.getLocalName();
}
/**
* This is used to acquire the namespace prefix associated with
* this node. A prefix is used to qualify an XML element or
* attribute within a namespace. So, if this represents a text
* event then a namespace prefix is not required.
*
* @return this returns the namespace prefix for this event
*/
public String getPrefix() {
return element.getPrefix();
}
/**
* This is used to acquire the namespace reference that this
* node is in. A namespace is normally associated with an XML
* element or attribute, so text events and element close events
* are not required to contain any namespace references.
*
* @return this will provide the associated namespace reference
*/
public String getReference() {
return element.getNamespaceURI();
}
/**
* This is used to acquire the attributes associated with the
* element. Providing the attributes in this format allows
* the reader to build a list of attributes for the event.
*
* @return this returns the attributes associated with this
*/
public NamedNodeMap getAttributes(){
return element.getAttributes();
}
/**
* This is used to return the node for the event. Because this
* represents a DOM element node the DOM node will be returned.
* Returning the node helps with certain debugging issues.
*
* @return this will return the source object for this event
*/
public Object getSource() {
return element;
}
}
/**
* The Text
object is used to represent a text event.
* If wraps a node that holds text consumed from the document.
* These are used by InputNode
objects to extract the
* text values for elements For debugging this exposes the node.
*
* @author Niall Gallagher
*/
private static class Text extends EventToken {
/**
* This is the node that is used to represent the text value.
*/
private final Node node;
/**
* Constructor for the Text
object. This creates
* an event that provides text to the core reader. Text can be
* in the form of a CDATA section or a normal text entry.
*
* @param node this is the node that represents the text value
*/
public Text(Node node) {
this.node = node;
}
/**
* This is true as this event represents a text token. Text
* tokens are required to provide a value only. So namespace
* details and the node name will always return null.
*
* @return this returns true as this event represents text
*/
public boolean isText() {
return true;
}
/**
* This returns the value of the event. This will return the
* text value contained within the node. If there is no
* text within the node this should return an empty string.
*
* @return this returns the value represented by this event
*/
public String getValue(){
return node.getNodeValue();
}
/**
* This is used to return the node for the event. Because this
* represents a DOM text value the DOM node will be returned.
* Returning the node helps with certain debugging issues.
*
* @return this will return the source object for this event
*/
public Object getSource() {
return node;
}
}
/**
* The End
object is used to represent the end of an
* element. It is used by the core reader to determine which nodes
* are in context and which ones are out of context. This allows
* the input nodes to determine if it can read any more children.
*
* @author Niall Gallagher
*/
private static class End extends EventToken {
/**
* This is true as this event represents an element end. Such
* events are required by the core reader to determine if a
* node is still in context. This helps to determine if there
* are any more children to be read from a specific node.
*
* @return this returns true as this token represents an end
*/
public boolean isEnd() {
return true;
}
}
}