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