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