jaxp/src/java.xml/share/classes/com/sun/org/apache/xml/internal/utils/DOMHelper.java
changeset 45505 ca0e16b2d5d6
parent 45504 ea7475564d07
parent 45495 8f5dd0fb0a6d
child 45506 790c716da86b
equal deleted inserted replaced
45504:ea7475564d07 45505:ca0e16b2d5d6
     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 }