View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2   // Copyright (c) 2018-2020 Saxonica Limited
3   // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4   // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
5   // This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
6   ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
7   
8   package net.sf.saxon.option.jdom2;
9   
10  import net.sf.saxon.lib.NamespaceConstant;
11  import net.sf.saxon.om.AxisInfo;
12  import net.sf.saxon.om.NamespaceBinding;
13  import net.sf.saxon.om.NamespaceMap;
14  import net.sf.saxon.om.NodeInfo;
15  import net.sf.saxon.pattern.AnyNodeTest;
16  import net.sf.saxon.pattern.NodeKindTest;
17  import net.sf.saxon.pattern.NodeTest;
18  import net.sf.saxon.tree.iter.*;
19  import net.sf.saxon.tree.util.FastStringBuffer;
20  import net.sf.saxon.tree.util.Navigator;
21  import net.sf.saxon.tree.wrapper.AbstractNodeWrapper;
22  import net.sf.saxon.tree.wrapper.SiblingCountingNode;
23  import net.sf.saxon.type.Type;
24  import net.sf.saxon.type.UType;
25  import org.jdom2.*;
26  import org.jdom2.filter.ElementFilter;
27  import org.jdom2.located.Located;
28  
29  import java.util.ArrayList;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.ListIterator;
33  import java.util.function.Predicate;
34  
35  /**
36   * A node in the XML parse tree representing an XML element, character content, or attribute.
37   * <p>This is the implementation of the NodeInfo interface used as a wrapper for JDOM2 nodes.</p>
38   *
39   * @author Michael H. Kay
40   */
41  
42  public class JDOM2NodeWrapper extends AbstractNodeWrapper implements SiblingCountingNode {
43  
44      protected Object node;          // the JDOM2 node to which this XPath node is mapped; or a List of
45      // adjacent text nodes
46      protected short nodeKind;
47      private JDOM2NodeWrapper parent;     // null means unknown
48      protected int index;            // -1 means unknown
49      protected NamespaceMap inScopeNamespaces; // computed lazily
50  
51      /**
52       * This constructor is protected: nodes should be created using the wrap
53       * factory method on the DocumentWrapper class
54       *
55       * @param node   The JDOM node to be wrapped
56       * @param parent The NodeWrapper that wraps the parent of this node
57       * @param index  Position of this node among its siblings
58       */
59      private JDOM2NodeWrapper./net/sf/saxon/option/jdom2/JDOM2NodeWrapper.html#JDOM2NodeWrapper">JDOM2NodeWrapper(Object node, JDOM2NodeWrapper parent, int index) {
60          this.node = node;
61          this.parent = parent;
62          this.index = index;
63      }
64  
65      /**
66       * Factory method to wrap a JDOM node with a wrapper that implements the Saxon
67       * NodeInfo interface.
68       *
69       * @param node       The JDOM node
70       * @param docWrapper The wrapper for the Document containing this node
71       * @return The new wrapper for the supplied node
72       */
73      protected static JDOM2NodeWrapper makeWrapper(Object node, JDOM2DocumentWrapper docWrapper) {
74          return makeWrapper(node, docWrapper, null, -1);
75      }
76  
77      /**
78       * Factory method to wrap a JDOM node with a wrapper that implements the Saxon
79       * NodeInfo interface.
80       *
81       * @param node       The JDOM node
82       * @param docWrapper The wrapper for the Document containing this node
83       * @param parent     The wrapper for the parent of the JDOM node
84       * @param index      The position of this node relative to its siblings
85       * @return The new wrapper for the supplied node
86       */
87  
88      protected static JDOM2NodeWrapper makeWrapper(Object node, JDOM2DocumentWrapper docWrapper,
89                                             JDOM2NodeWrapper parent, int index) {
90          JDOM2NodeWrapper wrapper;
91          if (node instanceof Document) {
92              wrapper = (JDOM2NodeWrapper)docWrapper.getRootNode();
93              if (wrapper == null) {
94                  wrapper = new JDOM2NodeWrapper(node, parent, index);
95                  wrapper.nodeKind = Type.DOCUMENT;
96              }
97          } else if (node instanceof Element) {
98              wrapper = new JDOM2NodeWrapper(node, parent, index);
99              wrapper.nodeKind = Type.ELEMENT;
100         } else if (node instanceof Attribute) {
101             wrapper = new JDOM2NodeWrapper(node, parent, index);
102             wrapper.nodeKind = Type.ATTRIBUTE;
103         } else if (node instanceof String || node instanceof Text) {
104             wrapper = new JDOM2NodeWrapper(node, parent, index);
105             wrapper.nodeKind = Type.TEXT;
106         } else if (node instanceof Comment) {
107             wrapper = new JDOM2NodeWrapper(node, parent, index);
108             wrapper.nodeKind = Type.COMMENT;
109         } else if (node instanceof ProcessingInstruction) {
110             wrapper = new JDOM2NodeWrapper(node, parent, index);
111             wrapper.nodeKind = Type.PROCESSING_INSTRUCTION;
112         } else if (node instanceof Namespace) {
113             throw new IllegalArgumentException("Cannot wrap JDOM namespace objects");
114         } else {
115             throw new IllegalArgumentException("Bad node type in JDOM! " + node.getClass() + " instance " + node.toString());
116         }
117         wrapper.treeInfo = docWrapper;
118         return wrapper;
119     }
120 
121     /**
122      * Get the underlying JDOM2 node, to implement the VirtualNode interface. If this wrapper
123      * node represents a list of adjacent text nodes, then the first of the text nodes is
124      * returned.
125      */
126 
127     @Override
128     public Object getUnderlyingNode() {
129         if (node instanceof List) {
130             return ((List) node).get(0);
131         } else {
132             return node;
133         }
134     }
135 
136     @Override
137     public JDOM2DocumentWrapper getTreeInfo() {
138         return (JDOM2DocumentWrapper)treeInfo;
139     }
140 
141     /**
142      * Return the type of node.
143      *
144      * @return one of the values Node.ELEMENT, Node.TEXT, Node.ATTRIBUTE, etc.
145      */
146 
147     @Override
148     public int getNodeKind() {
149         return nodeKind;
150     }
151 
152     /**
153      * Determine the relative position of this node and another node, in document order.
154      * The other node will always be in the same document.
155      *
156      * @param other The other node, whose position is to be compared with this node
157      * @return -1 if this node precedes the other node, +1 if it follows the other
158      *         node, or 0 if they are the same node. (In this case, isSameNode() will always
159      *         return true, and the two nodes will produce the same result for generateId())
160      */
161 
162     @Override
163     public int compareOrder(NodeInfo other) {
164         if (other instanceof SiblingCountingNode) {
165             return Navigator.compareOrder(this, (SiblingCountingNode) other);
166         } else {
167             // it must be a namespace node
168             return -other.compareOrder(this);
169         }
170     }
171 
172     /**
173      * Get the value of the item as a CharSequence. This is in some cases more efficient than
174      * the version of the method that returns a String.
175      */
176 
177     @Override
178     public CharSequence getStringValueCS() {
179         if (node instanceof List) {
180             // This wrapper is mapped to a list of adjacent text nodes
181             List nodes = (List) node;
182             FastStringBuffer fsb = new FastStringBuffer(FastStringBuffer.C64);
183             for (Object node1 : nodes) {
184                 Text o = (Text) node1;
185                 fsb.append(getStringValue(o));
186             }
187             return fsb;
188         } else {
189             return getStringValue(node);
190         }
191     }
192 
193     @Override
194     public int getLineNumber() {
195         if (node instanceof Located) {
196             return ((Located) node).getLine();
197         } else {
198             return -1;
199         }
200     }
201 
202     @Override
203     public int getColumnNumber() {
204         if (node instanceof Located) {
205             return ((Located) node).getColumn();
206         } else {
207             return -1;
208         }
209     }
210 
211 
212     /**
213      * Supporting method to get the string value of a node
214      *
215      * @param node the JDOM node
216      * @return the XPath string value of the node
217      */
218 
219     private static String getStringValue(Object node) {
220         if (node instanceof Document) {
221             List<Content> children1 = ((Document) node).getContent();
222             FastStringBuffer sb1 = new FastStringBuffer(FastStringBuffer.C256);
223             expandStringValue(children1, sb1);
224             return sb1.toString();
225         } else if (node instanceof Element) {
226             return ((Element) node).getValue();
227         } else if (node instanceof Attribute) {
228             return ((Attribute) node).getValue();
229         } else if (node instanceof Text) {
230             return ((Text) node).getText();
231         } else if (node instanceof String) {
232             return (String) node;
233         } else if (node instanceof Comment) {
234             return ((Comment) node).getText();
235         } else if (node instanceof ProcessingInstruction) {
236             return ((ProcessingInstruction) node).getData();
237         } else if (node instanceof Namespace) {
238             return ((Namespace) node).getURI();
239         } else {
240             return "";
241         }
242     }
243 
244     /**
245      * Get the string values of all the nodes in a list, concatenating the values into
246      * a supplied string buffer
247      *
248      * @param list the list containing the nodes
249      * @param sb   the StringBuffer to contain the result
250      */
251     private static void expandStringValue(List<Content> list, FastStringBuffer sb) {
252         for (Content obj : list) {
253             if (obj instanceof Element) {
254                 sb.append(obj.getValue());
255             } else if (obj instanceof Text) {
256                 sb.append(((Text) obj).getText());
257             } else if (obj instanceof EntityRef) {
258                 throw new IllegalStateException("Unexpanded entity in JDOM2 tree");
259             } else if (obj instanceof DocType) {
260                 //noinspection UnnecessaryContinue
261                 continue;
262             } else {
263                 throw new AssertionError("Unknown JDOM2 node type");
264             }
265         }
266     }
267 
268     /**
269      * Get the local part of the name of this node. This is the name after the ":" if any.
270      *
271      * @return the local part of the name. For an unnamed node, returns "".
272      */
273 
274     @Override
275     public String getLocalPart() {
276         switch (nodeKind) {
277             case Type.ELEMENT:
278                 return ((Element) node).getName();
279             case Type.ATTRIBUTE:
280                 return ((Attribute) node).getName();
281             case Type.TEXT:
282             case Type.COMMENT:
283             case Type.DOCUMENT:
284                 return "";
285             case Type.PROCESSING_INSTRUCTION:
286                 return ((ProcessingInstruction) node).getTarget();
287             case Type.NAMESPACE:
288                 return ((Namespace) node).getPrefix();
289             default:
290                 return null;
291         }
292     }
293 
294     /**
295      * Get the prefix part of the name of this node. This is the name before the ":" if any.
296      * (Note, this method isn't required as part of the NodeInfo interface.)
297      *
298      * @return the prefix part of the name. For an unnamed node, return an empty string.
299      */
300 
301     @Override
302     public String getPrefix() {
303         switch (nodeKind) {
304             case Type.ELEMENT:
305                 return ((Element) node).getNamespacePrefix();
306             case Type.ATTRIBUTE:
307                 return ((Attribute) node).getNamespacePrefix();
308             default:
309                 return "";
310         }
311     }
312 
313     /**
314      * Get the URI part of the name of this node. This is the URI corresponding to the
315      * prefix, or the URI of the default namespace if appropriate.
316      *
317      * @return The URI of the namespace of this node. For an unnamed node,
318      *         or for a node with an empty prefix, return an empty
319      *         string.
320      */
321 
322     @Override
323     public String getURI() {
324         switch (nodeKind) {
325             case Type.ELEMENT:
326                 return ((Element) node).getNamespaceURI();
327             case Type.ATTRIBUTE:
328                 return ((Attribute) node).getNamespaceURI();
329             default:
330                 return "";
331         }
332     }
333 
334     /**
335      * Get the display name of this node. For elements and attributes this is [prefix:]localname.
336      * For unnamed nodes, it is an empty string.
337      *
338      * @return The display name of this node.
339      *         For a node with no name, return an empty string.
340      */
341 
342     @Override
343     public String getDisplayName() {
344         switch (nodeKind) {
345             case Type.ELEMENT:
346                 return ((Element) node).getQualifiedName();
347             case Type.ATTRIBUTE:
348                 return ((Attribute) node).getQualifiedName();
349             case Type.PROCESSING_INSTRUCTION:
350             case Type.NAMESPACE:
351                 return getLocalPart();
352             default:
353                 return "";
354 
355         }
356     }
357 
358     /**
359      * Get the NodeInfo object representing the parent of this node
360      */
361 
362     @Override
363     public NodeInfo getParent() {
364         if (parent == null) {
365             if (node instanceof Element) {
366                 if (((Element) node).isRootElement()) {
367                     parent = makeWrapper(((Element) node).getDocument(), getTreeInfo());
368                 } else {
369                     parent = makeWrapper(((Element) node).getParent(), getTreeInfo());
370                 }
371             } else if (node instanceof Text) {
372                 parent = makeWrapper(((Text) node).getParent(), getTreeInfo());
373             } else if (node instanceof Comment) {
374                 parent = makeWrapper(((Comment) node).getParent(), getTreeInfo());
375             } else if (node instanceof ProcessingInstruction) {
376                 parent = makeWrapper(((ProcessingInstruction) node).getParent(), getTreeInfo());
377             } else if (node instanceof Attribute) {
378                 parent = makeWrapper(((Attribute) node).getParent(), getTreeInfo());
379             } else if (node instanceof Document) {
380                 parent = null;
381             } else if (node instanceof Namespace) {
382                 throw new UnsupportedOperationException("Cannot find parent of JDOM namespace node");
383             } else {
384                 throw new IllegalStateException("Unknown JDOM node type " + node.getClass());
385             }
386         }
387         return parent;
388     }
389 
390     /**
391      * Get the index position of this node among its siblings (starting from 0)
392      * In the case of a text node that maps to several adjacent siblings in the JDOM,
393      * the numbering actually refers to the position of the underlying JDOM nodes;
394      * thus the sibling position for the text node is that of the first JDOM node
395      * to which it relates, and the numbering of subsequent XPath nodes is not necessarily
396      * consecutive.
397      */
398 
399     @Override
400     public int getSiblingPosition() {
401         if (index == -1) {
402             int ix = 0;
403             getParent();
404             if (parent == null) {
405                 return 0;
406             }
407             AxisIterator iter;
408             switch (nodeKind) {
409                 case Type.ELEMENT:
410                 case Type.TEXT:
411                 case Type.COMMENT:
412                 case Type.PROCESSING_INSTRUCTION:
413                     iter = parent.iterateAxis(AxisInfo.CHILD);
414                     break;
415                 case Type.ATTRIBUTE:
416                     iter = parent.iterateAxis(AxisInfo.ATTRIBUTE);
417                     break;
418                 case Type.NAMESPACE:
419                     iter = parent.iterateAxis(AxisInfo.NAMESPACE);
420                     break;
421                 default:
422                     index = 0;
423                     return index;
424             }
425             while (true) {
426                 NodeInfo n = iter.next();
427                 if (n == null) {
428                     break;
429                 }
430                 if (n.equals(this)) {
431                     index = ix;
432                     return index;
433                 }
434                 if (((JDOM2NodeWrapper) n).node instanceof List) {
435                     ix += ((List) ((JDOM2NodeWrapper) n).node).size();
436                 } else {
437                     ix++;
438                 }
439             }
440             throw new IllegalStateException("JDOM node not linked to parent node");
441         }
442         return index;
443     }
444 
445     @Override
446     protected AxisIterator iterateAttributes(Predicate<? super NodeInfo> nodeTest) {
447         AxisIterator base = new AttributeEnumeration(this);
448         if (nodeTest == AnyNodeTest.getInstance()) {
449             return base;
450         } else {
451             return new Navigator.AxisFilter(base, nodeTest);
452         }
453     }
454 
455     @Override
456     protected AxisIterator iterateChildren(Predicate<? super NodeInfo> nodeTest) {
457         if (hasChildNodes()) {
458             AxisIterator base = new ChildEnumeration(this, true, true);
459             if (nodeTest == AnyNodeTest.getInstance()) {
460                 return base;
461             } else {
462                 return new Navigator.AxisFilter(base, nodeTest);
463             }
464         } else {
465             return EmptyIterator.ofNodes();
466         }
467     }
468 
469     @Override
470     protected AxisIterator iterateSiblings(Predicate<? super NodeInfo> nodeTest, boolean forwards) {
471         if (nodeTest == AnyNodeTest.getInstance()) {
472             return new ChildEnumeration(this, false, forwards);
473         } else {
474             return new Navigator.AxisFilter(
475                     new ChildEnumeration(this, false, forwards),
476                     nodeTest);
477         }
478     }
479 
480     @Override
481     protected AxisIterator iterateDescendants(Predicate<? super NodeInfo> nodeTest, boolean includeSelf) {
482         Iterator<? extends Content> descendants;
483         if (nodeTest instanceof NodeTest && ((NodeTest)nodeTest).getUType() == UType.ELEMENT) {
484             // only select element nodes
485             descendants = ((Parent) node).getDescendants(new ElementFilter());
486         } else {
487             descendants = ((Parent) node).getDescendants();
488         }
489         NodeWrappingFunction<Content, NodeInfo> wrappingFunct = new NodeWrappingFunction<Content, NodeInfo>() {
490             @Override
491             public NodeInfo wrap(Content node) {
492                 return makeWrapper(node, getTreeInfo());
493             }
494         };
495         AxisIterator wrappedDescendants = new DescendantWrappingIterator<Content>(descendants, wrappingFunct);
496 
497         if (includeSelf && nodeTest.test(this)) {
498             wrappedDescendants = new PrependAxisIterator(this, wrappedDescendants);
499         }
500 
501         if (nodeTest instanceof AnyNodeTest || (nodeTest instanceof NodeKindTest && ((NodeKindTest) nodeTest).getNodeKind() == Type.ELEMENT)) {
502             return wrappedDescendants;
503         } else {
504             return new Navigator.AxisFilter(wrappedDescendants, nodeTest);
505         }
506     }
507 
508     private static class DescendantWrappingIterator<N> extends NodeWrappingAxisIterator<N> {
509 
510         DescendantWrappingIterator(
511                 Iterator<? extends N> descendantIterator,
512                 NodeWrappingFunction<? super N, NodeInfo> wrappingFunction) {
513             super(descendantIterator, wrappingFunction);
514         }
515 
516         @Override
517         public boolean isIgnorable(Object node) {
518             return node instanceof DocType;
519         }
520     }
521 
522     /**
523      * Get the string value of a given attribute of this node
524      *
525      * @param uri   the namespace URI of the attribute name. Supply the empty string for an attribute
526      *              that is in no namespace
527      * @param local the local part of the attribute name.
528      * @return the attribute value if it exists, or null if it does not exist. Always returns null
529      *         if this node is not an element.
530      * @since 8.4
531      */
532 
533 
534     @Override
535     public String getAttributeValue(String uri, String local) {
536         if (nodeKind == Type.ELEMENT) {
537             return ((Element) node).getAttributeValue(local,
538                     (uri.equals(NamespaceConstant.XML) ?
539                             Namespace.XML_NAMESPACE :
540                             Namespace.getNamespace(uri)));
541             // JDOM doesn't allow getNamespace() on the XML namespace URI
542         }
543         return null;
544     }
545 
546     /**
547      * Get the root node - always a document node with this tree implementation
548      *
549      * @return the NodeInfo representing the containing document
550      */
551 
552     @Override
553     public NodeInfo getRoot() {
554         return treeInfo.getRootNode();
555     }
556 
557     /**
558      * Determine whether the node has any children.
559      * <p>Note: the result is equivalent to
560      * <code>getEnumeration(AxisInfo.CHILD, AnyNodeTest.getInstance()).hasNext()</code></p>
561      */
562 
563     @Override
564     public boolean hasChildNodes() {
565         switch (nodeKind) {
566             case Type.DOCUMENT:
567                 return true;
568             case Type.ELEMENT:
569                 return !((Element) node).getContent().isEmpty();
570             default:
571                 return false;
572         }
573     }
574 
575     /**
576      * Get a character string that uniquely identifies this node.
577      * Note: a.isSameNode(b) if and only if generateId(a)==generateId(b)
578      *
579      * @param buffer a Buffer to contain a string that uniquely identifies this node, across all
580      *               documents
581      */
582 
583     @Override
584     public void generateId(FastStringBuffer buffer) {
585         Navigator.appendSequentialKey(this, buffer, true);
586         //buffer.append(Navigator.getSequentialKey(this));
587     }
588 
589     /**
590      * Get all namespace undeclarations and undeclarations defined on this element.
591      *
592      * @param buffer If this is non-null, and the result array fits in this buffer, then the result
593      *               may overwrite the contents of this array, to avoid the cost of allocating a new array on the heap.
594      * @return An array of namespace binding objects representing the namespace declarations and undeclarations present on
595      *         this element. For a node other than an element, return null.
596      *         If the uri part is "", then this is a namespace undeclaration rather than a declaration.
597      *         The XML namespace is never included in the list. If the supplied array is larger than required,
598      *         then the first unused entry will be set to null.
599      *         <p>
600      *         For a node other than an element, the method returns null.</p>
601      */
602 
603     @Override
604     public NamespaceBinding[] getDeclaredNamespaces(NamespaceBinding[] buffer) {
605         if (node instanceof Element) {
606             Element elem = (Element) node;
607             List<Namespace> addl = elem.getAdditionalNamespaces();
608             List<NamespaceBinding> bindings = new ArrayList<>();
609             Namespace ns = elem.getNamespace();
610             String prefix = ns.getPrefix();
611             String uri = ns.getURI();
612             if (!(prefix.isEmpty() && uri.isEmpty())) {
613                 bindings.add(new NamespaceBinding(prefix, uri));
614             }
615             if (!addl.isEmpty()) {
616                 for (Namespace ns2 : addl) {
617                     bindings.add(new NamespaceBinding(ns2.getPrefix(), ns2.getURI()));
618                 }
619             }
620             return bindings.toArray(NamespaceBinding.EMPTY_ARRAY);
621         } else {
622             return null;
623         }
624     }
625 
626     /**
627      * Get all the namespace bindings that are in-scope for this element.
628      * <p>For an element return all the prefix-to-uri bindings that are in scope. This may include
629      * a binding to the default namespace (represented by a prefix of ""). It will never include
630      * "undeclarations" - that is, the namespace URI will never be empty; the effect of an undeclaration
631      * is to remove a binding from the in-scope namespaces, not to add anything.</p>
632      * <p>For a node other than an element, returns null.</p>
633      *
634      * @return the in-scope namespaces for an element, or null for any other kind of node.
635      */
636     @Override
637     public NamespaceMap getAllNamespaces() {
638         if (getNodeKind() == Type.ELEMENT) {
639             if (inScopeNamespaces != null) {
640                 return inScopeNamespaces;
641             } else {
642                 NamespaceMap nsMap = getParent().getNodeKind() == Type.ELEMENT
643                         ? getParent().getAllNamespaces()
644                         : NamespaceMap.emptyMap();
645                 Element elem = (Element) node;
646                 List<Namespace> addl = elem.getAdditionalNamespaces();
647                 Namespace ns = elem.getNamespace();
648                 String prefix = ns.getPrefix();
649                 String uri = ns.getURI();
650                 nsMap = nsMap.bind(prefix, uri);
651                 if (!addl.isEmpty()) {
652                     for (Namespace ns2 : addl) {
653                         nsMap = nsMap.bind(ns2.getPrefix(), ns2.getURI());
654                     }
655                 }
656                 return inScopeNamespaces = nsMap;
657             }
658         } else {
659             return null;
660         }
661     }
662 
663     /**
664      * Determine whether this node has the is-id property
665      *
666      * @return true if the node is an ID
667      */
668 
669     @Override
670     public boolean isId() {
671         return node instanceof Attribute && ((Attribute) node).getAttributeType() == Attribute.ID_TYPE;
672     }
673 
674     /**
675      * Determine whether this node has the is-idref property
676      *
677      * @return true if the node is an IDREF or IDREFS element or attribute
678      */
679 
680     @Override
681     public boolean isIdref() {
682         if (node instanceof Attribute) {
683             AttributeType type = ((Attribute) node).getAttributeType();
684             return type == Attribute.IDREF_TYPE || type == Attribute.IDREFS_TYPE;
685         } else {
686             return false;
687         }
688     }
689 
690 
691     ///////////////////////////////////////////////////////////////////////////////
692     // Axis enumeration classes
693     ///////////////////////////////////////////////////////////////////////////////
694 
695 
696     private final class AttributeEnumeration implements AxisIterator {
697 
698         private Iterator<Attribute> atts;
699         private int ix = 0;
700         private JDOM2NodeWrapper start;
701 
702         AttributeEnumeration(JDOM2NodeWrapper start) {
703             this.start = start;
704             atts = ((Element) start.node).getAttributes().iterator();
705         }
706 
707         @Override
708         public JDOM2NodeWrapper next() {
709             if (atts.hasNext()) {
710                 return makeWrapper(atts.next(), getTreeInfo(), start, ix++);
711             } else {
712                 return null;
713             }
714         }
715 
716     }  // end of class AttributeEnumeration
717 
718 
719     /**
720      * The class ChildEnumeration handles not only the child axis, but also the
721      * following-sibling and preceding-sibling axes. It can also iterate the children
722      * of the start node in reverse order, something that is needed to support the
723      * preceding and preceding-or-ancestor axes (the latter being used by xsl:number)
724      */
725 
726     private final class ChildEnumeration implements AxisIterator {
727 
728         private JDOM2NodeWrapper commonParent;
729         private ListIterator children;
730         private int ix = 0;
731         private boolean forwards;   // iterate in document order (not reverse order)
732 
733         public ChildEnumeration(JDOM2NodeWrapper start,
734                                 boolean downwards, boolean forwards) {
735             this.forwards = forwards;
736 
737             if (downwards) {
738                 commonParent = start;
739             } else {
740                 commonParent = (JDOM2NodeWrapper) start.getParent();
741             }
742 
743             if (commonParent.getNodeKind() == Type.DOCUMENT) {
744                 children = ((Document) commonParent.node).getContent().listIterator();
745             } else {
746                 children = ((Element) commonParent.node).getContent().listIterator();
747             }
748 
749             if (downwards) {
750                 if (!forwards) {
751                     // backwards enumeration: go to the end
752                     while (children.hasNext()) {
753                         children.next();
754                         ix++;
755                     }
756                 }
757             } else {
758                 ix = start.getSiblingPosition();
759                 // find the start node among the list of siblings
760                 Object n = null;
761                 if (forwards) {
762                     for (int i = 0; i <= ix; i++) {
763                         n = children.next();
764                     }
765                     if (n instanceof Text) {
766                         // move to the last of a sequence of adjacent text nodes
767                         boolean atEnd = false;
768                         while (n instanceof Text) {
769                             if (children.hasNext()) {
770                                 n = children.next();
771                                 ix++;
772                             } else {
773                                 atEnd = true;
774                                 break;
775                             }
776                         }
777                         if (!atEnd) {
778                             children.previous();
779                         }
780                     } else {
781                         ix++;
782                     }
783                 } else {
784                     for (int i = 0; i < ix; i++) {
785                         children.next();
786                     }
787                     ix--;
788                 }
789             }
790         }
791 
792         @Override
793         public JDOM2NodeWrapper next() {
794             if (forwards) {
795                 if (children.hasNext()) {
796                     Object nextChild = children.next();
797                     if (nextChild instanceof DocType) {
798                         return next();
799                     }
800                     if (nextChild instanceof EntityRef) {
801                         throw new IllegalStateException("Unexpanded entity in JDOM tree");
802                     } else if (nextChild instanceof Text) {
803                         JDOM2NodeWrapper current = makeWrapper(nextChild, getTreeInfo(), commonParent, ix++);
804                         List<Object> list = null;
805                         while (children.hasNext()) {
806                             Object n = children.next();
807                             if (n instanceof Text) {
808                                 if (list == null) {
809                                     list = new ArrayList<Object>(4);
810                                     list.add(current.node);
811                                 }
812                                 list.add(n);
813                                 ix++;
814                             } else {
815                                 // we've looked ahead too far
816                                 children.previous();
817                                 break;
818                             }
819                         }
820                         if (list != null) {
821                             current.node = list;
822                         }
823                         return current;
824                     } else {
825                         return makeWrapper(nextChild, getTreeInfo(), commonParent, ix++);
826                     }
827                 } else {
828                     return null;
829                 }
830             } else {    // backwards
831                 if (children.hasPrevious()) {
832                     Object nextChild = children.previous();
833                     if (nextChild instanceof DocType) {
834                         return next();
835                     }
836                     if (nextChild instanceof EntityRef) {
837                         throw new IllegalStateException("Unexpanded entity in JDOM tree");
838                     } else if (nextChild instanceof Text) {
839                         JDOM2NodeWrapper current = makeWrapper(nextChild, getTreeInfo(), commonParent, ix--);
840                         List<Object> list = null;
841                         while (children.hasPrevious()) {
842                             Object n = children.previous();
843                             if (n instanceof Text) {
844                                 if (list == null) {
845                                     list = new ArrayList<Object>(4);
846                                     list.add(current.node);
847                                 }
848                                 list.add(0, n);
849                                 ix--;
850                             } else {
851                                 // we've looked ahead too far
852                                 children.next();
853                                 break;
854                             }
855                         }
856                         if (list != null) {
857                             current.node = list;
858                         }
859                         return current;
860                     } else {
861                         return makeWrapper(nextChild, getTreeInfo(), commonParent, ix--);
862                     }
863                 } else {
864                     return null;
865                 }
866             }
867         }
868 
869 
870     } // end of class ChildEnumeration
871 
872 }
873 
874 
875 // Original Code is Copyright (c) 2009-2020 Saxonica Limited