0001: package net.sf.saxon.dom;
0002:
0003: import net.sf.saxon.Configuration;
0004: import net.sf.saxon.event.Receiver;
0005: import net.sf.saxon.om.*;
0006: import net.sf.saxon.pattern.NameTest;
0007: import net.sf.saxon.pattern.NodeTest;
0008: import net.sf.saxon.style.StandardNames;
0009: import net.sf.saxon.trans.XPathException;
0010: import net.sf.saxon.type.Type;
0011: import net.sf.saxon.value.UntypedAtomicValue;
0012: import net.sf.saxon.value.Value;
0013: import org.w3c.dom.*;
0014:
0015: import java.lang.reflect.InvocationTargetException;
0016: import java.lang.reflect.Method;
0017: import java.util.ArrayList;
0018:
0019: /**
0020: * A node in the XML parse tree representing an XML element, character content, or attribute.<P>
0021: * This is the implementation of the NodeInfo interface used as a wrapper for DOM nodes.
0022: */
0023:
0024: public class NodeWrapper implements NodeInfo, VirtualNode,
0025: SiblingCountingNode {
0026:
0027: protected Node node;
0028: private int namecode = -1;
0029: protected short nodeKind;
0030: private NodeWrapper parent; // null means unknown
0031: protected DocumentWrapper docWrapper;
0032: protected int index; // -1 means unknown
0033: protected int span = 1; // the number of adjacent text nodes wrapped by this NodeWrapper.
0034:
0035: // If span>1, node will always be the first of a sequence of adjacent text nodes
0036:
0037: /**
0038: * This constructor is protected: nodes should be created using the makeWrapper
0039: * factory method
0040: * @param node The DOM node to be wrapped
0041: * @param parent The NodeWrapper that wraps the parent of this node
0042: * @param index Position of this node among its siblings
0043: */
0044: protected NodeWrapper(Node node, NodeWrapper parent, int index) {
0045: this .node = node;
0046: this .parent = parent;
0047: this .index = index;
0048: }
0049:
0050: /**
0051: * Factory method to wrap a DOM node with a wrapper that implements the Saxon
0052: * NodeInfo interface.
0053: * @param node The DOM node
0054: * @param docWrapper The wrapper for the containing Document node
0055: * @return The new wrapper for the supplied node
0056: * @throws NullPointerException if the node or the document wrapper are null
0057: */
0058: protected NodeWrapper makeWrapper(Node node,
0059: DocumentWrapper docWrapper) {
0060: if (node == null) {
0061: throw new NullPointerException(
0062: "NodeWrapper#makeWrapper: Node must not be null");
0063: }
0064: if (docWrapper == null) {
0065: throw new NullPointerException(
0066: "NodeWrapper#makeWrapper: DocumentWrapper must not be null");
0067: }
0068: return makeWrapper(node, docWrapper, null, -1);
0069: }
0070:
0071: /**
0072: * Factory method to wrap a DOM node with a wrapper that implements the Saxon
0073: * NodeInfo interface.
0074: * @param node The DOM node
0075: * @param docWrapper The wrapper for the containing Document node *
0076: * @param parent The wrapper for the parent of the JDOM node
0077: * @param index The position of this node relative to its siblings
0078: * @return The new wrapper for the supplied node
0079: */
0080:
0081: protected NodeWrapper makeWrapper(Node node,
0082: DocumentWrapper docWrapper, NodeWrapper parent, int index) {
0083: NodeWrapper wrapper;
0084: switch (node.getNodeType()) {
0085: case Node.DOCUMENT_NODE:
0086: return docWrapper;
0087: case Node.ELEMENT_NODE:
0088: wrapper = new NodeWrapper(node, parent, index);
0089: wrapper.nodeKind = Type.ELEMENT;
0090: break;
0091: case Node.ATTRIBUTE_NODE:
0092: wrapper = new NodeWrapper(node, parent, index);
0093: wrapper.nodeKind = Type.ATTRIBUTE;
0094: break;
0095: case Node.TEXT_NODE:
0096: wrapper = new NodeWrapper(node, parent, index);
0097: wrapper.nodeKind = Type.TEXT;
0098: break;
0099: case Node.CDATA_SECTION_NODE:
0100: wrapper = new NodeWrapper(node, parent, index);
0101: wrapper.nodeKind = Type.TEXT;
0102: break;
0103: case Node.COMMENT_NODE:
0104: wrapper = new NodeWrapper(node, parent, index);
0105: wrapper.nodeKind = Type.COMMENT;
0106: break;
0107: case Node.PROCESSING_INSTRUCTION_NODE:
0108: wrapper = new NodeWrapper(node, parent, index);
0109: wrapper.nodeKind = Type.PROCESSING_INSTRUCTION;
0110: break;
0111: default:
0112: throw new IllegalArgumentException(
0113: "Unsupported node type in DOM! "
0114: + node.getNodeType() + " instance "
0115: + node.toString());
0116: }
0117: wrapper.docWrapper = docWrapper;
0118: return wrapper;
0119: }
0120:
0121: /**
0122: * Get the underlying DOM node, to implement the VirtualNode interface
0123: */
0124:
0125: public Object getUnderlyingNode() {
0126: return node;
0127: }
0128:
0129: /**
0130: * Get the configuration
0131: */
0132:
0133: public Configuration getConfiguration() {
0134: return docWrapper.getConfiguration();
0135: }
0136:
0137: /**
0138: * Get the name pool for this node
0139: * @return the NamePool
0140: */
0141:
0142: public NamePool getNamePool() {
0143: return docWrapper.getNamePool();
0144: }
0145:
0146: /**
0147: * Return the type of node.
0148: * @return one of the values Node.ELEMENT, Node.TEXT, Node.ATTRIBUTE, etc.
0149: */
0150:
0151: public int getNodeKind() {
0152: return nodeKind;
0153: }
0154:
0155: /**
0156: * Get the typed value of the item
0157: */
0158:
0159: public SequenceIterator getTypedValue() {
0160: return SingletonIterator.makeIterator(new UntypedAtomicValue(
0161: getStringValueCS()));
0162: }
0163:
0164: /**
0165: * Get the typed value. The result of this method will always be consistent with the method
0166: * {@link net.sf.saxon.om.Item#getTypedValue()}. However, this method is often more convenient and may be
0167: * more efficient, especially in the common case where the value is expected to be a singleton.
0168: *
0169: * @return the typed value. If requireSingleton is set to true, the result will always be an
0170: * AtomicValue. In other cases it may be a Value representing a sequence whose items are atomic
0171: * values.
0172: * @since 8.5
0173: */
0174:
0175: public Value atomize() throws XPathException {
0176: return new UntypedAtomicValue(getStringValueCS());
0177: }
0178:
0179: /**
0180: * Get the type annotation
0181: * @return -1 (there is no type annotation)
0182: */
0183:
0184: public int getTypeAnnotation() {
0185: return -1;
0186: }
0187:
0188: /**
0189: * Determine whether this is the same node as another node. <br />
0190: * Note: a.isSameNodeInfo(b) if and only if generateId(a)==generateId(b)
0191: * @return true if this Node object and the supplied Node object represent the
0192: * same node in the tree.
0193: */
0194:
0195: public boolean isSameNodeInfo(NodeInfo other) {
0196: // DOM does not offer any guarantees that the same node is always represented
0197: // by the same object
0198:
0199: if (!(other instanceof NodeWrapper)) {
0200: return false;
0201: }
0202:
0203: // For a level-3 DOM, use the DOM isSameNode() method
0204: if (docWrapper.level3) {
0205: try {
0206: Class[] argClasses = { Node.class };
0207: Method isSameNode = Node.class.getMethod("isSameNode",
0208: argClasses);
0209: Object[] args = { ((NodeWrapper) other).node };
0210: Boolean b = (Boolean) (isSameNode.invoke(node, args));
0211: return b.booleanValue();
0212: } catch (NoSuchMethodException e) {
0213: // use fallback implementation
0214: } catch (IllegalAccessException e) {
0215: // use fallback implementation
0216: } catch (InvocationTargetException e) {
0217: // use fallback implementation
0218: }
0219: }
0220: NodeWrapper ow = (NodeWrapper) other;
0221: return getNodeKind() == ow.getNodeKind()
0222: && getNameCode() == ow.getNameCode()
0223: && // redundant, but gives a quick exit
0224: getSiblingPosition() == ow.getSiblingPosition()
0225: && getParent().isSameNodeInfo(ow.getParent());
0226: // Note: this has been known to fail because getParent() returns null. Extra checks have been added to the
0227: // methods for constructing DOM document wrappers to ensure that wrapped nodes always belong to a wrapped
0228: // Document, which should prevent this happening again.
0229: }
0230:
0231: /**
0232: * Get the System ID for the node.
0233: * @return the System Identifier of the entity in the source document containing the node,
0234: * or null if not known. Note this is not the same as the base URI: the base URI can be
0235: * modified by xml:base, but the system ID cannot.
0236: */
0237:
0238: public String getSystemId() {
0239: return docWrapper.baseURI;
0240: }
0241:
0242: public void setSystemId(String uri) {
0243: docWrapper.baseURI = uri;
0244: }
0245:
0246: /**
0247: * Get the Base URI for the node, that is, the URI used for resolving a relative URI contained
0248: * in the node. In the DOM model, base URIs are held only an the document level.
0249: */
0250:
0251: public String getBaseURI() {
0252: NodeInfo n = this ;
0253: if (getNodeKind() != Type.ELEMENT) {
0254: n = getParent();
0255: }
0256: // Look for an xml:base attribute
0257: while (n != null) {
0258: String xmlbase = n
0259: .getAttributeValue(StandardNames.XML_BASE);
0260: if (xmlbase != null) {
0261: return xmlbase;
0262: }
0263: n = n.getParent();
0264: }
0265: // if not found, return the base URI of the document node
0266: return docWrapper.baseURI;
0267: }
0268:
0269: /**
0270: * Get line number
0271: * @return the line number of the node in its original source document; or -1 if not available
0272: */
0273:
0274: public int getLineNumber() {
0275: return -1;
0276: }
0277:
0278: /**
0279: * Determine the relative position of this node and another node, in document order.
0280: * The other node will always be in the same document.
0281: * @param other The other node, whose position is to be compared with this node
0282: * @return -1 if this node precedes the other node, +1 if it follows the other
0283: * node, or 0 if they are the same node. (In this case, isSameNode() will always
0284: * return true, and the two nodes will produce the same result for generateId())
0285: */
0286:
0287: public int compareOrder(NodeInfo other) {
0288: // Use the DOM Level-3 compareDocumentPosition() method if available
0289: if (docWrapper.level3 && other instanceof NodeWrapper) {
0290: if (isSameNodeInfo(other)) {
0291: return 0;
0292: }
0293: try {
0294: Class[] argClasses = { Node.class };
0295: Method compareDocumentPosition = Node.class.getMethod(
0296: "compareDocumentPosition", argClasses);
0297: Object[] args = { ((NodeWrapper) other).node };
0298: Short i = (Short) (compareDocumentPosition.invoke(node,
0299: args));
0300: switch (i.shortValue()) {
0301: // the symbolic constants require JDK 1.5
0302: case 2: //Node.DOCUMENT_POSITION_PRECEDING:
0303: case 8: //Node.DOCUMENT_POSITION_CONTAINS:
0304: return +1;
0305: case 4: //Node.DOCUMENT_POSITION_FOLLOWING:
0306: case 16: //Node.DOCUMENT_POSITION_CONTAINED_BY:
0307: return -1;
0308: default:
0309: // use fallback implementation
0310: }
0311: } catch (NoSuchMethodException e) {
0312: // use fallback implementation
0313: } catch (IllegalAccessException e) {
0314: // use fallback implementation
0315: } catch (InvocationTargetException e) {
0316: // use fallback implementation
0317: } catch (DOMException e) {
0318: // use fallback implementation
0319: }
0320: }
0321:
0322: if (other instanceof SiblingCountingNode) {
0323: return Navigator.compareOrder(this ,
0324: (SiblingCountingNode) other);
0325: } else {
0326: // it's presumably a Namespace Node
0327: return -other.compareOrder(this );
0328: }
0329: }
0330:
0331: /**
0332: * Return the string value of the node. The interpretation of this depends on the type
0333: * of node. For an element it is the accumulated character content of the element,
0334: * including descendant elements.
0335: * @return the string value of the node
0336: */
0337:
0338: public String getStringValue() {
0339: return getStringValueCS().toString();
0340:
0341: }
0342:
0343: /**
0344: * Get the value of the item as a CharSequence. This is in some cases more efficient than
0345: * the version of the method that returns a String.
0346: */
0347:
0348: public CharSequence getStringValueCS() {
0349: //return getStringValue(node, nodeKind);
0350: switch (nodeKind) {
0351: case Type.DOCUMENT:
0352: case Type.ELEMENT:
0353: NodeList children1 = node.getChildNodes();
0354: StringBuffer sb1 = new StringBuffer(16);
0355: expandStringValue(children1, sb1);
0356: return sb1;
0357:
0358: case Type.ATTRIBUTE:
0359: return ((Attr) node).getValue();
0360:
0361: case Type.TEXT:
0362: if (span == 1) {
0363: return node.getNodeValue();
0364: } else {
0365: FastStringBuffer fsb = new FastStringBuffer(100);
0366: Node textNode = node;
0367: for (int i = 0; i < span; i++) {
0368: fsb.append(textNode.getNodeValue());
0369: textNode = textNode.getNextSibling();
0370: }
0371: return fsb.condense();
0372: }
0373:
0374: case Type.COMMENT:
0375: case Type.PROCESSING_INSTRUCTION:
0376: return node.getNodeValue();
0377:
0378: default:
0379: return "";
0380: }
0381: }
0382:
0383: /**
0384: * Get the string value of a DOM node
0385: * @param node
0386: * @param nodeKind
0387: * @return
0388: */
0389:
0390: private static CharSequence getStringValue(Node node, int nodeKind) {
0391: switch (nodeKind) {
0392: case Type.DOCUMENT:
0393: case Type.ELEMENT:
0394: NodeList children1 = node.getChildNodes();
0395: StringBuffer sb1 = new StringBuffer(16);
0396: expandStringValue(children1, sb1);
0397: return sb1;
0398:
0399: case Type.ATTRIBUTE:
0400: return ((Attr) node).getValue();
0401:
0402: case Type.TEXT:
0403: case Node.CDATA_SECTION_NODE:
0404: return node.getNodeValue();
0405:
0406: case Type.COMMENT:
0407: case Type.PROCESSING_INSTRUCTION:
0408: return node.getNodeValue();
0409:
0410: default:
0411: return "";
0412: }
0413: }
0414:
0415: private static void expandStringValue(NodeList list, StringBuffer sb) {
0416: for (int i = 0; i < list.getLength(); i++) {
0417: Node child = list.item(i);
0418: switch (child.getNodeType()) {
0419: case Node.ELEMENT_NODE:
0420: expandStringValue(child.getChildNodes(), sb);
0421: break;
0422: default:
0423: sb.append(child.getNodeValue());
0424: }
0425: }
0426: }
0427:
0428: /**
0429: * Get name code. The name code is a coded form of the node name: two nodes
0430: * with the same name code have the same namespace URI, the same local name,
0431: * and the same prefix. By masking the name code with &0xfffff, you get a
0432: * fingerprint: two nodes with the same fingerprint have the same local name
0433: * and namespace URI.
0434: * @see NamePool#allocate allocate
0435: */
0436:
0437: public int getNameCode() {
0438: if (namecode != -1) {
0439: // this is a memo function
0440: return namecode;
0441: }
0442: int nodeKind = getNodeKind();
0443: if (nodeKind == Type.ELEMENT || nodeKind == Type.ATTRIBUTE) {
0444: String prefix = node.getPrefix();
0445: if (prefix == null) {
0446: prefix = "";
0447: }
0448: namecode = docWrapper.getNamePool().allocate(prefix,
0449: getURI(), getLocalPart());
0450: return namecode;
0451: } else if (nodeKind == Type.PROCESSING_INSTRUCTION) {
0452: namecode = docWrapper.getNamePool().allocate("", "",
0453: getLocalPart());
0454: return namecode;
0455: } else {
0456: return -1;
0457: }
0458: }
0459:
0460: /**
0461: * Get fingerprint. The fingerprint is a coded form of the expanded name
0462: * of the node: two nodes
0463: * with the same name code have the same namespace URI and the same local name.
0464: * A fingerprint of -1 should be returned for a node with no name.
0465: */
0466:
0467: public int getFingerprint() {
0468: int nc = getNameCode();
0469: if (nc == -1) {
0470: return -1;
0471: }
0472: return nc & 0xfffff;
0473: }
0474:
0475: /**
0476: * Get the local part of the name of this node. This is the name after the ":" if any.
0477: * @return the local part of the name. For an unnamed node, returns null, except for
0478: * un unnamed namespace node, which returns "".
0479: */
0480:
0481: public String getLocalPart() {
0482: String s = node.getLocalName();
0483: if (s == null) {
0484: // Crimson returns null for the attribute "xml:space": test axes-dom132
0485: String n = getDisplayName();
0486: int colon = n.indexOf(':');
0487: if (colon >= 0) {
0488: return n.substring(colon + 1);
0489: }
0490: return n;
0491: } else {
0492: return s;
0493: }
0494: }
0495:
0496: /**
0497: * Get the URI part of the name of this node. This is the URI corresponding to the
0498: * prefix, or the URI of the default namespace if appropriate.
0499: * @return The URI of the namespace of this node. For an unnamed node,
0500: * or for a node with an empty prefix, return an empty
0501: * string.
0502: */
0503:
0504: public String getURI() {
0505: NodeInfo element;
0506: if (nodeKind == Type.ELEMENT) {
0507: element = this ;
0508: } else if (nodeKind == Type.ATTRIBUTE) {
0509: element = parent;
0510: } else {
0511: return "";
0512: }
0513:
0514: // The DOM methods getPrefix() and getNamespaceURI() do not always
0515: // return the prefix and the URI; they both return null, unless the
0516: // prefix and URI have been explicitly set in the node by using DOM
0517: // level 2 interfaces. There's no obvious way of deciding whether
0518: // an element whose name has no prefix is in the default namespace,
0519: // other than searching for a default namespace declaration. So we have to
0520: // be prepared to search.
0521:
0522: // If getPrefix() and getNamespaceURI() are non-null, however,
0523: // we can use the values.
0524:
0525: String uri = node.getNamespaceURI();
0526: if (uri != null) {
0527: return uri;
0528: }
0529:
0530: // Otherwise we have to work it out the hard way...
0531:
0532: if (node.getNodeName().startsWith("xml:")) {
0533: return NamespaceConstant.XML;
0534: }
0535:
0536: String[] parts;
0537: try {
0538: parts = Name11Checker.getInstance().getQNameParts(
0539: node.getNodeName());
0540: // use the XML 1.1 rules: these will do because it should already have been checked
0541: } catch (QNameException e) {
0542: throw new IllegalStateException(
0543: "Invalid QName in DOM node. " + e);
0544: }
0545:
0546: if (nodeKind == Type.ATTRIBUTE && parts[0].equals("")) {
0547: // for an attribute, no prefix means no namespace
0548: uri = "";
0549: } else {
0550: AxisIterator nsiter = element.iterateAxis(Axis.NAMESPACE);
0551: while (true) {
0552: NodeInfo ns = (NodeInfo) nsiter.next();
0553: if (ns == null)
0554: break;
0555: if (ns.getLocalPart().equals(parts[0])) {
0556: uri = ns.getStringValue();
0557: break;
0558: }
0559: }
0560: if (uri == null) {
0561: if (parts[0].equals("")) {
0562: uri = "";
0563: } else {
0564: throw new IllegalStateException(
0565: "Undeclared namespace prefix in DOM input: "
0566: + parts[0]);
0567: }
0568: }
0569: }
0570: return uri;
0571: }
0572:
0573: /**
0574: * Get the prefix of the name of the node. This is defined only for elements and attributes.
0575: * If the node has no prefix, or for other kinds of node, return a zero-length string.
0576: * This implementation simply returns the prefix defined in the DOM model; this is nto strictly
0577: * accurate in all cases, but is good enough for the purpose.
0578: * @return The prefix of the name of the node.
0579: */
0580:
0581: public String getPrefix() {
0582: return node.getPrefix();
0583: }
0584:
0585: /**
0586: * Get the display name of this node. For elements and attributes this is [prefix:]localname.
0587: * For unnamed nodes, it is an empty string.
0588: * @return The display name of this node.
0589: * For a node with no name, return an empty string.
0590: */
0591:
0592: public String getDisplayName() {
0593: switch (nodeKind) {
0594: case Type.ELEMENT:
0595: case Type.ATTRIBUTE:
0596: case Type.PROCESSING_INSTRUCTION:
0597: return node.getNodeName();
0598: default:
0599: return "";
0600:
0601: }
0602: }
0603:
0604: /**
0605: * Get the NodeInfo object representing the parent of this node
0606: */
0607:
0608: public NodeInfo getParent() {
0609: if (parent == null) {
0610: switch (getNodeKind()) {
0611: case Type.ATTRIBUTE:
0612: parent = makeWrapper(((Attr) node).getOwnerElement(),
0613: docWrapper);
0614: break;
0615: default:
0616: Node p = node.getParentNode();
0617: if (p == null) {
0618: return null;
0619: } else {
0620: parent = makeWrapper(p, docWrapper);
0621: }
0622: }
0623: }
0624: return parent;
0625: }
0626:
0627: /**
0628: * Get the index position of this node among its siblings (starting from 0).
0629: * In the case of a text node that maps to several adjacent siblings in the DOM,
0630: * the numbering actually refers to the position of the underlying DOM nodes;
0631: * thus the sibling position for the text node is that of the first DOM node
0632: * to which it relates, and the numbering of subsequent XPath nodes is not necessarily
0633: * consecutive.
0634: */
0635:
0636: public int getSiblingPosition() {
0637: if (index == -1) {
0638: switch (nodeKind) {
0639: case Type.ELEMENT:
0640: case Type.TEXT:
0641: case Type.COMMENT:
0642: case Type.PROCESSING_INSTRUCTION:
0643: int ix = 0;
0644: Node start = node;
0645: while (true) {
0646: start = start.getPreviousSibling();
0647: if (start == null) {
0648: index = ix;
0649: return ix;
0650: }
0651: ix++;
0652: }
0653: case Type.ATTRIBUTE:
0654: ix = 0;
0655: int fp = getFingerprint();
0656: AxisIterator iter = parent.iterateAxis(Axis.ATTRIBUTE);
0657: while (true) {
0658: NodeInfo n = (NodeInfo) iter.next();
0659: if (n == null || n.getFingerprint() == fp) {
0660: index = ix;
0661: return ix;
0662: }
0663: ix++;
0664: }
0665:
0666: case Type.NAMESPACE:
0667: ix = 0;
0668: fp = getFingerprint();
0669: iter = parent.iterateAxis(Axis.NAMESPACE);
0670: while (true) {
0671: NodeInfo n = (NodeInfo) iter.next();
0672: if (n == null || n.getFingerprint() == fp) {
0673: index = ix;
0674: return ix;
0675: }
0676: ix++;
0677: }
0678: default:
0679: index = 0;
0680: return index;
0681: }
0682: }
0683: return index;
0684: }
0685:
0686: /**
0687: * Return an iteration over the nodes reached by the given axis from this node
0688: * @param axisNumber the axis to be used
0689: * @return a SequenceIterator that scans the nodes reached by the axis in turn.
0690: */
0691:
0692: public AxisIterator iterateAxis(byte axisNumber) {
0693: switch (axisNumber) {
0694: case Axis.ANCESTOR:
0695: if (nodeKind == Type.DOCUMENT)
0696: return EmptyIterator.getInstance();
0697: return new Navigator.AncestorEnumeration(this , false);
0698:
0699: case Axis.ANCESTOR_OR_SELF:
0700: if (nodeKind == Type.DOCUMENT)
0701: return EmptyIterator.getInstance();
0702: return new Navigator.AncestorEnumeration(this , true);
0703:
0704: case Axis.ATTRIBUTE:
0705: if (nodeKind != Type.ELEMENT)
0706: return EmptyIterator.getInstance();
0707: return new AttributeEnumeration(this );
0708:
0709: case Axis.CHILD:
0710: if (hasChildNodes()) {
0711: return new ChildEnumeration(this , true, true);
0712: } else {
0713: return EmptyIterator.getInstance();
0714: }
0715:
0716: case Axis.DESCENDANT:
0717: if (hasChildNodes()) {
0718: return new Navigator.DescendantEnumeration(this , false,
0719: true);
0720: } else {
0721: return EmptyIterator.getInstance();
0722: }
0723:
0724: case Axis.DESCENDANT_OR_SELF:
0725: return new Navigator.DescendantEnumeration(this , true, true);
0726:
0727: case Axis.FOLLOWING:
0728: return new Navigator.FollowingEnumeration(this );
0729:
0730: case Axis.FOLLOWING_SIBLING:
0731: switch (nodeKind) {
0732: case Type.DOCUMENT:
0733: case Type.ATTRIBUTE:
0734: case Type.NAMESPACE:
0735: return EmptyIterator.getInstance();
0736: default:
0737: return new ChildEnumeration(this , false, true);
0738: }
0739:
0740: case Axis.NAMESPACE:
0741: if (nodeKind != Type.ELEMENT) {
0742: return EmptyIterator.getInstance();
0743: }
0744: return new NamespaceIterator(this , null);
0745: //return new NamespaceEnumeration(this);
0746:
0747: case Axis.PARENT:
0748: getParent();
0749: return SingletonIterator.makeIterator(parent);
0750:
0751: case Axis.PRECEDING:
0752: return new Navigator.PrecedingEnumeration(this , false);
0753:
0754: case Axis.PRECEDING_SIBLING:
0755: switch (nodeKind) {
0756: case Type.DOCUMENT:
0757: case Type.ATTRIBUTE:
0758: case Type.NAMESPACE:
0759: return EmptyIterator.getInstance();
0760: default:
0761: return new ChildEnumeration(this , false, false);
0762: }
0763:
0764: case Axis.SELF:
0765: return SingletonIterator.makeIterator(this );
0766:
0767: case Axis.PRECEDING_OR_ANCESTOR:
0768: return new Navigator.PrecedingEnumeration(this , true);
0769:
0770: default:
0771: throw new IllegalArgumentException("Unknown axis number "
0772: + axisNumber);
0773: }
0774: }
0775:
0776: /**
0777: * Return an iteration over the nodes reached by the given axis from this node
0778: * @param axisNumber the axis to be used
0779: * @param nodeTest A pattern to be matched by the returned nodes
0780: * @return a SequenceIterator that scans the nodes reached by the axis in turn.
0781: */
0782:
0783: public AxisIterator iterateAxis(byte axisNumber, NodeTest nodeTest) {
0784: return new Navigator.AxisFilter(iterateAxis(axisNumber),
0785: nodeTest);
0786: }
0787:
0788: /**
0789: * Get the value of a given attribute of this node
0790: * @param fingerprint The fingerprint of the attribute name
0791: * @return the attribute value if it exists or null if not
0792: */
0793:
0794: public String getAttributeValue(int fingerprint) {
0795: NameTest test = new NameTest(Type.ATTRIBUTE, fingerprint,
0796: getNamePool());
0797: AxisIterator iterator = iterateAxis(Axis.ATTRIBUTE, test);
0798: NodeInfo attribute = (NodeInfo) iterator.next();
0799: if (attribute == null) {
0800: return null;
0801: } else {
0802: return attribute.getStringValue();
0803: }
0804: }
0805:
0806: /**
0807: * Get the root node - always a document node with this tree implementation
0808: * @return the NodeInfo representing the containing document
0809: */
0810:
0811: public NodeInfo getRoot() {
0812: return docWrapper;
0813: }
0814:
0815: /**
0816: * Get the root (document) node
0817: * @return the DocumentInfo representing the containing document
0818: */
0819:
0820: public DocumentInfo getDocumentRoot() {
0821: return docWrapper;
0822: }
0823:
0824: /**
0825: * Determine whether the node has any children. <br />
0826: * Note: the result is equivalent to <br />
0827: * getEnumeration(Axis.CHILD, AnyNodeTest.getInstance()).hasNext()
0828: */
0829:
0830: public boolean hasChildNodes() {
0831: // In Xerces, an attribute node has child text nodes
0832: if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
0833: return false;
0834: }
0835: return node.hasChildNodes();
0836: }
0837:
0838: /**
0839: * Get a character string that uniquely identifies this node.
0840: * Note: a.isSameNode(b) if and only if generateId(a)==generateId(b)
0841: * @return a string that uniquely identifies this node, across all
0842: * documents
0843: */
0844:
0845: public String generateId() {
0846: return Navigator.getSequentialKey(this );
0847: }
0848:
0849: /**
0850: * Get the document number of the document containing this node. For a free-standing
0851: * orphan node, just return the hashcode.
0852: */
0853:
0854: public int getDocumentNumber() {
0855: return getDocumentRoot().getDocumentNumber();
0856: }
0857:
0858: /**
0859: * Copy this node to a given outputter (deep copy)
0860: */
0861:
0862: public void copy(Receiver out, int whichNamespaces,
0863: boolean copyAnnotations, int locationId)
0864: throws XPathException {
0865: Navigator.copy(this , out, docWrapper.getNamePool(),
0866: whichNamespaces, copyAnnotations, locationId);
0867: }
0868:
0869: /**
0870: * Output all namespace nodes associated with this element. Does nothing if
0871: * the node is not an element.
0872: * @param out The relevant outputter
0873: * @param includeAncestors True if namespaces declared on ancestor elements must
0874: */
0875:
0876: public void sendNamespaceDeclarations(Receiver out,
0877: boolean includeAncestors) throws XPathException {
0878: Navigator
0879: .sendNamespaceDeclarations(this , out, includeAncestors);
0880: }
0881:
0882: /**
0883: * Get all namespace undeclarations and undeclarations defined on this element.
0884: *
0885: * @param buffer If this is non-null, and the result array fits in this buffer, then the result
0886: * may overwrite the contents of this array, to avoid the cost of allocating a new array on the heap.
0887: * @return An array of integers representing the namespace declarations and undeclarations present on
0888: * this element. For a node other than an element, return null. Otherwise, the returned array is a
0889: * sequence of namespace codes, whose meaning may be interpreted by reference to the name pool. The
0890: * top half word of each namespace code represents the prefix, the bottom half represents the URI.
0891: * If the bottom half is zero, then this is a namespace undeclaration rather than a declaration.
0892: * The XML namespace is never included in the list. If the supplied array is larger than required,
0893: * then the first unused entry will be set to -1.
0894: * <p/>
0895: * <p>For a node other than an element, the method returns null.</p>
0896: */
0897:
0898: public int[] getDeclaredNamespaces(int[] buffer) {
0899: if (node.getNodeType() == Node.ELEMENT_NODE) {
0900: Element elem = (Element) node;
0901: NamedNodeMap atts = elem.getAttributes();
0902:
0903: if (atts == null) {
0904: return EMPTY_NAMESPACE_LIST;
0905: }
0906: int count = 0;
0907: for (int i = 0; i < atts.getLength(); i++) {
0908: Attr att = (Attr) atts.item(i);
0909: String attName = att.getName();
0910: if (attName.equals("xmlns")) {
0911: count++;
0912: } else if (attName.startsWith("xmlns:")) {
0913: count++;
0914: }
0915: }
0916: if (count == 0) {
0917: return EMPTY_NAMESPACE_LIST;
0918: } else {
0919: int[] result = (count > buffer.length ? new int[count]
0920: : buffer);
0921: NamePool pool = getNamePool();
0922: int n = 0;
0923: for (int i = 0; i < atts.getLength(); i++) {
0924: Attr att = (Attr) atts.item(i);
0925: String attName = att.getName();
0926: if (attName.equals("xmlns")) {
0927: String prefix = "";
0928: String uri = att.getValue();
0929: result[n++] = pool.allocateNamespaceCode(
0930: prefix, uri);
0931: } else if (attName.startsWith("xmlns:")) {
0932: String prefix = attName.substring(6);
0933: String uri = att.getValue();
0934: result[n++] = pool.allocateNamespaceCode(
0935: prefix, uri);
0936: }
0937: }
0938: if (count < result.length) {
0939: result[count] = -1;
0940: }
0941: return result;
0942: }
0943: } else {
0944: return null;
0945: }
0946: }
0947:
0948: private final class AttributeEnumeration implements AxisIterator,
0949: LookaheadIterator {
0950:
0951: private ArrayList attList = new ArrayList(10);
0952: private int ix = 0;
0953: private NodeWrapper start;
0954: private NodeWrapper current;
0955:
0956: public AttributeEnumeration(NodeWrapper start) {
0957: this .start = start;
0958: NamedNodeMap atts = start.node.getAttributes();
0959: if (atts != null) {
0960: for (int i = 0; i < atts.getLength(); i++) {
0961: String name = atts.item(i).getNodeName();
0962: if (!(name.startsWith("xmlns") && (name.length() == 5 || name
0963: .charAt(5) == ':'))) {
0964: attList.add(atts.item(i));
0965: }
0966: }
0967: }
0968: ix = 0;
0969: }
0970:
0971: public boolean hasNext() {
0972: return ix < attList.size();
0973: }
0974:
0975: public Item next() {
0976: if (ix >= attList.size()) {
0977: return null;
0978: }
0979: current = start.makeWrapper((Attr) attList.get(ix),
0980: docWrapper, start, ix);
0981: ix++;
0982: return current;
0983: }
0984:
0985: public Item current() {
0986: return current;
0987: }
0988:
0989: public int position() {
0990: return ix + 1;
0991: }
0992:
0993: public SequenceIterator getAnother() {
0994: return new AttributeEnumeration(start);
0995: }
0996:
0997: /**
0998: * Get properties of this iterator, as a bit-significant integer.
0999: *
1000: * @return the properties of this iterator. This will be some combination of
1001: * properties such as {@link GROUNDED}, {@link LAST_POSITION_FINDER},
1002: * and {@link LOOKAHEAD}. It is always
1003: * acceptable to return the value zero, indicating that there are no known special properties.
1004: * It is acceptable for the properties of the iterator to change depending on its state.
1005: */
1006:
1007: public int getProperties() {
1008: return LOOKAHEAD;
1009: }
1010: }
1011:
1012: /**
1013: * The class ChildEnumeration handles not only the child axis, but also the
1014: * following-sibling and preceding-sibling axes. It can also iterate the children
1015: * of the start node in reverse order, something that is needed to support the
1016: * preceding and preceding-or-ancestor axes (the latter being used by xsl:number)
1017: */
1018:
1019: private final class ChildEnumeration extends AxisIteratorImpl
1020: implements LookaheadIterator {
1021:
1022: private NodeWrapper start;
1023: private NodeWrapper commonParent;
1024: private ArrayList items = new ArrayList(20);
1025: private int ix = 0;
1026: private boolean downwards; // iterate children of start node (not siblings)
1027: private boolean forwards; // iterate in document order (not reverse order)
1028:
1029: public ChildEnumeration(NodeWrapper start, boolean downwards,
1030: boolean forwards) {
1031: this .start = start;
1032: this .downwards = downwards;
1033: this .forwards = forwards;
1034: position = 0;
1035:
1036: if (downwards) {
1037: commonParent = start;
1038: } else {
1039: commonParent = (NodeWrapper) start.getParent();
1040: }
1041:
1042: NodeList childNodes = commonParent.node.getChildNodes();
1043: if (downwards) {
1044: if (!forwards) {
1045: // backwards enumeration: go to the end
1046: ix = childNodes.getLength() - 1;
1047: }
1048: } else {
1049: ix = start.getSiblingPosition()
1050: + (forwards ? span : -1);
1051: }
1052:
1053: if (forwards) {
1054: boolean previousText = false;
1055: for (int i = ix; i < childNodes.getLength(); i++) {
1056: boolean this Text = false;
1057: Node node = childNodes.item(i);
1058: switch (node.getNodeType()) {
1059: case Node.DOCUMENT_TYPE_NODE:
1060: break;
1061: case Node.TEXT_NODE:
1062: case Node.CDATA_SECTION_NODE:
1063: this Text = true;
1064: if (previousText) {
1065: if (isAtomizing()) {
1066: UntypedAtomicValue old = (UntypedAtomicValue) (items
1067: .get(items.size() - 1));
1068: String newval = old.getStringValue()
1069: + getStringValue(node, node
1070: .getNodeType());
1071: items.set(items.size() - 1,
1072: new UntypedAtomicValue(newval));
1073: } else {
1074: NodeWrapper old = ((NodeWrapper) items
1075: .get(items.size() - 1));
1076: old.span++;
1077: }
1078: break;
1079: }
1080: // otherwise fall through to default case
1081: default:
1082: previousText = this Text;
1083: if (isAtomizing()) {
1084: items.add(new UntypedAtomicValue(
1085: getStringValue(node, node
1086: .getNodeType())));
1087: } else {
1088: items.add(makeWrapper(node, docWrapper,
1089: commonParent, i));
1090: }
1091: }
1092: }
1093: } else {
1094: boolean previousText = false;
1095: for (int i = ix; i >= 0; i--) {
1096: boolean this Text = false;
1097: Node node = childNodes.item(i);
1098: switch (node.getNodeType()) {
1099: case Node.DOCUMENT_TYPE_NODE:
1100: break;
1101: case Node.TEXT_NODE:
1102: case Node.CDATA_SECTION_NODE:
1103: this Text = true;
1104: if (previousText) {
1105: if (isAtomizing()) {
1106: UntypedAtomicValue old = (UntypedAtomicValue) (items
1107: .get(items.size() - 1));
1108: String newval = old.getStringValue()
1109: + getStringValue(node, node
1110: .getNodeType());
1111: items.set(items.size() - 1,
1112: new UntypedAtomicValue(newval));
1113: } else {
1114: NodeWrapper old = ((NodeWrapper) items
1115: .get(items.size() - 1));
1116: old.node = node;
1117: old.span++;
1118: }
1119: break;
1120: }
1121: // otherwise fall through to default case
1122: default:
1123: previousText = this Text;
1124: if (isAtomizing()) {
1125: items.add(new UntypedAtomicValue(
1126: getStringValue(node, node
1127: .getNodeType())));
1128: } else {
1129: items.add(makeWrapper(node, docWrapper,
1130: commonParent, i));
1131: }
1132: }
1133: }
1134: }
1135: }
1136:
1137: public boolean hasNext() {
1138: return position < items.size();
1139: }
1140:
1141: public Item next() {
1142: if (position < items.size()) {
1143: current = (Item) items.get(position++);
1144: return current;
1145: } else {
1146: return null;
1147: }
1148: }
1149:
1150: public SequenceIterator getAnother() {
1151: return new ChildEnumeration(start, downwards, forwards);
1152: }
1153:
1154: /**
1155: * Get properties of this iterator, as a bit-significant integer.
1156: *
1157: * @return the properties of this iterator. This will be some combination of
1158: * properties such as {@link GROUNDED}, {@link LAST_POSITION_FINDER},
1159: * and {@link LOOKAHEAD}. It is always
1160: * acceptable to return the value zero, indicating that there are no known special properties.
1161: * It is acceptable for the properties of the iterator to change depending on its state.
1162: */
1163:
1164: public int getProperties() {
1165: return LOOKAHEAD;
1166: }
1167:
1168: } // end of class ChildEnumeration
1169:
1170: }
1171:
1172: //
1173: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
1174: // you may not use this file except in compliance with the License. You may obtain a copy of the
1175: // License at http://www.mozilla.org/MPL/
1176: //
1177: // Software distributed under the License is distributed on an "AS IS" basis,
1178: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
1179: // See the License for the specific language governing rights and limitations under the License.
1180: //
1181: // The Original Code is: all this file.
1182: //
1183: // The Initial Developer of the Original Code is Michael Kay
1184: //
1185: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
1186: //
1187: // Contributor(s): none.
1188: //
|