1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * @LastModified: Oct 2017 4 */ 5 /* 6 * Licensed to the Apache Software Foundation (ASF) under one or more 7 * contributor license agreements. See the NOTICE file distributed with 8 * this work for additional information regarding copyright ownership. 9 * The ASF licenses this file to You under the Apache License, Version 2.0 10 * (the "License"); you may not use this file except in compliance with 11 * the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an "AS IS" BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 */ 21 package com.sun.org.apache.xerces.internal.dom; 22 23 import java.util.ArrayList; 24 import java.util.List; 25 import org.w3c.dom.Node; 26 import org.w3c.dom.NodeList; 27 28 /** 29 * This class implements the DOM's NodeList behavior for 30 * Element.getElementsByTagName() 31 * <P> 32 * The DOM describes NodeList as follows: 33 * <P> 34 * 1) It may represent EITHER nodes scattered through a subtree (when 35 * returned by Element.getElementsByTagName), or just the immediate 36 * children (when returned by Node.getChildNodes). The latter is easy, 37 * but the former (which this class addresses) is more challenging. 38 * <P> 39 * 2) Its behavior is "live" -- that is, it always reflects the 40 * current state of the document tree. To put it another way, the 41 * NodeLists obtained before and after a series of insertions and 42 * deletions are effectively identical (as far as the user is 43 * concerned, the former has been dynamically updated as the changes 44 * have been made). 45 * <P> 46 * 3) Its API accesses individual nodes via an integer index, with the 47 * listed nodes numbered sequentially in the order that they were 48 * found during a preorder depth-first left-to-right search of the tree. 49 * (Of course in the case of getChildNodes, depth is not involved.) As 50 * nodes are inserted or deleted in the tree, and hence the NodeList, 51 * the numbering of nodes that follow them in the NodeList will 52 * change. 53 * <P> 54 * It is rather painful to support the latter two in the 55 * getElementsByTagName case. The current solution is for Nodes to 56 * maintain a change count (eventually that may be a Digest instead), 57 * which the NodeList tracks and uses to invalidate itself. 58 * <P> 59 * Unfortunately, this does _not_ respond efficiently in the case that 60 * the dynamic behavior was supposed to address: scanning a tree while 61 * it is being extended. That requires knowing which subtrees have 62 * changed, which can become an arbitrarily complex problem. 63 * <P> 64 * We save some work by filling the vector only as we access the 65 * item()s... but I suspect the same users who demanded index-based 66 * access will also start by doing a getLength() to control their loop, 67 * blowing this optimization out of the water. 68 * <P> 69 * NOTE: Level 2 of the DOM will probably _not_ use NodeList for its 70 * extended search mechanisms, partly for the reasons just discussed. 71 * 72 * @xerces.internal 73 * 74 * @since PR-DOM-Level-1-19980818. 75 */ 76 public class DeepNodeListImpl 77 implements NodeList { 78 79 // 80 // Data 81 // 82 protected NodeImpl rootNode; // Where the search started 83 protected String tagName; // Or "*" to mean all-tags-acceptable 84 protected int changes = 0; 85 protected List<Node> nodes; 86 87 protected String nsName; 88 protected boolean enableNS = false; 89 90 // 91 // Constructors 92 // 93 94 /** Constructor. */ 95 public DeepNodeListImpl(NodeImpl rootNode, String tagName) { 96 this.rootNode = rootNode; 97 this.tagName = tagName; 98 nodes = new ArrayList<>(); 99 } 100 101 /** Constructor for Namespace support. */ 102 public DeepNodeListImpl(NodeImpl rootNode, 103 String nsName, String tagName) { 104 this(rootNode, tagName); 105 this.nsName = (nsName != null && nsName.length() != 0) ? nsName : null; 106 enableNS = true; 107 } 108 109 // 110 // NodeList methods 111 // 112 113 /** Returns the length of the node list. */ 114 public int getLength() { 115 // Preload all matching elements. (Stops when we run out of subtree!) 116 item(java.lang.Integer.MAX_VALUE); 117 return nodes.size(); 118 } 119 120 /** Returns the node at the specified index. */ 121 public Node item(int index) { 122 Node thisNode; 123 124 // Tree changed. Do it all from scratch! 125 if (rootNode.changes() != changes) { 126 nodes = new ArrayList<>(); 127 changes = rootNode.changes(); 128 } 129 130 // In the cache 131 final int currentSize = nodes.size(); 132 if (index < currentSize) { 133 return nodes.get(index); 134 } // Not yet seen 135 else { 136 137 // Pick up where we left off (Which may be the beginning) 138 if (currentSize == 0) { 139 thisNode = rootNode; 140 } else { 141 thisNode = (NodeImpl) (nodes.get(currentSize - 1)); 142 } 143 144 // Add nodes up to the one we're looking for 145 while (thisNode != null && index >= nodes.size()) { 146 thisNode = nextMatchingElementAfter(thisNode); 147 if (thisNode != null) { 148 nodes.add(thisNode); 149 } 150 } 151 152 // Either what we want, or null (not avail.) 153 return thisNode; 154 } 155 156 } // item(int):Node 157 158 // 159 // Protected methods (might be overridden by an extending DOM) 160 // 161 162 /** 163 * Iterative tree-walker. When you have a Parent link, there's often no 164 * need to resort to recursion. NOTE THAT only Element nodes are matched 165 * since we're specifically supporting getElementsByTagName(). 166 */ 167 protected Node nextMatchingElementAfter(Node current) { 168 169 Node next; 170 while (current != null) { 171 // Look down to first child. 172 if (current.hasChildNodes()) { 173 current = (current.getFirstChild()); 174 } // Look right to sibling (but not from root!) 175 else if (current != rootNode && null != (next = current.getNextSibling())) { 176 current = next; 177 } // Look up and right (but not past root!) 178 else { 179 next = null; 180 for (; current != rootNode; // Stop when we return to starting point 181 current = current.getParentNode()) { 182 183 next = current.getNextSibling(); 184 if (next != null) { 185 break; 186 } 187 } 188 current = next; 189 } 190 191 // Have we found an Element with the right tagName? 192 // ("*" matches anything.) 193 if (current != rootNode 194 && current != null 195 && current.getNodeType() == Node.ELEMENT_NODE) { 196 if (!enableNS) { 197 if (tagName.equals("*") 198 || ((ElementImpl) current).getTagName().equals(tagName)) { 199 return current; 200 } 201 } else { 202 // DOM2: Namespace logic. 203 if (tagName.equals("*")) { 204 if (nsName != null && nsName.equals("*")) { 205 return current; 206 } else { 207 ElementImpl el = (ElementImpl) current; 208 if ((nsName == null 209 && el.getNamespaceURI() == null) 210 || (nsName != null 211 && nsName.equals(el.getNamespaceURI()))) { 212 return current; 213 } 214 } 215 } else { 216 ElementImpl el = (ElementImpl) current; 217 if (el.getLocalName() != null 218 && el.getLocalName().equals(tagName)) { 219 if (nsName != null && nsName.equals("*")) { 220 return current; 221 } else { 222 if ((nsName == null 223 && el.getNamespaceURI() == null) 224 || (nsName != null 225 && nsName.equals(el.getNamespaceURI()))) { 226 return current; 227 } 228 } 229 } 230 } 231 } 232 } 233 234 // Otherwise continue walking the tree 235 } 236 237 // Fell out of tree-walk; no more instances found 238 return null; 239 240 } // nextMatchingElementAfter(int):Node 241 242 } // class DeepNodeListImpl