1 /* |
|
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
|
3 */ |
|
4 /* |
|
5 * Licensed to the Apache Software Foundation (ASF) under one or more |
|
6 * contributor license agreements. See the NOTICE file distributed with |
|
7 * this work for additional information regarding copyright ownership. |
|
8 * The ASF licenses this file to You under the Apache License, Version 2.0 |
|
9 * (the "License"); you may not use this file except in compliance with |
|
10 * the License. You may obtain a copy of the License at |
|
11 * |
|
12 * http://www.apache.org/licenses/LICENSE-2.0 |
|
13 * |
|
14 * Unless required by applicable law or agreed to in writing, software |
|
15 * distributed under the License is distributed on an "AS IS" BASIS, |
|
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
17 * See the License for the specific language governing permissions and |
|
18 * limitations under the License. |
|
19 */ |
|
20 /* |
|
21 * $Id: DOMHelper.java,v 1.2.4.1 2005/09/15 08:15:40 suresh_emailid Exp $ |
|
22 */ |
|
23 package com.sun.org.apache.xml.internal.utils; |
|
24 |
|
25 import com.sun.org.apache.xml.internal.dtm.ref.DTMNodeProxy; |
|
26 import com.sun.org.apache.xml.internal.res.XMLErrorResources; |
|
27 import com.sun.org.apache.xml.internal.res.XMLMessages; |
|
28 import java.util.HashMap; |
|
29 import java.util.Map; |
|
30 import java.util.Vector; |
|
31 import javax.xml.XMLConstants; |
|
32 import javax.xml.parsers.DocumentBuilder; |
|
33 import javax.xml.parsers.DocumentBuilderFactory; |
|
34 import javax.xml.parsers.ParserConfigurationException; |
|
35 import org.w3c.dom.Attr; |
|
36 import org.w3c.dom.DOMImplementation; |
|
37 import org.w3c.dom.Document; |
|
38 import org.w3c.dom.DocumentType; |
|
39 import org.w3c.dom.Element; |
|
40 import org.w3c.dom.Entity; |
|
41 import org.w3c.dom.NamedNodeMap; |
|
42 import org.w3c.dom.Node; |
|
43 import org.w3c.dom.Text; |
|
44 |
|
45 /** |
|
46 * @deprecated Since the introduction of the DTM, this class will be removed. |
|
47 * This class provides a front-end to DOM implementations, providing |
|
48 * a number of utility functions that either aren't yet standardized |
|
49 * by the DOM spec or that are defined in optional DOM modules and |
|
50 * hence may not be present in all DOMs. |
|
51 */ |
|
52 public class DOMHelper |
|
53 { |
|
54 |
|
55 /** |
|
56 * DOM Level 1 did not have a standard mechanism for creating a new |
|
57 * Document object. This function provides a DOM-implementation-independent |
|
58 * abstraction for that for that concept. It's typically used when |
|
59 * outputting a new DOM as the result of an operation. |
|
60 * <p> |
|
61 * TODO: This isn't directly compatable with DOM Level 2. |
|
62 * The Level 2 createDocument call also creates the root |
|
63 * element, and thus requires that you know what that element will be |
|
64 * before creating the Document. We should think about whether we want |
|
65 * to change this code, and the callers, so we can use the DOM's own |
|
66 * method. (It's also possible that DOM Level 3 may relax this |
|
67 * sequence, but you may give up some intelligence in the DOM by |
|
68 * doing so; the intent was that knowing the document type and root |
|
69 * element might let the DOM automatically switch to a specialized |
|
70 * subclass for particular kinds of documents.) |
|
71 * |
|
72 * @param isSecureProcessing state of the secure processing feature. |
|
73 * @return The newly created DOM Document object, with no children, or |
|
74 * null if we can't find a DOM implementation that permits creating |
|
75 * new empty Documents. |
|
76 */ |
|
77 public static Document createDocument(boolean isSecureProcessing) |
|
78 { |
|
79 |
|
80 try |
|
81 { |
|
82 |
|
83 // Use an implementation of the JAVA API for XML Parsing 1.0 to |
|
84 // create a DOM Document node to contain the result. |
|
85 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); |
|
86 |
|
87 dfactory.setNamespaceAware(true); |
|
88 dfactory.setValidating(true); |
|
89 |
|
90 if (isSecureProcessing) |
|
91 { |
|
92 try |
|
93 { |
|
94 dfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); |
|
95 } |
|
96 catch (ParserConfigurationException pce) {} |
|
97 } |
|
98 |
|
99 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); |
|
100 Document outNode = docBuilder.newDocument(); |
|
101 |
|
102 return outNode; |
|
103 } |
|
104 catch (ParserConfigurationException pce) |
|
105 { |
|
106 throw new RuntimeException( |
|
107 XMLMessages.createXMLMessage( |
|
108 XMLErrorResources.ER_CREATEDOCUMENT_NOT_SUPPORTED, null)); //"createDocument() not supported in XPathContext!"); |
|
109 |
|
110 // return null; |
|
111 } |
|
112 } |
|
113 |
|
114 /** |
|
115 * DOM Level 1 did not have a standard mechanism for creating a new |
|
116 * Document object. This function provides a DOM-implementation-independent |
|
117 * abstraction for that for that concept. It's typically used when |
|
118 * outputting a new DOM as the result of an operation. |
|
119 * |
|
120 * @return The newly created DOM Document object, with no children, or |
|
121 * null if we can't find a DOM implementation that permits creating |
|
122 * new empty Documents. |
|
123 */ |
|
124 public static Document createDocument() |
|
125 { |
|
126 return createDocument(false); |
|
127 } |
|
128 |
|
129 /** |
|
130 * Tells, through the combination of the default-space attribute |
|
131 * on xsl:stylesheet, xsl:strip-space, xsl:preserve-space, and the |
|
132 * xml:space attribute, whether or not extra whitespace should be stripped |
|
133 * from the node. Literal elements from template elements should |
|
134 * <em>not</em> be tested with this function. |
|
135 * @param textNode A text node from the source tree. |
|
136 * @return true if the text node should be stripped of extra whitespace. |
|
137 * |
|
138 * @throws javax.xml.transform.TransformerException |
|
139 * @xsl.usage advanced |
|
140 */ |
|
141 public boolean shouldStripSourceNode(Node textNode) |
|
142 throws javax.xml.transform.TransformerException |
|
143 { |
|
144 |
|
145 // return (null == m_envSupport) ? false : m_envSupport.shouldStripSourceNode(textNode); |
|
146 return false; |
|
147 } |
|
148 |
|
149 /** |
|
150 * Supports the XPath function GenerateID by returning a unique |
|
151 * identifier string for any given DOM Node. |
|
152 * <p> |
|
153 * Warning: The base implementation uses the Node object's hashCode(), |
|
154 * which is NOT guaranteed to be unique. If that method hasn't been |
|
155 * overridden in this DOM ipmlementation, most Java implementions will |
|
156 * derive it from the object's address and should be OK... but if |
|
157 * your DOM uses a different definition of hashCode (eg hashing the |
|
158 * contents of the subtree), or if your DOM may have multiple objects |
|
159 * that represent a single Node in the data structure (eg via proxying), |
|
160 * you may need to find another way to assign a unique identifier. |
|
161 * <p> |
|
162 * Also, be aware that if nodes are destroyed and recreated, there is |
|
163 * an open issue regarding whether an ID may be reused. Currently |
|
164 * we're assuming that the input document is stable for the duration |
|
165 * of the XPath/XSLT operation, so this shouldn't arise in this context. |
|
166 * <p> |
|
167 * (DOM Level 3 is investigating providing a unique node "key", but |
|
168 * that won't help Level 1 and Level 2 implementations.) |
|
169 * |
|
170 * @param node whose identifier you want to obtain |
|
171 * |
|
172 * @return a string which should be different for every Node object. |
|
173 */ |
|
174 public String getUniqueID(Node node) |
|
175 { |
|
176 return "N" + Integer.toHexString(node.hashCode()).toUpperCase(); |
|
177 } |
|
178 |
|
179 /** |
|
180 * Figure out whether node2 should be considered as being later |
|
181 * in the document than node1, in Document Order as defined |
|
182 * by the XPath model. This may not agree with the ordering defined |
|
183 * by other XML applications. |
|
184 * <p> |
|
185 * There are some cases where ordering isn't defined, and neither are |
|
186 * the results of this function -- though we'll generally return true. |
|
187 * |
|
188 * TODO: Make sure this does the right thing with attribute nodes!!! |
|
189 * |
|
190 * @param node1 DOM Node to perform position comparison on. |
|
191 * @param node2 DOM Node to perform position comparison on . |
|
192 * |
|
193 * @return false if node2 comes before node1, otherwise return true. |
|
194 * You can think of this as |
|
195 * <code>(node1.documentOrderPosition <= node2.documentOrderPosition)</code>. |
|
196 */ |
|
197 public static boolean isNodeAfter(Node node1, Node node2) |
|
198 { |
|
199 if (node1 == node2 || isNodeTheSame(node1, node2)) |
|
200 return true; |
|
201 |
|
202 // Default return value, if there is no defined ordering |
|
203 boolean isNodeAfter = true; |
|
204 |
|
205 Node parent1 = getParentOfNode(node1); |
|
206 Node parent2 = getParentOfNode(node2); |
|
207 |
|
208 // Optimize for most common case |
|
209 if (parent1 == parent2 || isNodeTheSame(parent1, parent2)) // then we know they are siblings |
|
210 { |
|
211 if (null != parent1) |
|
212 isNodeAfter = isNodeAfterSibling(parent1, node1, node2); |
|
213 else |
|
214 { |
|
215 // If both parents are null, ordering is not defined. |
|
216 // We're returning a value in lieu of throwing an exception. |
|
217 // Not a case we expect to arise in XPath, but beware if you |
|
218 // try to reuse this method. |
|
219 |
|
220 // We can just fall through in this case, which allows us |
|
221 // to hit the debugging code at the end of the function. |
|
222 //return isNodeAfter; |
|
223 } |
|
224 } |
|
225 else |
|
226 { |
|
227 |
|
228 // General strategy: Figure out the lengths of the two |
|
229 // ancestor chains, reconcile the lengths, and look for |
|
230 // the lowest common ancestor. If that ancestor is one of |
|
231 // the nodes being compared, it comes before the other. |
|
232 // Otherwise perform a sibling compare. |
|
233 // |
|
234 // NOTE: If no common ancestor is found, ordering is undefined |
|
235 // and we return the default value of isNodeAfter. |
|
236 |
|
237 // Count parents in each ancestor chain |
|
238 int nParents1 = 2, nParents2 = 2; // include node & parent obtained above |
|
239 |
|
240 while (parent1 != null) |
|
241 { |
|
242 nParents1++; |
|
243 |
|
244 parent1 = getParentOfNode(parent1); |
|
245 } |
|
246 |
|
247 while (parent2 != null) |
|
248 { |
|
249 nParents2++; |
|
250 |
|
251 parent2 = getParentOfNode(parent2); |
|
252 } |
|
253 |
|
254 // Initially assume scan for common ancestor starts with |
|
255 // the input nodes. |
|
256 Node startNode1 = node1, startNode2 = node2; |
|
257 |
|
258 // If one ancestor chain is longer, adjust its start point |
|
259 // so we're comparing at the same depths |
|
260 if (nParents1 < nParents2) |
|
261 { |
|
262 // Adjust startNode2 to depth of startNode1 |
|
263 int adjust = nParents2 - nParents1; |
|
264 |
|
265 for (int i = 0; i < adjust; i++) |
|
266 { |
|
267 startNode2 = getParentOfNode(startNode2); |
|
268 } |
|
269 } |
|
270 else if (nParents1 > nParents2) |
|
271 { |
|
272 // adjust startNode1 to depth of startNode2 |
|
273 int adjust = nParents1 - nParents2; |
|
274 |
|
275 for (int i = 0; i < adjust; i++) |
|
276 { |
|
277 startNode1 = getParentOfNode(startNode1); |
|
278 } |
|
279 } |
|
280 |
|
281 Node prevChild1 = null, prevChild2 = null; // so we can "back up" |
|
282 |
|
283 // Loop up the ancestor chain looking for common parent |
|
284 while (null != startNode1) |
|
285 { |
|
286 if (startNode1 == startNode2 || isNodeTheSame(startNode1, startNode2)) // common parent? |
|
287 { |
|
288 if (null == prevChild1) // first time in loop? |
|
289 { |
|
290 |
|
291 // Edge condition: one is the ancestor of the other. |
|
292 isNodeAfter = (nParents1 < nParents2) ? true : false; |
|
293 |
|
294 break; // from while loop |
|
295 } |
|
296 else |
|
297 { |
|
298 // Compare ancestors below lowest-common as siblings |
|
299 isNodeAfter = isNodeAfterSibling(startNode1, prevChild1, |
|
300 prevChild2); |
|
301 |
|
302 break; // from while loop |
|
303 } |
|
304 } // end if(startNode1 == startNode2) |
|
305 |
|
306 // Move up one level and try again |
|
307 prevChild1 = startNode1; |
|
308 startNode1 = getParentOfNode(startNode1); |
|
309 prevChild2 = startNode2; |
|
310 startNode2 = getParentOfNode(startNode2); |
|
311 } // end while(parents exist to examine) |
|
312 } // end big else (not immediate siblings) |
|
313 |
|
314 // WARNING: The following diagnostic won't report the early |
|
315 // "same node" case. Fix if/when needed. |
|
316 |
|
317 /* -- please do not remove... very useful for diagnostics -- |
|
318 System.out.println("node1 = "+node1.getNodeName()+"("+node1.getNodeType()+")"+ |
|
319 ", node2 = "+node2.getNodeName() |
|
320 +"("+node2.getNodeType()+")"+ |
|
321 ", isNodeAfter = "+isNodeAfter); */ |
|
322 return isNodeAfter; |
|
323 } // end isNodeAfter(Node node1, Node node2) |
|
324 |
|
325 /** |
|
326 * Use DTMNodeProxy to determine whether two nodes are the same. |
|
327 * |
|
328 * @param node1 The first DOM node to compare. |
|
329 * @param node2 The second DOM node to compare. |
|
330 * @return true if the two nodes are the same. |
|
331 */ |
|
332 public static boolean isNodeTheSame(Node node1, Node node2) |
|
333 { |
|
334 if (node1 instanceof DTMNodeProxy && node2 instanceof DTMNodeProxy) |
|
335 return ((DTMNodeProxy)node1).equals((DTMNodeProxy)node2); |
|
336 else |
|
337 return (node1 == node2); |
|
338 } |
|
339 |
|
340 /** |
|
341 * Figure out if child2 is after child1 in document order. |
|
342 * <p> |
|
343 * Warning: Some aspects of "document order" are not well defined. |
|
344 * For example, the order of attributes is considered |
|
345 * meaningless in XML, and the order reported by our model will |
|
346 * be consistant for a given invocation but may not |
|
347 * match that of either the source file or the serialized output. |
|
348 * |
|
349 * @param parent Must be the parent of both child1 and child2. |
|
350 * @param child1 Must be the child of parent and not equal to child2. |
|
351 * @param child2 Must be the child of parent and not equal to child1. |
|
352 * @return true if child 2 is after child1 in document order. |
|
353 */ |
|
354 private static boolean isNodeAfterSibling(Node parent, Node child1, |
|
355 Node child2) |
|
356 { |
|
357 |
|
358 boolean isNodeAfterSibling = false; |
|
359 short child1type = child1.getNodeType(); |
|
360 short child2type = child2.getNodeType(); |
|
361 |
|
362 if ((Node.ATTRIBUTE_NODE != child1type) |
|
363 && (Node.ATTRIBUTE_NODE == child2type)) |
|
364 { |
|
365 |
|
366 // always sort attributes before non-attributes. |
|
367 isNodeAfterSibling = false; |
|
368 } |
|
369 else if ((Node.ATTRIBUTE_NODE == child1type) |
|
370 && (Node.ATTRIBUTE_NODE != child2type)) |
|
371 { |
|
372 |
|
373 // always sort attributes before non-attributes. |
|
374 isNodeAfterSibling = true; |
|
375 } |
|
376 else if (Node.ATTRIBUTE_NODE == child1type) |
|
377 { |
|
378 NamedNodeMap children = parent.getAttributes(); |
|
379 int nNodes = children.getLength(); |
|
380 boolean found1 = false, found2 = false; |
|
381 |
|
382 // Count from the start until we find one or the other. |
|
383 for (int i = 0; i < nNodes; i++) |
|
384 { |
|
385 Node child = children.item(i); |
|
386 |
|
387 if (child1 == child || isNodeTheSame(child1, child)) |
|
388 { |
|
389 if (found2) |
|
390 { |
|
391 isNodeAfterSibling = false; |
|
392 |
|
393 break; |
|
394 } |
|
395 |
|
396 found1 = true; |
|
397 } |
|
398 else if (child2 == child || isNodeTheSame(child2, child)) |
|
399 { |
|
400 if (found1) |
|
401 { |
|
402 isNodeAfterSibling = true; |
|
403 |
|
404 break; |
|
405 } |
|
406 |
|
407 found2 = true; |
|
408 } |
|
409 } |
|
410 } |
|
411 else |
|
412 { |
|
413 // TODO: Check performance of alternate solution: |
|
414 // There are two choices here: Count from the start of |
|
415 // the document until we find one or the other, or count |
|
416 // from one until we find or fail to find the other. |
|
417 // Either can wind up scanning all the siblings in the worst |
|
418 // case, which on a wide document can be a lot of work but |
|
419 // is more typically is a short list. |
|
420 // Scanning from the start involves two tests per iteration, |
|
421 // but it isn't clear that scanning from the middle doesn't |
|
422 // yield more iterations on average. |
|
423 // We should run some testcases. |
|
424 Node child = parent.getFirstChild(); |
|
425 boolean found1 = false, found2 = false; |
|
426 |
|
427 while (null != child) |
|
428 { |
|
429 |
|
430 // Node child = children.item(i); |
|
431 if (child1 == child || isNodeTheSame(child1, child)) |
|
432 { |
|
433 if (found2) |
|
434 { |
|
435 isNodeAfterSibling = false; |
|
436 |
|
437 break; |
|
438 } |
|
439 |
|
440 found1 = true; |
|
441 } |
|
442 else if (child2 == child || isNodeTheSame(child2, child)) |
|
443 { |
|
444 if (found1) |
|
445 { |
|
446 isNodeAfterSibling = true; |
|
447 |
|
448 break; |
|
449 } |
|
450 |
|
451 found2 = true; |
|
452 } |
|
453 |
|
454 child = child.getNextSibling(); |
|
455 } |
|
456 } |
|
457 |
|
458 return isNodeAfterSibling; |
|
459 } // end isNodeAfterSibling(Node parent, Node child1, Node child2) |
|
460 |
|
461 //========================================================== |
|
462 // SECTION: Namespace resolution |
|
463 //========================================================== |
|
464 |
|
465 /** |
|
466 * Get the depth level of this node in the tree (equals 1 for |
|
467 * a parentless node). |
|
468 * |
|
469 * @param n Node to be examined. |
|
470 * @return the number of ancestors, plus one |
|
471 * @xsl.usage internal |
|
472 */ |
|
473 public short getLevel(Node n) |
|
474 { |
|
475 |
|
476 short level = 1; |
|
477 |
|
478 while (null != (n = getParentOfNode(n))) |
|
479 { |
|
480 level++; |
|
481 } |
|
482 |
|
483 return level; |
|
484 } |
|
485 |
|
486 /** |
|
487 * Given an XML Namespace prefix and a context in which the prefix |
|
488 * is to be evaluated, return the Namespace Name this prefix was |
|
489 * bound to. Note that DOM Level 3 is expected to provide a version of |
|
490 * this which deals with the DOM's "early binding" behavior. |
|
491 * |
|
492 * Default handling: |
|
493 * |
|
494 * @param prefix String containing namespace prefix to be resolved, |
|
495 * without the ':' which separates it from the localname when used |
|
496 * in a Node Name. The empty sting signifies the default namespace |
|
497 * at this point in the document. |
|
498 * @param namespaceContext Element which provides context for resolution. |
|
499 * (We could extend this to work for other nodes by first seeking their |
|
500 * nearest Element ancestor.) |
|
501 * |
|
502 * @return a String containing the Namespace URI which this prefix |
|
503 * represents in the specified context. |
|
504 */ |
|
505 public String getNamespaceForPrefix(String prefix, Element namespaceContext) |
|
506 { |
|
507 |
|
508 int type; |
|
509 Node parent = namespaceContext; |
|
510 String namespace = null; |
|
511 |
|
512 if (prefix.equals("xml")) |
|
513 { |
|
514 namespace = QName.S_XMLNAMESPACEURI; // Hardcoded, per Namespace spec |
|
515 } |
|
516 else if(prefix.equals("xmlns")) |
|
517 { |
|
518 // Hardcoded in the DOM spec, expected to be adopted by |
|
519 // Namespace spec. NOTE: Namespace declarations _must_ use |
|
520 // the xmlns: prefix; other prefixes declared as belonging |
|
521 // to this namespace will not be recognized and should |
|
522 // probably be rejected by parsers as erroneous declarations. |
|
523 namespace = "http://www.w3.org/2000/xmlns/"; |
|
524 } |
|
525 else |
|
526 { |
|
527 // Attribute name for this prefix's declaration |
|
528 String declname=(prefix=="") |
|
529 ? "xmlns" |
|
530 : "xmlns:"+prefix; |
|
531 |
|
532 // Scan until we run out of Elements or have resolved the namespace |
|
533 while ((null != parent) && (null == namespace) |
|
534 && (((type = parent.getNodeType()) == Node.ELEMENT_NODE) |
|
535 || (type == Node.ENTITY_REFERENCE_NODE))) |
|
536 { |
|
537 if (type == Node.ELEMENT_NODE) |
|
538 { |
|
539 |
|
540 // Look for the appropriate Namespace Declaration attribute, |
|
541 // either "xmlns:prefix" or (if prefix is "") "xmlns". |
|
542 // TODO: This does not handle "implicit declarations" |
|
543 // which may be created when the DOM is edited. DOM Level |
|
544 // 3 will define how those should be interpreted. But |
|
545 // this issue won't arise in freshly-parsed DOMs. |
|
546 |
|
547 // NOTE: declname is set earlier, outside the loop. |
|
548 Attr attr=((Element)parent).getAttributeNode(declname); |
|
549 if(attr!=null) |
|
550 { |
|
551 namespace = attr.getNodeValue(); |
|
552 break; |
|
553 } |
|
554 } |
|
555 |
|
556 parent = getParentOfNode(parent); |
|
557 } |
|
558 } |
|
559 |
|
560 return namespace; |
|
561 } |
|
562 |
|
563 /** |
|
564 * An experiment for the moment. |
|
565 */ |
|
566 Map<Node, NSInfo> m_NSInfos = new HashMap<>(); |
|
567 |
|
568 /** Object to put into the m_NSInfos table that tells that a node has not been |
|
569 * processed, but has xmlns namespace decls. */ |
|
570 protected static final NSInfo m_NSInfoUnProcWithXMLNS = new NSInfo(false, |
|
571 true); |
|
572 |
|
573 /** Object to put into the m_NSInfos table that tells that a node has not been |
|
574 * processed, but has no xmlns namespace decls. */ |
|
575 protected static final NSInfo m_NSInfoUnProcWithoutXMLNS = new NSInfo(false, |
|
576 false); |
|
577 |
|
578 /** Object to put into the m_NSInfos table that tells that a node has not been |
|
579 * processed, and has no xmlns namespace decls, and has no ancestor decls. */ |
|
580 protected static final NSInfo m_NSInfoUnProcNoAncestorXMLNS = |
|
581 new NSInfo(false, false, NSInfo.ANCESTORNOXMLNS); |
|
582 |
|
583 /** Object to put into the m_NSInfos table that tells that a node has been |
|
584 * processed, and has xmlns namespace decls. */ |
|
585 protected static final NSInfo m_NSInfoNullWithXMLNS = new NSInfo(true, |
|
586 true); |
|
587 |
|
588 /** Object to put into the m_NSInfos table that tells that a node has been |
|
589 * processed, and has no xmlns namespace decls. */ |
|
590 protected static final NSInfo m_NSInfoNullWithoutXMLNS = new NSInfo(true, |
|
591 false); |
|
592 |
|
593 /** Object to put into the m_NSInfos table that tells that a node has been |
|
594 * processed, and has no xmlns namespace decls. and has no ancestor decls. */ |
|
595 protected static final NSInfo m_NSInfoNullNoAncestorXMLNS = |
|
596 new NSInfo(true, false, NSInfo.ANCESTORNOXMLNS); |
|
597 |
|
598 /** Vector of node (odd indexes) and NSInfos (even indexes) that tell if |
|
599 * the given node is a candidate for ancestor namespace processing. */ |
|
600 protected Vector m_candidateNoAncestorXMLNS = new Vector(); |
|
601 |
|
602 /** |
|
603 * Returns the namespace of the given node. Differs from simply getting |
|
604 * the node's prefix and using getNamespaceForPrefix in that it attempts |
|
605 * to cache some of the data in NSINFO objects, to avoid repeated lookup. |
|
606 * TODO: Should we consider moving that logic into getNamespaceForPrefix? |
|
607 * |
|
608 * @param n Node to be examined. |
|
609 * |
|
610 * @return String containing the Namespace Name (uri) for this node. |
|
611 * Note that this is undefined for any nodes other than Elements and |
|
612 * Attributes. |
|
613 */ |
|
614 public String getNamespaceOfNode(Node n) |
|
615 { |
|
616 |
|
617 String namespaceOfPrefix; |
|
618 boolean hasProcessedNS; |
|
619 NSInfo nsInfo; |
|
620 short ntype = n.getNodeType(); |
|
621 |
|
622 if (Node.ATTRIBUTE_NODE != ntype) |
|
623 { |
|
624 nsInfo = m_NSInfos.get(n); |
|
625 hasProcessedNS = (nsInfo == null) ? false : nsInfo.m_hasProcessedNS; |
|
626 } |
|
627 else |
|
628 { |
|
629 hasProcessedNS = false; |
|
630 nsInfo = null; |
|
631 } |
|
632 |
|
633 if (hasProcessedNS) |
|
634 { |
|
635 namespaceOfPrefix = nsInfo.m_namespace; |
|
636 } |
|
637 else |
|
638 { |
|
639 namespaceOfPrefix = null; |
|
640 |
|
641 String nodeName = n.getNodeName(); |
|
642 int indexOfNSSep = nodeName.indexOf(':'); |
|
643 String prefix; |
|
644 |
|
645 if (Node.ATTRIBUTE_NODE == ntype) |
|
646 { |
|
647 if (indexOfNSSep > 0) |
|
648 { |
|
649 prefix = nodeName.substring(0, indexOfNSSep); |
|
650 } |
|
651 else |
|
652 { |
|
653 |
|
654 // Attributes don't use the default namespace, so if |
|
655 // there isn't a prefix, we're done. |
|
656 return namespaceOfPrefix; |
|
657 } |
|
658 } |
|
659 else |
|
660 { |
|
661 prefix = (indexOfNSSep >= 0) |
|
662 ? nodeName.substring(0, indexOfNSSep) : ""; |
|
663 } |
|
664 |
|
665 boolean ancestorsHaveXMLNS = false; |
|
666 boolean nHasXMLNS = false; |
|
667 |
|
668 if (prefix.equals("xml")) |
|
669 { |
|
670 namespaceOfPrefix = QName.S_XMLNAMESPACEURI; |
|
671 } |
|
672 else |
|
673 { |
|
674 int parentType; |
|
675 Node parent = n; |
|
676 |
|
677 while ((null != parent) && (null == namespaceOfPrefix)) |
|
678 { |
|
679 if ((null != nsInfo) |
|
680 && (nsInfo.m_ancestorHasXMLNSAttrs |
|
681 == NSInfo.ANCESTORNOXMLNS)) |
|
682 { |
|
683 break; |
|
684 } |
|
685 |
|
686 parentType = parent.getNodeType(); |
|
687 |
|
688 if ((null == nsInfo) || nsInfo.m_hasXMLNSAttrs) |
|
689 { |
|
690 boolean elementHasXMLNS = false; |
|
691 |
|
692 if (parentType == Node.ELEMENT_NODE) |
|
693 { |
|
694 NamedNodeMap nnm = parent.getAttributes(); |
|
695 |
|
696 for (int i = 0; i < nnm.getLength(); i++) |
|
697 { |
|
698 Node attr = nnm.item(i); |
|
699 String aname = attr.getNodeName(); |
|
700 |
|
701 if (aname.charAt(0) == 'x') |
|
702 { |
|
703 boolean isPrefix = aname.startsWith("xmlns:"); |
|
704 |
|
705 if (aname.equals("xmlns") || isPrefix) |
|
706 { |
|
707 if (n == parent) |
|
708 nHasXMLNS = true; |
|
709 |
|
710 elementHasXMLNS = true; |
|
711 ancestorsHaveXMLNS = true; |
|
712 |
|
713 String p = isPrefix ? aname.substring(6) : ""; |
|
714 |
|
715 if (p.equals(prefix)) |
|
716 { |
|
717 namespaceOfPrefix = attr.getNodeValue(); |
|
718 |
|
719 break; |
|
720 } |
|
721 } |
|
722 } |
|
723 } |
|
724 } |
|
725 |
|
726 if ((Node.ATTRIBUTE_NODE != parentType) && (null == nsInfo) |
|
727 && (n != parent)) |
|
728 { |
|
729 nsInfo = elementHasXMLNS |
|
730 ? m_NSInfoUnProcWithXMLNS : m_NSInfoUnProcWithoutXMLNS; |
|
731 |
|
732 m_NSInfos.put(parent, nsInfo); |
|
733 } |
|
734 } |
|
735 |
|
736 if (Node.ATTRIBUTE_NODE == parentType) |
|
737 { |
|
738 parent = getParentOfNode(parent); |
|
739 } |
|
740 else |
|
741 { |
|
742 m_candidateNoAncestorXMLNS.addElement(parent); |
|
743 m_candidateNoAncestorXMLNS.addElement(nsInfo); |
|
744 |
|
745 parent = parent.getParentNode(); |
|
746 } |
|
747 |
|
748 if (null != parent) |
|
749 { |
|
750 nsInfo = m_NSInfos.get(parent); |
|
751 } |
|
752 } |
|
753 |
|
754 int nCandidates = m_candidateNoAncestorXMLNS.size(); |
|
755 |
|
756 if (nCandidates > 0) |
|
757 { |
|
758 if ((false == ancestorsHaveXMLNS) && (null == parent)) |
|
759 { |
|
760 for (int i = 0; i < nCandidates; i += 2) |
|
761 { |
|
762 Object candidateInfo = m_candidateNoAncestorXMLNS.elementAt(i |
|
763 + 1); |
|
764 |
|
765 if (candidateInfo == m_NSInfoUnProcWithoutXMLNS) |
|
766 { |
|
767 m_NSInfos.put((Node)m_candidateNoAncestorXMLNS.elementAt(i), |
|
768 m_NSInfoUnProcNoAncestorXMLNS); |
|
769 } |
|
770 else if (candidateInfo == m_NSInfoNullWithoutXMLNS) |
|
771 { |
|
772 m_NSInfos.put((Node)m_candidateNoAncestorXMLNS.elementAt(i), |
|
773 m_NSInfoNullNoAncestorXMLNS); |
|
774 } |
|
775 } |
|
776 } |
|
777 |
|
778 m_candidateNoAncestorXMLNS.removeAllElements(); |
|
779 } |
|
780 } |
|
781 |
|
782 if (Node.ATTRIBUTE_NODE != ntype) |
|
783 { |
|
784 if (null == namespaceOfPrefix) |
|
785 { |
|
786 if (ancestorsHaveXMLNS) |
|
787 { |
|
788 if (nHasXMLNS) |
|
789 m_NSInfos.put(n, m_NSInfoNullWithXMLNS); |
|
790 else |
|
791 m_NSInfos.put(n, m_NSInfoNullWithoutXMLNS); |
|
792 } |
|
793 else |
|
794 { |
|
795 m_NSInfos.put(n, m_NSInfoNullNoAncestorXMLNS); |
|
796 } |
|
797 } |
|
798 else |
|
799 { |
|
800 m_NSInfos.put(n, new NSInfo(namespaceOfPrefix, nHasXMLNS)); |
|
801 } |
|
802 } |
|
803 } |
|
804 |
|
805 return namespaceOfPrefix; |
|
806 } |
|
807 |
|
808 /** |
|
809 * Returns the local name of the given node. If the node's name begins |
|
810 * with a namespace prefix, this is the part after the colon; otherwise |
|
811 * it's the full node name. |
|
812 * |
|
813 * @param n the node to be examined. |
|
814 * |
|
815 * @return String containing the Local Name |
|
816 */ |
|
817 public String getLocalNameOfNode(Node n) |
|
818 { |
|
819 |
|
820 String qname = n.getNodeName(); |
|
821 int index = qname.indexOf(':'); |
|
822 |
|
823 return (index < 0) ? qname : qname.substring(index + 1); |
|
824 } |
|
825 |
|
826 /** |
|
827 * Returns the element name with the namespace prefix (if any) replaced |
|
828 * by the Namespace URI it was bound to. This is not a standard |
|
829 * representation of a node name, but it allows convenient |
|
830 * single-string comparison of the "universal" names of two nodes. |
|
831 * |
|
832 * @param elem Element to be examined. |
|
833 * |
|
834 * @return String in the form "namespaceURI:localname" if the node |
|
835 * belongs to a namespace, or simply "localname" if it doesn't. |
|
836 * @see #getExpandedAttributeName |
|
837 */ |
|
838 public String getExpandedElementName(Element elem) |
|
839 { |
|
840 |
|
841 String namespace = getNamespaceOfNode(elem); |
|
842 |
|
843 return (null != namespace) |
|
844 ? namespace + ":" + getLocalNameOfNode(elem) |
|
845 : getLocalNameOfNode(elem); |
|
846 } |
|
847 |
|
848 /** |
|
849 * Returns the attribute name with the namespace prefix (if any) replaced |
|
850 * by the Namespace URI it was bound to. This is not a standard |
|
851 * representation of a node name, but it allows convenient |
|
852 * single-string comparison of the "universal" names of two nodes. |
|
853 * |
|
854 * @param attr Attr to be examined |
|
855 * |
|
856 * @return String in the form "namespaceURI:localname" if the node |
|
857 * belongs to a namespace, or simply "localname" if it doesn't. |
|
858 * @see #getExpandedElementName |
|
859 */ |
|
860 public String getExpandedAttributeName(Attr attr) |
|
861 { |
|
862 |
|
863 String namespace = getNamespaceOfNode(attr); |
|
864 |
|
865 return (null != namespace) |
|
866 ? namespace + ":" + getLocalNameOfNode(attr) |
|
867 : getLocalNameOfNode(attr); |
|
868 } |
|
869 |
|
870 //========================================================== |
|
871 // SECTION: DOM Helper Functions |
|
872 //========================================================== |
|
873 |
|
874 /** |
|
875 * Tell if the node is ignorable whitespace. Note that this can |
|
876 * be determined only in the context of a DTD or other Schema, |
|
877 * and that DOM Level 2 has nostandardized DOM API which can |
|
878 * return that information. |
|
879 * @deprecated |
|
880 * |
|
881 * @param node Node to be examined |
|
882 * |
|
883 * @return CURRENTLY HARDCODED TO FALSE, but should return true if |
|
884 * and only if the node is of type Text, contains only whitespace, |
|
885 * and does not appear as part of the #PCDATA content of an element. |
|
886 * (Note that determining this last may require allowing for |
|
887 * Entity References.) |
|
888 */ |
|
889 public boolean isIgnorableWhitespace(Text node) |
|
890 { |
|
891 |
|
892 boolean isIgnorable = false; // return value |
|
893 |
|
894 // TODO: I can probably do something to figure out if this |
|
895 // space is ignorable from just the information in |
|
896 // the DOM tree. |
|
897 // -- You need to be able to distinguish whitespace |
|
898 // that is #PCDATA from whitespace that isn't. That requires |
|
899 // DTD support, which won't be standardized until DOM Level 3. |
|
900 return isIgnorable; |
|
901 } |
|
902 |
|
903 /** |
|
904 * Get the first unparented node in the ancestor chain. |
|
905 * @deprecated |
|
906 * |
|
907 * @param node Starting node, to specify which chain to chase |
|
908 * |
|
909 * @return the topmost ancestor. |
|
910 */ |
|
911 public Node getRoot(Node node) |
|
912 { |
|
913 |
|
914 Node root = null; |
|
915 |
|
916 while (node != null) |
|
917 { |
|
918 root = node; |
|
919 node = getParentOfNode(node); |
|
920 } |
|
921 |
|
922 return root; |
|
923 } |
|
924 |
|
925 /** |
|
926 * Get the root node of the document tree, regardless of |
|
927 * whether or not the node passed in is a document node. |
|
928 * <p> |
|
929 * TODO: This doesn't handle DocumentFragments or "orphaned" subtrees |
|
930 * -- it's currently returning ownerDocument even when the tree is |
|
931 * not actually part of the main Document tree. We should either |
|
932 * rewrite the description to say that it finds the Document node, |
|
933 * or change the code to walk up the ancestor chain. |
|
934 |
|
935 * |
|
936 * @param n Node to be examined |
|
937 * |
|
938 * @return the Document node. Note that this is not the correct answer |
|
939 * if n was (or was a child of) a DocumentFragment or an orphaned node, |
|
940 * as can arise if the DOM has been edited rather than being generated |
|
941 * by a parser. |
|
942 */ |
|
943 public Node getRootNode(Node n) |
|
944 { |
|
945 int nt = n.getNodeType(); |
|
946 return ( (Node.DOCUMENT_NODE == nt) || (Node.DOCUMENT_FRAGMENT_NODE == nt) ) |
|
947 ? n : n.getOwnerDocument(); |
|
948 } |
|
949 |
|
950 /** |
|
951 * Test whether the given node is a namespace decl node. In DOM Level 2 |
|
952 * this can be done in a namespace-aware manner, but in Level 1 DOMs |
|
953 * it has to be done by testing the node name. |
|
954 * |
|
955 * @param n Node to be examined. |
|
956 * |
|
957 * @return boolean -- true iff the node is an Attr whose name is |
|
958 * "xmlns" or has the "xmlns:" prefix. |
|
959 */ |
|
960 public boolean isNamespaceNode(Node n) |
|
961 { |
|
962 |
|
963 if (Node.ATTRIBUTE_NODE == n.getNodeType()) |
|
964 { |
|
965 String attrName = n.getNodeName(); |
|
966 |
|
967 return (attrName.startsWith("xmlns:") || attrName.equals("xmlns")); |
|
968 } |
|
969 |
|
970 return false; |
|
971 } |
|
972 |
|
973 /** |
|
974 * Obtain the XPath-model parent of a DOM node -- ownerElement for Attrs, |
|
975 * parent for other nodes. |
|
976 * <p> |
|
977 * Background: The DOM believes that you must be your Parent's |
|
978 * Child, and thus Attrs don't have parents. XPath said that Attrs |
|
979 * do have their owning Element as their parent. This function |
|
980 * bridges the difference, either by using the DOM Level 2 ownerElement |
|
981 * function or by using a "silly and expensive function" in Level 1 |
|
982 * DOMs. |
|
983 * <p> |
|
984 * (There's some discussion of future DOMs generalizing ownerElement |
|
985 * into ownerNode and making it work on all types of nodes. This |
|
986 * still wouldn't help the users of Level 1 or Level 2 DOMs) |
|
987 * <p> |
|
988 * |
|
989 * @param node Node whose XPath parent we want to obtain |
|
990 * |
|
991 * @return the parent of the node, or the ownerElement if it's an |
|
992 * Attr node, or null if the node is an orphan. |
|
993 * |
|
994 * @throws RuntimeException if the Document has no root element. |
|
995 * This can't arise if the Document was created |
|
996 * via the DOM Level 2 factory methods, but is possible if other |
|
997 * mechanisms were used to obtain it |
|
998 */ |
|
999 public static Node getParentOfNode(Node node) throws RuntimeException |
|
1000 { |
|
1001 Node parent; |
|
1002 short nodeType = node.getNodeType(); |
|
1003 |
|
1004 if (Node.ATTRIBUTE_NODE == nodeType) |
|
1005 { |
|
1006 Document doc = node.getOwnerDocument(); |
|
1007 /* |
|
1008 TBD: |
|
1009 if(null == doc) |
|
1010 { |
|
1011 throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CHILD_HAS_NO_OWNER_DOCUMENT, null));//"Attribute child does not have an owner document!"); |
|
1012 } |
|
1013 */ |
|
1014 |
|
1015 // Given how expensive the tree walk may be, we should first ask |
|
1016 // whether this DOM can answer the question for us. The additional |
|
1017 // test does slow down Level 1 DOMs slightly. DOMHelper2, which |
|
1018 // is currently specialized for Xerces, assumes it can use the |
|
1019 // Level 2 solution. We might want to have an intermediate stage, |
|
1020 // which would assume DOM Level 2 but not assume Xerces. |
|
1021 // |
|
1022 // (Shouldn't have to check whether impl is null in a compliant DOM, |
|
1023 // but let's be paranoid for a moment...) |
|
1024 DOMImplementation impl=doc.getImplementation(); |
|
1025 if(impl!=null && impl.hasFeature("Core","2.0")) |
|
1026 { |
|
1027 parent=((Attr)node).getOwnerElement(); |
|
1028 return parent; |
|
1029 } |
|
1030 |
|
1031 // DOM Level 1 solution, as fallback. Hugely expensive. |
|
1032 |
|
1033 Element rootElem = doc.getDocumentElement(); |
|
1034 |
|
1035 if (null == rootElem) |
|
1036 { |
|
1037 throw new RuntimeException( |
|
1038 XMLMessages.createXMLMessage( |
|
1039 XMLErrorResources.ER_CHILD_HAS_NO_OWNER_DOCUMENT_ELEMENT, |
|
1040 null)); //"Attribute child does not have an owner document element!"); |
|
1041 } |
|
1042 |
|
1043 parent = locateAttrParent(rootElem, node); |
|
1044 |
|
1045 } |
|
1046 else |
|
1047 { |
|
1048 parent = node.getParentNode(); |
|
1049 |
|
1050 // if((Node.DOCUMENT_NODE != nodeType) && (null == parent)) |
|
1051 // { |
|
1052 // throw new RuntimeException("Child does not have parent!"); |
|
1053 // } |
|
1054 } |
|
1055 |
|
1056 return parent; |
|
1057 } |
|
1058 |
|
1059 /** |
|
1060 * Given an ID, return the element. This can work only if the document |
|
1061 * is interpreted in the context of a DTD or Schema, since otherwise |
|
1062 * we don't know which attributes are or aren't IDs. |
|
1063 * <p> |
|
1064 * Note that DOM Level 1 had no ability to retrieve this information. |
|
1065 * DOM Level 2 introduced it but does not promise that it will be |
|
1066 * supported in all DOMs; those which can't support it will always |
|
1067 * return null. |
|
1068 * <p> |
|
1069 * TODO: getElementByID is currently unimplemented. Support DOM Level 2? |
|
1070 * |
|
1071 * @param id The unique identifier to be searched for. |
|
1072 * @param doc The document to search within. |
|
1073 * @return CURRENTLY HARDCODED TO NULL, but it should be: |
|
1074 * The node which has this unique identifier, or null if there |
|
1075 * is no such node or this DOM can't reliably recognize it. |
|
1076 */ |
|
1077 public Element getElementByID(String id, Document doc) |
|
1078 { |
|
1079 return null; |
|
1080 } |
|
1081 |
|
1082 /** |
|
1083 * The getUnparsedEntityURI function returns the URI of the unparsed |
|
1084 * entity with the specified name in the same document as the context |
|
1085 * node (see [3.3 Unparsed Entities]). It returns the empty string if |
|
1086 * there is no such entity. |
|
1087 * <p> |
|
1088 * XML processors may choose to use the System Identifier (if one |
|
1089 * is provided) to resolve the entity, rather than the URI in the |
|
1090 * Public Identifier. The details are dependent on the processor, and |
|
1091 * we would have to support some form of plug-in resolver to handle |
|
1092 * this properly. Currently, we simply return the System Identifier if |
|
1093 * present, and hope that it a usable URI or that our caller can |
|
1094 * map it to one. |
|
1095 * TODO: Resolve Public Identifiers... or consider changing function name. |
|
1096 * <p> |
|
1097 * If we find a relative URI |
|
1098 * reference, XML expects it to be resolved in terms of the base URI |
|
1099 * of the document. The DOM doesn't do that for us, and it isn't |
|
1100 * entirely clear whether that should be done here; currently that's |
|
1101 * pushed up to a higher levelof our application. (Note that DOM Level |
|
1102 * 1 didn't store the document's base URI.) |
|
1103 * TODO: Consider resolving Relative URIs. |
|
1104 * <p> |
|
1105 * (The DOM's statement that "An XML processor may choose to |
|
1106 * completely expand entities before the structure model is passed |
|
1107 * to the DOM" refers only to parsed entities, not unparsed, and hence |
|
1108 * doesn't affect this function.) |
|
1109 * |
|
1110 * @param name A string containing the Entity Name of the unparsed |
|
1111 * entity. |
|
1112 * @param doc Document node for the document to be searched. |
|
1113 * |
|
1114 * @return String containing the URI of the Unparsed Entity, or an |
|
1115 * empty string if no such entity exists. |
|
1116 */ |
|
1117 public String getUnparsedEntityURI(String name, Document doc) |
|
1118 { |
|
1119 |
|
1120 String url = ""; |
|
1121 DocumentType doctype = doc.getDoctype(); |
|
1122 |
|
1123 if (null != doctype) |
|
1124 { |
|
1125 NamedNodeMap entities = doctype.getEntities(); |
|
1126 if(null == entities) |
|
1127 return url; |
|
1128 Entity entity = (Entity) entities.getNamedItem(name); |
|
1129 if(null == entity) |
|
1130 return url; |
|
1131 |
|
1132 String notationName = entity.getNotationName(); |
|
1133 |
|
1134 if (null != notationName) // then it's unparsed |
|
1135 { |
|
1136 // The draft says: "The XSLT processor may use the public |
|
1137 // identifier to generate a URI for the entity instead of the URI |
|
1138 // specified in the system identifier. If the XSLT processor does |
|
1139 // not use the public identifier to generate the URI, it must use |
|
1140 // the system identifier; if the system identifier is a relative |
|
1141 // URI, it must be resolved into an absolute URI using the URI of |
|
1142 // the resource containing the entity declaration as the base |
|
1143 // URI [RFC2396]." |
|
1144 // So I'm falling a bit short here. |
|
1145 url = entity.getSystemId(); |
|
1146 |
|
1147 if (null == url) |
|
1148 { |
|
1149 url = entity.getPublicId(); |
|
1150 } |
|
1151 else |
|
1152 { |
|
1153 // This should be resolved to an absolute URL, but that's hard |
|
1154 // to do from here. |
|
1155 } |
|
1156 } |
|
1157 } |
|
1158 |
|
1159 return url; |
|
1160 } |
|
1161 |
|
1162 /** |
|
1163 * Support for getParentOfNode; walks a DOM tree until it finds |
|
1164 * the Element which owns the Attr. This is hugely expensive, and |
|
1165 * if at all possible you should use the DOM Level 2 Attr.ownerElement() |
|
1166 * method instead. |
|
1167 * <p> |
|
1168 * The DOM Level 1 developers expected that folks would keep track |
|
1169 * of the last Element they'd seen and could recover the info from |
|
1170 * that source. Obviously that doesn't work very well if the only |
|
1171 * information you've been presented with is the Attr. The DOM Level 2 |
|
1172 * getOwnerElement() method fixes that, but only for Level 2 and |
|
1173 * later DOMs. |
|
1174 * |
|
1175 * @param elem Element whose subtree is to be searched for this Attr |
|
1176 * @param attr Attr whose owner is to be located. |
|
1177 * |
|
1178 * @return the first Element whose attribute list includes the provided |
|
1179 * attr. In modern DOMs, this will also be the only such Element. (Early |
|
1180 * DOMs had some hope that Attrs might be sharable, but this idea has |
|
1181 * been abandoned.) |
|
1182 */ |
|
1183 private static Node locateAttrParent(Element elem, Node attr) |
|
1184 { |
|
1185 |
|
1186 Node parent = null; |
|
1187 |
|
1188 // This should only be called for Level 1 DOMs, so we don't have to |
|
1189 // worry about namespace issues. In later levels, it's possible |
|
1190 // for a DOM to have two Attrs with the same NodeName but |
|
1191 // different namespaces, and we'd need to get getAttributeNodeNS... |
|
1192 // but later levels also have Attr.getOwnerElement. |
|
1193 Attr check=elem.getAttributeNode(attr.getNodeName()); |
|
1194 if(check==attr) |
|
1195 parent = elem; |
|
1196 |
|
1197 if (null == parent) |
|
1198 { |
|
1199 for (Node node = elem.getFirstChild(); null != node; |
|
1200 node = node.getNextSibling()) |
|
1201 { |
|
1202 if (Node.ELEMENT_NODE == node.getNodeType()) |
|
1203 { |
|
1204 parent = locateAttrParent((Element) node, attr); |
|
1205 |
|
1206 if (null != parent) |
|
1207 break; |
|
1208 } |
|
1209 } |
|
1210 } |
|
1211 |
|
1212 return parent; |
|
1213 } |
|
1214 |
|
1215 /** |
|
1216 * The factory object used for creating nodes |
|
1217 * in the result tree. |
|
1218 */ |
|
1219 protected Document m_DOMFactory = null; |
|
1220 |
|
1221 /** |
|
1222 * Store the factory object required to create DOM nodes |
|
1223 * in the result tree. In fact, that's just the result tree's |
|
1224 * Document node... |
|
1225 * |
|
1226 * @param domFactory The DOM Document Node within whose context |
|
1227 * the result tree will be built. |
|
1228 */ |
|
1229 public void setDOMFactory(Document domFactory) |
|
1230 { |
|
1231 this.m_DOMFactory = domFactory; |
|
1232 } |
|
1233 |
|
1234 /** |
|
1235 * Retrieve the factory object required to create DOM nodes |
|
1236 * in the result tree. |
|
1237 * |
|
1238 * @return The result tree's DOM Document Node. |
|
1239 */ |
|
1240 public Document getDOMFactory() |
|
1241 { |
|
1242 |
|
1243 if (null == this.m_DOMFactory) |
|
1244 { |
|
1245 this.m_DOMFactory = createDocument(); |
|
1246 } |
|
1247 |
|
1248 return this.m_DOMFactory; |
|
1249 } |
|
1250 |
|
1251 /** |
|
1252 * Get the textual contents of the node. See |
|
1253 * getNodeData(Node,FastStringBuffer) for discussion of how |
|
1254 * whitespace nodes are handled. |
|
1255 * |
|
1256 * @param node DOM Node to be examined |
|
1257 * @return String containing a concatenation of all the |
|
1258 * textual content within that node. |
|
1259 * @see #getNodeData(Node,FastStringBuffer) |
|
1260 * |
|
1261 */ |
|
1262 public static String getNodeData(Node node) |
|
1263 { |
|
1264 |
|
1265 FastStringBuffer buf = StringBufferPool.get(); |
|
1266 String s; |
|
1267 |
|
1268 try |
|
1269 { |
|
1270 getNodeData(node, buf); |
|
1271 |
|
1272 s = (buf.length() > 0) ? buf.toString() : ""; |
|
1273 } |
|
1274 finally |
|
1275 { |
|
1276 StringBufferPool.free(buf); |
|
1277 } |
|
1278 |
|
1279 return s; |
|
1280 } |
|
1281 |
|
1282 /** |
|
1283 * Retrieve the text content of a DOM subtree, appending it into a |
|
1284 * user-supplied FastStringBuffer object. Note that attributes are |
|
1285 * not considered part of the content of an element. |
|
1286 * <p> |
|
1287 * There are open questions regarding whitespace stripping. |
|
1288 * Currently we make no special effort in that regard, since the standard |
|
1289 * DOM doesn't yet provide DTD-based information to distinguish |
|
1290 * whitespace-in-element-context from genuine #PCDATA. Note that we |
|
1291 * should probably also consider xml:space if/when we address this. |
|
1292 * DOM Level 3 may solve the problem for us. |
|
1293 * |
|
1294 * @param node Node whose subtree is to be walked, gathering the |
|
1295 * contents of all Text or CDATASection nodes. |
|
1296 * @param buf FastStringBuffer into which the contents of the text |
|
1297 * nodes are to be concatenated. |
|
1298 */ |
|
1299 public static void getNodeData(Node node, FastStringBuffer buf) |
|
1300 { |
|
1301 |
|
1302 switch (node.getNodeType()) |
|
1303 { |
|
1304 case Node.DOCUMENT_FRAGMENT_NODE : |
|
1305 case Node.DOCUMENT_NODE : |
|
1306 case Node.ELEMENT_NODE : |
|
1307 { |
|
1308 for (Node child = node.getFirstChild(); null != child; |
|
1309 child = child.getNextSibling()) |
|
1310 { |
|
1311 getNodeData(child, buf); |
|
1312 } |
|
1313 } |
|
1314 break; |
|
1315 case Node.TEXT_NODE : |
|
1316 case Node.CDATA_SECTION_NODE : |
|
1317 buf.append(node.getNodeValue()); |
|
1318 break; |
|
1319 case Node.ATTRIBUTE_NODE : |
|
1320 buf.append(node.getNodeValue()); |
|
1321 break; |
|
1322 case Node.PROCESSING_INSTRUCTION_NODE : |
|
1323 // warning(XPATHErrorResources.WG_PARSING_AND_PREPARING); |
|
1324 break; |
|
1325 default : |
|
1326 // ignore |
|
1327 break; |
|
1328 } |
|
1329 } |
|
1330 } |
|