org.exolab.castor.types.AnyNode Maven / Gradle / Ivy
Show all versions of castor-xml Show documentation
/*
* Redistribution and use of this software and associated documentation ("Software"), with or
* without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright statements and notices. Redistributions
* must also contain a copy of this document.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials provided with
* the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
* without prior written permission of Intalio, Inc. For written permission, please contact
* [email protected].
*
* 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
* their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
* Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 2001 (C) Intalio, Inc. All Rights Reserved.
*
* $Id$ Date Author Changes 04/24/2001 Arnaud Blandin Rewrited from scratch 04/22/2001 Arnaud
* Blandin Clean-up and support of comments 04/04/2001 Arnaud Blandin Created
*/
package org.exolab.castor.types;
import java.io.StringWriter;
import java.util.Stack;
import org.castor.xml.BackwardCompatibilityContext;
import org.exolab.castor.xml.Serializer;
import org.exolab.castor.xml.util.AnyNode2SAX;
/**
* A class used to represent an XML node. This is an alternative to DOM which is too heavy for our
* purpose (mainly handle XML Fragment when {@literal } is used in an XML schema). The model is
* based on XPath Node. An AnyNode can be a:
*
* - ELEMENT
* - ATTRIBUTE
* - NAMESPACE
* - COMMENT
* - TEXT
* - PROCESSING INSTRUCTION
*
*
* @author Assaf Arkin
* @author Arnaud Blandin
* @author Keith Visco
* @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
*/
public final class AnyNode implements java.io.Serializable {
// TODO Processing Instructions
// TODO Full handling of namespaces
/** SerialVersionUID */
private static final long serialVersionUID = -4104117996051705975L;
/**
* The prefix for XML namespace
*/
private final static String XMLNS_PREFIX = "xmlns";
/**
* Representation for an element node.
*/
public static final short ELEMENT = 1;
/**
* Representation for an attribute node.
*/
public static final short ATTRIBUTE = 2;
/**
* Representation for a Namespace node.
*/
public static final short NAMESPACE = 3;
/**
* Representation for a processing instruction node.
*/
public static final short PI = 4;
/**
* Representation for a comment node.
*/
public static final short COMMENT = 5;
/**
* Representation for a text node.
*/
public static final short TEXT = 6;
/**
* The type of the current node. ELEMENT is the default value.
*/
private short _nodeType = ELEMENT;
/**
* The next sibling of this AnyNode
*/
private AnyNode _nextSiblingNode = null;
/**
* The first child of this AnyNode
*/
private AnyNode _firstChildNode = null;
/**
* the local name of the current node.
*/
private String _localName;
/**
* the Namespace URI of the current node
*/
private String _uri;
/**
* The prefix of the Namespace
*/
private String _prefix;
/**
* A stack used for avoiding endless loop in toString()
*/
private static Stack _elements;
/**
* The namespace context used in the toString()
*/
/**
* The value of this node defined as follow:
*
* - for an element the value is its TEXT NODE value (if any)
* - for an attribute the value is the value of the attribute
* - for a text node it is the character data
* - for a namespace it is the namespace URI that is being bound to the namespace prefix
* - for a comment it is the content of the comment not including the opening <!-- and the
* closing -->.
*
*/
private String _value;
/**
* Default constructor: creates an empty element node
*/
public AnyNode() {
this(ELEMENT, null, null, null, null);
}
/**
* Creates a node given all the necessary information: type, localName, prefix, uri and value.
* This constructor is not user-friendly and launched RunTime exception is you try to instantiate
* non-valid node.
*
* @param type the node type.
* @param localName the name of the node.
* @param prefix the prefix if any of the namespace.
* @param uri the namespace uri of this node.
* @param value the value of this node.
*/
public AnyNode(short type, String localName, String prefix, String uri, String value) {
if ((type > 6) && (type < 1)) {
throw new IllegalArgumentException("Illegal node type");
}
_nodeType = type;
// comment and text nodes don't have name
if ((type > PI) && (localName != null)) {
String err = "This node can not have a local name";
throw new IllegalArgumentException(err);
}
_localName = localName;
// for comment, pi or text we should have no namespaces
if ((type > NAMESPACE) && ((uri != null) || (prefix != null))) {
String err = "This node can not handle namespace";
throw new IllegalArgumentException(err);
}
_uri = uri;
_prefix = prefix;
// attributes can not be namespaces
if (type == AnyNode.ATTRIBUTE)
if (localName.startsWith(XMLNS_PREFIX)) {
String err = "Namespaces can't be used as attributes.";
throw new IllegalArgumentException(err);
}
// you can't set value for element
if ((type == ELEMENT) && (value != null)) {
String err = "You can't set a value for this node type";
throw new IllegalArgumentException(err);
}
_value = value;
}
/**
* Adds an AnyNode to the current node
*
* @param node the node to append
*/
public void addAnyNode(AnyNode node) {
if (node == null) {
throw new IllegalArgumentException("null argument in addAnyNode");
}
switch (node.getNodeType()) {
case ATTRIBUTE:
addAttribute(node);
break;
case NAMESPACE:
addNamespace(node);
break;
default:
addChild(node);
break;
}
}
/**
*
* Adds a child AnyNode to this node. A 'child' can be either an ELEMENT node, a COMMENT node, a
* TEXT node or a PROCESSING INSTRUCTION. If the current node already has a child then the node to
* add will be append as a sibling.
*
* Note: you cannot add a child to a TEXT node.
*
* @param node the node to add.
*/
public void addChild(AnyNode node) {
if (node == null) {
throw new IllegalArgumentException("null argument in appendChild");
}
if (node.getNodeType() == ATTRIBUTE || node.getNodeType() == NAMESPACE) {
throw new IllegalArgumentException("An Attribute or an Namespace can't be added as a child");
}
if (this.getNodeType() == TEXT) {
throw new IllegalArgumentException("a TEXT node can't have children.");
}
if (_firstChildNode == null) {
_firstChildNode = node;
} else if (_firstChildNode.getNodeType() == ATTRIBUTE
|| _firstChildNode.getNodeType() == NAMESPACE) {
_firstChildNode.addChild(node);
} else {
_firstChildNode.appendSibling(node);
}
}
/**
* Adds an attribute to the current node.
*
* @param node the attribute to add.
*/
public void addAttribute(AnyNode node) {
if (node == null) {
throw new IllegalArgumentException("null argument in addAttribute");
}
if (node.getNodeType() != ATTRIBUTE) {
throw new IllegalArgumentException("Only attribute can be added as an attribute");
}
if (_firstChildNode == null) {
_firstChildNode = node;
} else {
if (_firstChildNode.getNodeType() == ATTRIBUTE) {
// if we reach an attribute then we add the node as its sibling
_firstChildNode.appendSibling(node);
} else if (_firstChildNode.getNodeType() == NAMESPACE) {
// if we reach an namespace the attributre should be added to
// the first child of the namespace
_firstChildNode.addAttribute(node);
} else {
// unplug the current firstNode to add a new one
node.addChild(_firstChildNode);
_firstChildNode = node;
}
}
} // addAttribute
/**
* Appends an namespace to the current node.
*
* @param node the attribute to add.
*/
public void addNamespace(AnyNode node) {
if (node == null) {
throw new IllegalArgumentException("null argument in addNamespace");
}
if (node.getNodeType() != NAMESPACE) {
throw new IllegalArgumentException("Only namespace can be added as an namespace");
}
if (_firstChildNode == null) {
_firstChildNode = node;
} else {
if (_firstChildNode.getNodeType() == NAMESPACE) {
// if we reach an namepace then we add the node as its sibling
_firstChildNode.appendSibling(node);
} else if (_firstChildNode.getNodeType() == ATTRIBUTE) {
// if we reach an attribute the attributre should be added to
// the first child of the attribute
_firstChildNode.addNamespace(node);
} else {
// unplug the current firstNode to add a new one
node.addChild(_firstChildNode);
_firstChildNode = node;
}
}
} // addNamespace
/**
* Returns the first attribute of the current ELEMENT node or null. The next attribute,if any,is
* the sibling of the returned node.
*/
public AnyNode getFirstAttribute() {
if (this.getNodeType() != ELEMENT) {
String err = "This node type can not contain attributes";
throw new UnsupportedOperationException(err);
}
boolean found = false;
AnyNode tempNode = this.getFirstChildNode();
while (tempNode != null && !found) {
short type = tempNode.getNodeType();
// if the child is not an attribute or a namespace
// this element does not have any attribute
if (type == ELEMENT || type == COMMENT || type == TEXT || type == PI) {
tempNode = null;
} else if (type == NAMESPACE) {
tempNode = tempNode.getFirstChildNode();
} else {
found = true;
}
}
return tempNode;
}
/**
* Returns the first namespace of the current ELEMENT node or null. The next attribute if any is
* the sibling of the returned node.
*
* @return the first namespace of the current ELEMENT node or null.
*/
public AnyNode getFirstNamespace() {
if (this.getNodeType() != ELEMENT) {
String err = "This node type can not contain namespaces";
throw new UnsupportedOperationException(err);
}
AnyNode tempNode = this.getFirstChildNode();
boolean found = false;
while (tempNode != null && !found) {
short type = tempNode.getNodeType();
// if the child is not an attribute or a namespace
// this element does not have any namespace
if (type == ELEMENT || type == COMMENT || type == TEXT || type == PI) {
tempNode = null;
} else if (type == ATTRIBUTE) {
tempNode = tempNode.getFirstChildNode();
} else {
found = true;
}
}
return tempNode;
}
/**
* Returns the first Child node of this node. A 'child' can be either an ELEMENT node, a COMMENT
* node, a TEXT node or a PROCESSING INSTRUCTION.
*
* @return the first child of this node
*/
public AnyNode getFirstChild() {
// an ATTRIBUTE or a NAMESPACE can not
// have children
if (this.getNodeType() == ATTRIBUTE || this.getNodeType() == NAMESPACE) {
return null;
}
// loop througth the first two (in the worst case) nodes
// and then return the firstChild if any
AnyNode tempNode = this.getFirstChildNode();
boolean found = false;
while (tempNode != null && !found) {
short type = tempNode.getNodeType();
if (type == ELEMENT || type == COMMENT || type == TEXT || type == PI) {
found = true;
} else if (type == ATTRIBUTE || type == NAMESPACE) {
tempNode = tempNode.getFirstChildNode();
}
}
return tempNode;
}
/**
* Returns the next sibling of the current node. When the AnyNode is an ATTRIBUTE, it will return
* the next ATTRIBUTE node. When the AnyNode is a NAMESPACE, it will return the next NAMESPACE
* node.
*
* @return the next sibling of the current node
*/
public AnyNode getNextSibling() {
return _nextSiblingNode;
}
/**
* Returns the type of this node.
*
* @return The type of this node
*/
public short getNodeType() {
return _nodeType;
}
/**
* Returns the local name of the node. Returns the local name of an element or attribute, the
* prefix of a namespace node, the target of a processing instruction, or null for all other node
* types.
*
* @return The local name of the node, or null if the node has no name
*/
public String getLocalName() {
return _localName;
}
/**
* Returns the namespace URI of the node. Returns the namespace URI of an element, attribute or
* namespace node, or null for all other node types.
*
* @return The namespace URI of the node, or null if the node has no namespace URI
*/
public String getNamespaceURI() {
return _uri;
}
/**
* Returns the string value of the node. The string value of a text node or an attribute node is
* its text value. The string value of an element or a root node is the concatenation of the
* string value of all its child nodes. The string value of a namespace node is its namespace URI.
* The string value of a processing instruction is the instruction, and the string value of a
* comment is the comment text.
*
* @return The string value of the node
*/
public String getStringValue() {
switch (_nodeType) {
case ATTRIBUTE:
case TEXT:
return _value;
case NAMESPACE:
return _uri;
// not yet supported
case PI:
return "";
case COMMENT:
return _value;
case ELEMENT:
StringBuffer result = new StringBuffer(4096);
AnyNode tempNode = this.getNextSibling();
while (tempNode != null && tempNode.getNodeType() == TEXT) {
result.append(tempNode.getStringValue());
tempNode = tempNode.getNextSibling();
}
tempNode = this.getFirstChild();
while (tempNode != null) {
result.append(tempNode.getStringValue());
tempNode = tempNode.getNextSibling();
}
tempNode = null;
return result.toString();
default:
return null;
}
}
/**
* Returns the namespace prefix associated with the namespace URI of this node. Returns null if no
* prefix. is defined for this namespace URI. Returns an empty string if the default prefix is
* associated with this namespace URI.
*
* @return The namespace prefix, or null
*/
public String getNamespacePrefix() {
return _prefix;
}
/**
* Returns the String representation of this AnyNode. The String representation is a xml
* well-formed fragment corresponding to the representation of this node.
*
* @return the String representation of this AnyNode.
*/
public String toString() {
Serializer serializer = new BackwardCompatibilityContext().getSerializer();
if (serializer == null) {
throw new RuntimeException("Unable to obtain serializer");
}
StringWriter writer = new StringWriter();
serializer.setOutputCharStream(writer);
try {
AnyNode2SAX.fireEvents(this, serializer.asDocumentHandler());
} catch (java.io.IOException ioe) {
return privateToString();
} catch (org.xml.sax.SAXException saxe) {
throw new RuntimeException(saxe.getMessage());
}
return writer.toString();
}
private String privateToString() {
StringBuilder sb = new StringBuilder(4096);
if (_elements == null) {
_elements = new Stack<>();
}
// check the Stack too see if we have
// already proceed the node
if (_elements.search(this) == -1) {
_elements.push(this);
if (this.getNodeType() == ELEMENT) {
// open the tag
sb.append('<');
String prefix = getNamespacePrefix();
if (prefix != null) {
sb.append(prefix).append(':');
}
prefix = null;
sb.append(getLocalName());
// append the attributes
AnyNode tempNode = this.getFirstAttribute();
while (tempNode != null) {
sb.append(' ').append(tempNode.getLocalName()).append("='")
.append(tempNode.getStringValue()).append('\'');
tempNode = tempNode.getNextSibling();
}
// append the namespaces
tempNode = this.getFirstNamespace();
while (tempNode != null) {
sb.append(' ').append(XMLNS_PREFIX);
prefix = tempNode.getNamespacePrefix();
if (prefix != null && prefix.length() != 0) {
sb.append(':').append(prefix);
}
sb.append("='").append(tempNode.getNamespaceURI()).append('\'');
tempNode = tempNode.getNextSibling();
} // namespaceNode
tempNode = this.getFirstChild();
if (tempNode != null) {
sb.append('>');
while (tempNode != null) {
sb.append(tempNode.privateToString());
tempNode = tempNode.getNextSibling();
}
// close the tag
sb.append("');
} else {
sb.append("/>");
}
} else {
sb.append(this.getStringValue());
}
}
return sb.toString();
}// toString()
/**
* Appends a sibling AnyNode to the current node. The node to append will be added at the end of
* the sibling branch.
*
* @param node the node to add
*/
protected void appendSibling(AnyNode node) {
if (node == null) {
throw new IllegalArgumentException();
}
if (((node.getNodeType() == ATTRIBUTE) || (node.getNodeType() == NAMESPACE))
&& (this.getNodeType() != node.getNodeType())) {
String err =
"a NAMESPACE or an ATTRIBUTE can only be add as a sibling to a node of the same type";
throw new UnsupportedOperationException(err);
}
if (_nextSiblingNode == null) {
// if we already have a TEXT node -> merge
if ((node.getNodeType() == TEXT) && (this.getNodeType() == TEXT)) {
mergeTextNode(this, node);
} else {
_nextSiblingNode = node;
}
} else {
_nextSiblingNode.appendSibling(node);
}
}
/**
* Returns the first child node in the tree.
*
* @return the first child node in the tree.
*/
protected AnyNode getFirstChildNode() {
return _firstChildNode;
}
/**
* Adds the text value of a TEXT node to another TEXT node.
*
* @param node1 the AnyNode that receives the text value
* @param node2 the AnyNode that needs to be merges with node1.
*/
private void mergeTextNode(AnyNode node1, AnyNode node2) {
if (node1.getNodeType() != node2.getNodeType()) {
return;
}
if (node1.getNodeType() != AnyNode.TEXT) {
return;
}
node1._value = node1.getStringValue() + node2.getStringValue();
node2 = null;
}
}