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