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 &lt;= 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 }