KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > xml > utils > DOMHelper


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 /*
17  * $Id: DOMHelper.java,v 1.6 2004/02/17 04:21:14 minchau Exp $
18  */

19 package org.apache.xml.utils;
20
21 import java.util.Hashtable JavaDoc;
22 import java.util.Vector JavaDoc;
23
24 import javax.xml.parsers.DocumentBuilder JavaDoc;
25 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
26 import javax.xml.parsers.ParserConfigurationException JavaDoc;
27
28 import org.apache.xml.dtm.ref.DTMNodeProxy;
29 import org.apache.xml.res.XMLErrorResources;
30 import org.apache.xml.res.XMLMessages;
31
32 import org.w3c.dom.Attr JavaDoc;
33 import org.w3c.dom.DOMImplementation JavaDoc;
34 import org.w3c.dom.Document JavaDoc;
35 import org.w3c.dom.DocumentType JavaDoc;
36 import org.w3c.dom.Element JavaDoc;
37 import org.w3c.dom.Entity JavaDoc;
38 import org.w3c.dom.NamedNodeMap JavaDoc;
39 import org.w3c.dom.Node JavaDoc;
40 import org.w3c.dom.Text JavaDoc;
41
42 /**
43  * @deprecated Since the introduction of the DTM, this class will be removed.
44  * This class provides a front-end to DOM implementations, providing
45  * a number of utility functions that either aren't yet standardized
46  * by the DOM spec or that are defined in optional DOM modules and
47  * hence may not be present in all DOMs.
48  */

49 public class DOMHelper
50 {
51
52   /**
53    * DOM Level 1 did not have a standard mechanism for creating a new
54    * Document object. This function provides a DOM-implementation-independent
55    * abstraction for that for that concept. It's typically used when
56    * outputting a new DOM as the result of an operation.
57    * <p>
58    * TODO: This isn't directly compatable with DOM Level 2.
59    * The Level 2 createDocument call also creates the root
60    * element, and thus requires that you know what that element will be
61    * before creating the Document. We should think about whether we want
62    * to change this code, and the callers, so we can use the DOM's own
63    * method. (It's also possible that DOM Level 3 may relax this
64    * sequence, but you may give up some intelligence in the DOM by
65    * doing so; the intent was that knowing the document type and root
66    * element might let the DOM automatically switch to a specialized
67    * subclass for particular kinds of documents.)
68    *
69    * @return The newly created DOM Document object, with no children, or
70    * null if we can't find a DOM implementation that permits creating
71    * new empty Documents.
72    */

73   public static Document JavaDoc createDocument()
74   {
75
76     try
77     {
78
79       // Use an implementation of the JAVA API for XML Parsing 1.0 to
80
// create a DOM Document node to contain the result.
81
DocumentBuilderFactory JavaDoc dfactory = DocumentBuilderFactory.newInstance();
82
83       dfactory.setNamespaceAware(true);
84       dfactory.setValidating(true);
85
86       DocumentBuilder JavaDoc docBuilder = dfactory.newDocumentBuilder();
87       Document JavaDoc outNode = docBuilder.newDocument();
88
89       return outNode;
90     }
91     catch (ParserConfigurationException JavaDoc pce)
92     {
93       throw new RuntimeException JavaDoc(
94         XMLMessages.createXMLMessage(
95           XMLErrorResources.ER_CREATEDOCUMENT_NOT_SUPPORTED, null)); //"createDocument() not supported in XPathContext!");
96

97       // return null;
98
}
99   }
100
101   /**
102    * Tells, through the combination of the default-space attribute
103    * on xsl:stylesheet, xsl:strip-space, xsl:preserve-space, and the
104    * xml:space attribute, whether or not extra whitespace should be stripped
105    * from the node. Literal elements from template elements should
106    * <em>not</em> be tested with this function.
107    * @param textNode A text node from the source tree.
108    * @return true if the text node should be stripped of extra whitespace.
109    *
110    * @throws javax.xml.transform.TransformerException
111    * @xsl.usage advanced
112    */

113   public boolean shouldStripSourceNode(Node JavaDoc textNode)
114           throws javax.xml.transform.TransformerException JavaDoc
115   {
116
117     // return (null == m_envSupport) ? false : m_envSupport.shouldStripSourceNode(textNode);
118
return false;
119   }
120
121   /**
122    * Supports the XPath function GenerateID by returning a unique
123    * identifier string for any given DOM Node.
124    * <p>
125    * Warning: The base implementation uses the Node object's hashCode(),
126    * which is NOT guaranteed to be unique. If that method hasn't been
127    * overridden in this DOM ipmlementation, most Java implementions will
128    * derive it from the object's address and should be OK... but if
129    * your DOM uses a different definition of hashCode (eg hashing the
130    * contents of the subtree), or if your DOM may have multiple objects
131    * that represent a single Node in the data structure (eg via proxying),
132    * you may need to find another way to assign a unique identifier.
133    * <p>
134    * Also, be aware that if nodes are destroyed and recreated, there is
135    * an open issue regarding whether an ID may be reused. Currently
136    * we're assuming that the input document is stable for the duration
137    * of the XPath/XSLT operation, so this shouldn't arise in this context.
138    * <p>
139    * (DOM Level 3 is investigating providing a unique node "key", but
140    * that won't help Level 1 and Level 2 implementations.)
141    *
142    * @param node whose identifier you want to obtain
143    *
144    * @return a string which should be different for every Node object.
145    */

146   public String JavaDoc getUniqueID(Node JavaDoc node)
147   {
148     return "N" + Integer.toHexString(node.hashCode()).toUpperCase();
149   }
150
151   /**
152    * Figure out whether node2 should be considered as being later
153    * in the document than node1, in Document Order as defined
154    * by the XPath model. This may not agree with the ordering defined
155    * by other XML applications.
156    * <p>
157    * There are some cases where ordering isn't defined, and neither are
158    * the results of this function -- though we'll generally return true.
159    *
160    * TODO: Make sure this does the right thing with attribute nodes!!!
161    *
162    * @param node1 DOM Node to perform position comparison on.
163    * @param node2 DOM Node to perform position comparison on .
164    *
165    * @return false if node2 comes before node1, otherwise return true.
166    * You can think of this as
167    * <code>(node1.documentOrderPosition &lt;= node2.documentOrderPosition)</code>.
168    */

169   public static boolean isNodeAfter(Node JavaDoc node1, Node JavaDoc node2)
170   {
171     if (node1 == node2 || isNodeTheSame(node1, node2))
172       return true;
173
174         // Default return value, if there is no defined ordering
175
boolean isNodeAfter = true;
176         
177     Node JavaDoc parent1 = getParentOfNode(node1);
178     Node JavaDoc parent2 = getParentOfNode(node2);
179
180     // Optimize for most common case
181
if (parent1 == parent2 || isNodeTheSame(parent1, parent2)) // then we know they are siblings
182
{
183       if (null != parent1)
184         isNodeAfter = isNodeAfterSibling(parent1, node1, node2);
185       else
186       {
187                   // If both parents are null, ordering is not defined.
188
// We're returning a value in lieu of throwing an exception.
189
// Not a case we expect to arise in XPath, but beware if you
190
// try to reuse this method.
191

192                   // We can just fall through in this case, which allows us
193
// to hit the debugging code at the end of the function.
194
//return isNodeAfter;
195
}
196     }
197     else
198     {
199
200       // General strategy: Figure out the lengths of the two
201
// ancestor chains, reconcile the lengths, and look for
202
// the lowest common ancestor. If that ancestor is one of
203
// the nodes being compared, it comes before the other.
204
// Otherwise perform a sibling compare.
205
//
206
// NOTE: If no common ancestor is found, ordering is undefined
207
// and we return the default value of isNodeAfter.
208

209       // Count parents in each ancestor chain
210
int nParents1 = 2, nParents2 = 2; // include node & parent obtained above
211

212       while (parent1 != null)
213       {
214         nParents1++;
215
216         parent1 = getParentOfNode(parent1);
217       }
218
219       while (parent2 != null)
220       {
221         nParents2++;
222
223         parent2 = getParentOfNode(parent2);
224       }
225
226           // Initially assume scan for common ancestor starts with
227
// the input nodes.
228
Node JavaDoc startNode1 = node1, startNode2 = node2;
229
230       // If one ancestor chain is longer, adjust its start point
231
// so we're comparing at the same depths
232
if (nParents1 < nParents2)
233       {
234         // Adjust startNode2 to depth of startNode1
235
int adjust = nParents2 - nParents1;
236
237         for (int i = 0; i < adjust; i++)
238         {
239           startNode2 = getParentOfNode(startNode2);
240         }
241       }
242       else if (nParents1 > nParents2)
243       {
244         // adjust startNode1 to depth of startNode2
245
int adjust = nParents1 - nParents2;
246
247         for (int i = 0; i < adjust; i++)
248         {
249           startNode1 = getParentOfNode(startNode1);
250         }
251       }
252
253       Node JavaDoc prevChild1 = null, prevChild2 = null; // so we can "back up"
254

255       // Loop up the ancestor chain looking for common parent
256
while (null != startNode1)
257       {
258         if (startNode1 == startNode2 || isNodeTheSame(startNode1, startNode2)) // common parent?
259
{
260           if (null == prevChild1) // first time in loop?
261
{
262
263             // Edge condition: one is the ancestor of the other.
264
isNodeAfter = (nParents1 < nParents2) ? true : false;
265
266             break; // from while loop
267
}
268           else
269           {
270                         // Compare ancestors below lowest-common as siblings
271
isNodeAfter = isNodeAfterSibling(startNode1, prevChild1,
272                                              prevChild2);
273
274             break; // from while loop
275
}
276         } // end if(startNode1 == startNode2)
277

278                 // Move up one level and try again
279
prevChild1 = startNode1;
280         startNode1 = getParentOfNode(startNode1);
281         prevChild2 = startNode2;
282         startNode2 = getParentOfNode(startNode2);
283       } // end while(parents exist to examine)
284
} // end big else (not immediate siblings)
285

286         // WARNING: The following diagnostic won't report the early
287
// "same node" case. Fix if/when needed.
288

289     /* -- please do not remove... very useful for diagnostics --
290     System.out.println("node1 = "+node1.getNodeName()+"("+node1.getNodeType()+")"+
291     ", node2 = "+node2.getNodeName()
292     +"("+node2.getNodeType()+")"+
293     ", isNodeAfter = "+isNodeAfter); */

294     return isNodeAfter;
295   } // end isNodeAfter(Node node1, Node node2)
296

297   /**
298    * Use DTMNodeProxy to determine whether two nodes are the same.
299    *
300    * @param node1 The first DOM node to compare.
301    * @param node2 The second DOM node to compare.
302    * @return true if the two nodes are the same.
303    */

304   public static boolean isNodeTheSame(Node JavaDoc node1, Node JavaDoc node2)
305   {
306     if (node1 instanceof DTMNodeProxy && node2 instanceof DTMNodeProxy)
307       return ((DTMNodeProxy)node1).equals((DTMNodeProxy)node2);
308     else
309       return (node1 == node2);
310   }
311
312   /**
313    * Figure out if child2 is after child1 in document order.
314    * <p>
315    * Warning: Some aspects of "document order" are not well defined.
316    * For example, the order of attributes is considered
317    * meaningless in XML, and the order reported by our model will
318    * be consistant for a given invocation but may not
319    * match that of either the source file or the serialized output.
320    *
321    * @param parent Must be the parent of both child1 and child2.
322    * @param child1 Must be the child of parent and not equal to child2.
323    * @param child2 Must be the child of parent and not equal to child1.
324    * @return true if child 2 is after child1 in document order.
325    */

326   private static boolean isNodeAfterSibling(Node JavaDoc parent, Node JavaDoc child1,
327                                             Node JavaDoc child2)
328   {
329
330     boolean isNodeAfterSibling = false;
331     short child1type = child1.getNodeType();
332     short child2type = child2.getNodeType();
333
334     if ((Node.ATTRIBUTE_NODE != child1type)
335             && (Node.ATTRIBUTE_NODE == child2type))
336     {
337
338       // always sort attributes before non-attributes.
339
isNodeAfterSibling = false;
340     }
341     else if ((Node.ATTRIBUTE_NODE == child1type)
342              && (Node.ATTRIBUTE_NODE != child2type))
343     {
344
345       // always sort attributes before non-attributes.
346
isNodeAfterSibling = true;
347     }
348     else if (Node.ATTRIBUTE_NODE == child1type)
349     {
350       NamedNodeMap JavaDoc children = parent.getAttributes();
351       int nNodes = children.getLength();
352       boolean found1 = false, found2 = false;
353
354           // Count from the start until we find one or the other.
355
for (int i = 0; i < nNodes; i++)
356       {
357         Node JavaDoc child = children.item(i);
358
359         if (child1 == child || isNodeTheSame(child1, child))
360         {
361           if (found2)
362           {
363             isNodeAfterSibling = false;
364
365             break;
366           }
367
368           found1 = true;
369         }
370         else if (child2 == child || isNodeTheSame(child2, child))
371         {
372           if (found1)
373           {
374             isNodeAfterSibling = true;
375
376             break;
377           }
378
379           found2 = true;
380         }
381       }
382     }
383     else
384     {
385                 // TODO: Check performance of alternate solution:
386
// There are two choices here: Count from the start of
387
// the document until we find one or the other, or count
388
// from one until we find or fail to find the other.
389
// Either can wind up scanning all the siblings in the worst
390
// case, which on a wide document can be a lot of work but
391
// is more typically is a short list.
392
// Scanning from the start involves two tests per iteration,
393
// but it isn't clear that scanning from the middle doesn't
394
// yield more iterations on average.
395
// We should run some testcases.
396
Node JavaDoc child = parent.getFirstChild();
397       boolean found1 = false, found2 = false;
398
399       while (null != child)
400       {
401
402         // Node child = children.item(i);
403
if (child1 == child || isNodeTheSame(child1, child))
404         {
405           if (found2)
406           {
407             isNodeAfterSibling = false;
408
409             break;
410           }
411
412           found1 = true;
413         }
414         else if (child2 == child || isNodeTheSame(child2, child))
415         {
416           if (found1)
417           {
418             isNodeAfterSibling = true;
419
420             break;
421           }
422
423           found2 = true;
424         }
425
426         child = child.getNextSibling();
427       }
428     }
429
430     return isNodeAfterSibling;
431   } // end isNodeAfterSibling(Node parent, Node child1, Node child2)
432

433   //==========================================================
434
// SECTION: Namespace resolution
435
//==========================================================
436

437   /**
438    * Get the depth level of this node in the tree (equals 1 for
439    * a parentless node).
440    *
441    * @param n Node to be examined.
442    * @return the number of ancestors, plus one
443    * @xsl.usage internal
444    */

445   public short getLevel(Node JavaDoc n)
446   {
447
448     short level = 1;
449
450     while (null != (n = getParentOfNode(n)))
451     {
452       level++;
453     }
454
455     return level;
456   }
457
458   /**
459    * Given an XML Namespace prefix and a context in which the prefix
460    * is to be evaluated, return the Namespace Name this prefix was
461    * bound to. Note that DOM Level 3 is expected to provide a version of
462    * this which deals with the DOM's "early binding" behavior.
463    *
464    * Default handling:
465    *
466    * @param prefix String containing namespace prefix to be resolved,
467    * without the ':' which separates it from the localname when used
468    * in a Node Name. The empty sting signifies the default namespace
469    * at this point in the document.
470    * @param namespaceContext Element which provides context for resolution.
471    * (We could extend this to work for other nodes by first seeking their
472    * nearest Element ancestor.)
473    *
474    * @return a String containing the Namespace URI which this prefix
475    * represents in the specified context.
476    */

477   public String JavaDoc getNamespaceForPrefix(String JavaDoc prefix, Element JavaDoc namespaceContext)
478   {
479
480     int type;
481     Node JavaDoc parent = namespaceContext;
482     String JavaDoc namespace = null;
483
484     if (prefix.equals("xml"))
485     {
486       namespace = QName.S_XMLNAMESPACEURI; // Hardcoded, per Namespace spec
487
}
488         else if(prefix.equals("xmlns"))
489     {
490           // Hardcoded in the DOM spec, expected to be adopted by
491
// Namespace spec. NOTE: Namespace declarations _must_ use
492
// the xmlns: prefix; other prefixes declared as belonging
493
// to this namespace will not be recognized and should
494
// probably be rejected by parsers as erroneous declarations.
495
namespace = "http://www.w3.org/2000/xmlns/";
496     }
497     else
498     {
499           // Attribute name for this prefix's declaration
500
String JavaDoc declname=(prefix=="")
501                         ? "xmlns"
502                         : "xmlns:"+prefix;
503                                            
504           // Scan until we run out of Elements or have resolved the namespace
505
while ((null != parent) && (null == namespace)
506              && (((type = parent.getNodeType()) == Node.ELEMENT_NODE)
507                  || (type == Node.ENTITY_REFERENCE_NODE)))
508       {
509         if (type == Node.ELEMENT_NODE)
510         {
511                         
512                         // Look for the appropriate Namespace Declaration attribute,
513
// either "xmlns:prefix" or (if prefix is "") "xmlns".
514
// TODO: This does not handle "implicit declarations"
515
// which may be created when the DOM is edited. DOM Level
516
// 3 will define how those should be interpreted. But
517
// this issue won't arise in freshly-parsed DOMs.
518

519                 // NOTE: declname is set earlier, outside the loop.
520
Attr JavaDoc attr=((Element JavaDoc)parent).getAttributeNode(declname);
521                         if(attr!=null)
522                         {
523                 namespace = attr.getNodeValue();
524                 break;
525                         }
526                 }
527
528         parent = getParentOfNode(parent);
529       }
530     }
531
532     return namespace;
533   }
534
535   /**
536    * An experiment for the moment.
537    */

538   Hashtable JavaDoc m_NSInfos = new Hashtable JavaDoc();
539
540   /** Object to put into the m_NSInfos table that tells that a node has not been
541    * processed, but has xmlns namespace decls. */

542   protected static final NSInfo m_NSInfoUnProcWithXMLNS = new NSInfo(false,
543                                                             true);
544
545   /** Object to put into the m_NSInfos table that tells that a node has not been
546    * processed, but has no xmlns namespace decls. */

547   protected static final NSInfo m_NSInfoUnProcWithoutXMLNS = new NSInfo(false,
548                                                                false);
549
550   /** Object to put into the m_NSInfos table that tells that a node has not been
551    * processed, and has no xmlns namespace decls, and has no ancestor decls. */

552   protected static final NSInfo m_NSInfoUnProcNoAncestorXMLNS =
553     new NSInfo(false, false, NSInfo.ANCESTORNOXMLNS);
554
555   /** Object to put into the m_NSInfos table that tells that a node has been
556    * processed, and has xmlns namespace decls. */

557   protected static final NSInfo m_NSInfoNullWithXMLNS = new NSInfo(true,
558                                                           true);
559
560   /** Object to put into the m_NSInfos table that tells that a node has been
561    * processed, and has no xmlns namespace decls. */

562   protected static final NSInfo m_NSInfoNullWithoutXMLNS = new NSInfo(true,
563                                                              false);
564
565   /** Object to put into the m_NSInfos table that tells that a node has been
566    * processed, and has no xmlns namespace decls. and has no ancestor decls. */

567   protected static final NSInfo m_NSInfoNullNoAncestorXMLNS =
568     new NSInfo(true, false, NSInfo.ANCESTORNOXMLNS);
569
570   /** Vector of node (odd indexes) and NSInfos (even indexes) that tell if
571    * the given node is a candidate for ancestor namespace processing. */

572   protected Vector JavaDoc m_candidateNoAncestorXMLNS = new Vector JavaDoc();
573
574   /**
575    * Returns the namespace of the given node. Differs from simply getting
576    * the node's prefix and using getNamespaceForPrefix in that it attempts
577    * to cache some of the data in NSINFO objects, to avoid repeated lookup.
578    * TODO: Should we consider moving that logic into getNamespaceForPrefix?
579    *
580    * @param n Node to be examined.
581    *
582    * @return String containing the Namespace Name (uri) for this node.
583    * Note that this is undefined for any nodes other than Elements and
584    * Attributes.
585    */

586   public String JavaDoc getNamespaceOfNode(Node JavaDoc n)
587   {
588
589     String JavaDoc namespaceOfPrefix;
590     boolean hasProcessedNS;
591     NSInfo nsInfo;
592     short ntype = n.getNodeType();
593
594     if (Node.ATTRIBUTE_NODE != ntype)
595     {
596       Object JavaDoc nsObj = m_NSInfos.get(n); // return value
597

598       nsInfo = (nsObj == null) ? null : (NSInfo) nsObj;
599       hasProcessedNS = (nsInfo == null) ? false : nsInfo.m_hasProcessedNS;
600     }
601     else
602     {
603       hasProcessedNS = false;
604       nsInfo = null;
605     }
606
607     if (hasProcessedNS)
608     {
609       namespaceOfPrefix = nsInfo.m_namespace;
610     }
611     else
612     {
613       namespaceOfPrefix = null;
614
615       String JavaDoc nodeName = n.getNodeName();
616       int indexOfNSSep = nodeName.indexOf(':');
617       String JavaDoc prefix;
618
619       if (Node.ATTRIBUTE_NODE == ntype)
620       {
621         if (indexOfNSSep > 0)
622         {
623           prefix = nodeName.substring(0, indexOfNSSep);
624         }
625         else
626         {
627
628           // Attributes don't use the default namespace, so if
629
// there isn't a prefix, we're done.
630
return namespaceOfPrefix;
631         }
632       }
633       else
634       {
635         prefix = (indexOfNSSep >= 0)
636                  ? nodeName.substring(0, indexOfNSSep) : "";
637       }
638
639       boolean ancestorsHaveXMLNS = false;
640       boolean nHasXMLNS = false;
641
642       if (prefix.equals("xml"))
643       {
644         namespaceOfPrefix = QName.S_XMLNAMESPACEURI;
645       }
646       else
647       {
648         int parentType;
649         Node JavaDoc parent = n;
650
651         while ((null != parent) && (null == namespaceOfPrefix))
652         {
653           if ((null != nsInfo)
654                   && (nsInfo.m_ancestorHasXMLNSAttrs
655                       == NSInfo.ANCESTORNOXMLNS))
656           {
657             break;
658           }
659
660           parentType = parent.getNodeType();
661
662           if ((null == nsInfo) || nsInfo.m_hasXMLNSAttrs)
663           {
664             boolean elementHasXMLNS = false;
665
666             if (parentType == Node.ELEMENT_NODE)
667             {
668               NamedNodeMap JavaDoc nnm = parent.getAttributes();
669
670               for (int i = 0; i < nnm.getLength(); i++)
671               {
672                 Node JavaDoc attr = nnm.item(i);
673                 String JavaDoc aname = attr.getNodeName();
674
675                 if (aname.charAt(0) == 'x')
676                 {
677                   boolean isPrefix = aname.startsWith("xmlns:");
678
679                   if (aname.equals("xmlns") || isPrefix)
680                   {
681                     if (n == parent)
682                       nHasXMLNS = true;
683
684                     elementHasXMLNS = true;
685                     ancestorsHaveXMLNS = true;
686
687                     String JavaDoc p = isPrefix ? aname.substring(6) : "";
688
689                     if (p.equals(prefix))
690                     {
691                       namespaceOfPrefix = attr.getNodeValue();
692
693                       break;
694                     }
695                   }
696                 }
697               }
698             }
699
700             if ((Node.ATTRIBUTE_NODE != parentType) && (null == nsInfo)
701                     && (n != parent))
702             {
703               nsInfo = elementHasXMLNS
704                        ? m_NSInfoUnProcWithXMLNS : m_NSInfoUnProcWithoutXMLNS;
705
706               m_NSInfos.put(parent, nsInfo);
707             }
708           }
709
710           if (Node.ATTRIBUTE_NODE == parentType)
711           {
712             parent = getParentOfNode(parent);
713           }
714           else
715           {
716             m_candidateNoAncestorXMLNS.addElement(parent);
717             m_candidateNoAncestorXMLNS.addElement(nsInfo);
718
719             parent = parent.getParentNode();
720           }
721
722           if (null != parent)
723           {
724             Object JavaDoc nsObj = m_NSInfos.get(parent); // return value
725

726             nsInfo = (nsObj == null) ? null : (NSInfo) nsObj;
727           }
728         }
729
730         int nCandidates = m_candidateNoAncestorXMLNS.size();
731
732         if (nCandidates > 0)
733         {
734           if ((false == ancestorsHaveXMLNS) && (null == parent))
735           {
736             for (int i = 0; i < nCandidates; i += 2)
737             {
738               Object JavaDoc candidateInfo = m_candidateNoAncestorXMLNS.elementAt(i
739                                        + 1);
740
741               if (candidateInfo == m_NSInfoUnProcWithoutXMLNS)
742               {
743                 m_NSInfos.put(m_candidateNoAncestorXMLNS.elementAt(i),
744                               m_NSInfoUnProcNoAncestorXMLNS);
745               }
746               else if (candidateInfo == m_NSInfoNullWithoutXMLNS)
747               {
748                 m_NSInfos.put(m_candidateNoAncestorXMLNS.elementAt(i),
749                               m_NSInfoNullNoAncestorXMLNS);
750               }
751             }
752           }
753
754           m_candidateNoAncestorXMLNS.removeAllElements();
755         }
756       }
757
758       if (Node.ATTRIBUTE_NODE != ntype)
759       {
760         if (null == namespaceOfPrefix)
761         {
762           if (ancestorsHaveXMLNS)
763           {
764             if (nHasXMLNS)
765               m_NSInfos.put(n, m_NSInfoNullWithXMLNS);
766             else
767               m_NSInfos.put(n, m_NSInfoNullWithoutXMLNS);
768           }
769           else
770           {
771             m_NSInfos.put(n, m_NSInfoNullNoAncestorXMLNS);
772           }
773         }
774         else
775         {
776           m_NSInfos.put(n, new NSInfo(namespaceOfPrefix, nHasXMLNS));
777         }
778       }
779     }
780
781     return namespaceOfPrefix;
782   }
783
784   /**
785    * Returns the local name of the given node. If the node's name begins
786    * with a namespace prefix, this is the part after the colon; otherwise
787    * it's the full node name.
788    *
789    * @param n the node to be examined.
790    *
791    * @return String containing the Local Name
792    */

793   public String JavaDoc getLocalNameOfNode(Node JavaDoc n)
794   {
795
796     String JavaDoc qname = n.getNodeName();
797     int index = qname.indexOf(':');
798
799     return (index < 0) ? qname : qname.substring(index + 1);
800   }
801
802   /**
803    * Returns the element name with the namespace prefix (if any) replaced
804    * by the Namespace URI it was bound to. This is not a standard
805    * representation of a node name, but it allows convenient
806    * single-string comparison of the "universal" names of two nodes.
807    *
808    * @param elem Element to be examined.
809    *
810    * @return String in the form "namespaceURI:localname" if the node
811    * belongs to a namespace, or simply "localname" if it doesn't.
812    * @see #getExpandedAttributeName
813    */

814   public String JavaDoc getExpandedElementName(Element JavaDoc elem)
815   {
816
817     String JavaDoc namespace = getNamespaceOfNode(elem);
818
819     return (null != namespace)
820            ? namespace + ":" + getLocalNameOfNode(elem)
821            : getLocalNameOfNode(elem);
822   }
823
824   /**
825    * Returns the attribute name with the namespace prefix (if any) replaced
826    * by the Namespace URI it was bound to. This is not a standard
827    * representation of a node name, but it allows convenient
828    * single-string comparison of the "universal" names of two nodes.
829    *
830    * @param attr Attr to be examined
831    *
832    * @return String in the form "namespaceURI:localname" if the node
833    * belongs to a namespace, or simply "localname" if it doesn't.
834    * @see #getExpandedElementName
835    */

836   public String JavaDoc getExpandedAttributeName(Attr JavaDoc attr)
837   {
838
839     String JavaDoc namespace = getNamespaceOfNode(attr);
840
841     return (null != namespace)
842            ? namespace + ":" + getLocalNameOfNode(attr)
843            : getLocalNameOfNode(attr);
844   }
845
846   //==========================================================
847
// SECTION: DOM Helper Functions
848
//==========================================================
849

850   /**
851    * Tell if the node is ignorable whitespace. Note that this can
852    * be determined only in the context of a DTD or other Schema,
853    * and that DOM Level 2 has nostandardized DOM API which can
854    * return that information.
855    * @deprecated
856    *
857    * @param node Node to be examined
858    *
859    * @return CURRENTLY HARDCODED TO FALSE, but should return true if
860    * and only if the node is of type Text, contains only whitespace,
861    * and does not appear as part of the #PCDATA content of an element.
862    * (Note that determining this last may require allowing for
863    * Entity References.)
864    */

865   public boolean isIgnorableWhitespace(Text JavaDoc node)
866   {
867
868     boolean isIgnorable = false; // return value
869

870     // TODO: I can probably do something to figure out if this
871
// space is ignorable from just the information in
872
// the DOM tree.
873
// -- You need to be able to distinguish whitespace
874
// that is #PCDATA from whitespace that isn't. That requires
875
// DTD support, which won't be standardized until DOM Level 3.
876
return isIgnorable;
877   }
878
879   /**
880    * Get the first unparented node in the ancestor chain.
881    * @deprecated
882    *
883    * @param node Starting node, to specify which chain to chase
884    *
885    * @return the topmost ancestor.
886    */

887   public Node JavaDoc getRoot(Node JavaDoc node)
888   {
889
890     Node JavaDoc root = null;
891
892     while (node != null)
893     {
894       root = node;
895       node = getParentOfNode(node);
896     }
897
898     return root;
899   }
900
901   /**
902    * Get the root node of the document tree, regardless of
903    * whether or not the node passed in is a document node.
904    * <p>
905    * TODO: This doesn't handle DocumentFragments or "orphaned" subtrees
906    * -- it's currently returning ownerDocument even when the tree is
907    * not actually part of the main Document tree. We should either
908    * rewrite the description to say that it finds the Document node,
909    * or change the code to walk up the ancestor chain.
910
911    *
912    * @param n Node to be examined
913    *
914    * @return the Document node. Note that this is not the correct answer
915    * if n was (or was a child of) a DocumentFragment or an orphaned node,
916    * as can arise if the DOM has been edited rather than being generated
917    * by a parser.
918    */

919   public Node JavaDoc getRootNode(Node JavaDoc n)
920   {
921     int nt = n.getNodeType();
922     return ( (Node.DOCUMENT_NODE == nt) || (Node.DOCUMENT_FRAGMENT_NODE == nt) )
923            ? n : n.getOwnerDocument();
924   }
925
926   /**
927    * Test whether the given node is a namespace decl node. In DOM Level 2
928    * this can be done in a namespace-aware manner, but in Level 1 DOMs
929    * it has to be done by testing the node name.
930    *
931    * @param n Node to be examined.
932    *
933    * @return boolean -- true iff the node is an Attr whose name is
934    * "xmlns" or has the "xmlns:" prefix.
935    */

936   public boolean isNamespaceNode(Node JavaDoc n)
937   {
938
939     if (Node.ATTRIBUTE_NODE == n.getNodeType())
940     {
941       String JavaDoc attrName = n.getNodeName();
942
943       return (attrName.startsWith("xmlns:") || attrName.equals("xmlns"));
944     }
945
946     return false;
947   }
948
949   /**
950    * Obtain the XPath-model parent of a DOM node -- ownerElement for Attrs,
951    * parent for other nodes.
952    * <p>
953    * Background: The DOM believes that you must be your Parent's
954    * Child, and thus Attrs don't have parents. XPath said that Attrs
955    * do have their owning Element as their parent. This function
956    * bridges the difference, either by using the DOM Level 2 ownerElement
957    * function or by using a "silly and expensive function" in Level 1
958    * DOMs.
959    * <p>
960    * (There's some discussion of future DOMs generalizing ownerElement
961    * into ownerNode and making it work on all types of nodes. This
962    * still wouldn't help the users of Level 1 or Level 2 DOMs)
963    * <p>
964    *
965    * @param node Node whose XPath parent we want to obtain
966    *
967    * @return the parent of the node, or the ownerElement if it's an
968    * Attr node, or null if the node is an orphan.
969    *
970    * @throws RuntimeException if the Document has no root element.
971    * This can't arise if the Document was created
972    * via the DOM Level 2 factory methods, but is possible if other
973    * mechanisms were used to obtain it
974    */

975   public static Node JavaDoc getParentOfNode(Node JavaDoc node) throws RuntimeException JavaDoc
976   {
977     Node JavaDoc parent;
978     short nodeType = node.getNodeType();
979
980     if (Node.ATTRIBUTE_NODE == nodeType)
981     {
982       Document JavaDoc doc = node.getOwnerDocument();
983           /*
984       TBD:
985       if(null == doc)
986       {
987         throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CHILD_HAS_NO_OWNER_DOCUMENT, null));//"Attribute child does not have an owner document!");
988       }
989       */

990
991           // Given how expensive the tree walk may be, we should first ask
992
// whether this DOM can answer the question for us. The additional
993
// test does slow down Level 1 DOMs slightly. DOMHelper2, which
994
// is currently specialized for Xerces, assumes it can use the
995
// Level 2 solution. We might want to have an intermediate stage,
996
// which would assume DOM Level 2 but not assume Xerces.
997
//
998
// (Shouldn't have to check whether impl is null in a compliant DOM,
999
// but let's be paranoid for a moment...)
1000
DOMImplementation JavaDoc impl=doc.getImplementation();
1001          if(impl!=null && impl.hasFeature("Core","2.0"))
1002          {
1003                  parent=((Attr JavaDoc)node).getOwnerElement();
1004                  return parent;
1005          }
1006
1007          // DOM Level 1 solution, as fallback. Hugely expensive.
1008

1009      Element JavaDoc rootElem = doc.getDocumentElement();
1010
1011      if (null == rootElem)
1012      {
1013        throw new RuntimeException JavaDoc(
1014          XMLMessages.createXMLMessage(
1015            XMLErrorResources.ER_CHILD_HAS_NO_OWNER_DOCUMENT_ELEMENT,
1016            null)); //"Attribute child does not have an owner document element!");
1017
}
1018
1019      parent = locateAttrParent(rootElem, node);
1020
1021        }
1022    else
1023    {
1024      parent = node.getParentNode();
1025
1026      // if((Node.DOCUMENT_NODE != nodeType) && (null == parent))
1027
// {
1028
// throw new RuntimeException("Child does not have parent!");
1029
// }
1030
}
1031
1032    return parent;
1033  }
1034
1035  /**
1036   * Given an ID, return the element. This can work only if the document
1037   * is interpreted in the context of a DTD or Schema, since otherwise
1038   * we don't know which attributes are or aren't IDs.
1039   * <p>
1040   * Note that DOM Level 1 had no ability to retrieve this information.
1041   * DOM Level 2 introduced it but does not promise that it will be
1042   * supported in all DOMs; those which can't support it will always
1043   * return null.
1044   * <p>
1045   * TODO: getElementByID is currently unimplemented. Support DOM Level 2?
1046   *
1047   * @param id The unique identifier to be searched for.
1048   * @param doc The document to search within.
1049   * @return CURRENTLY HARDCODED TO NULL, but it should be:
1050   * The node which has this unique identifier, or null if there
1051   * is no such node or this DOM can't reliably recognize it.
1052   */

1053  public Element JavaDoc getElementByID(String JavaDoc id, Document JavaDoc doc)
1054  {
1055    return null;
1056  }
1057
1058  /**
1059   * The getUnparsedEntityURI function returns the URI of the unparsed
1060   * entity with the specified name in the same document as the context
1061   * node (see [3.3 Unparsed Entities]). It returns the empty string if
1062   * there is no such entity.
1063   * <p>
1064   * XML processors may choose to use the System Identifier (if one
1065   * is provided) to resolve the entity, rather than the URI in the
1066   * Public Identifier. The details are dependent on the processor, and
1067   * we would have to support some form of plug-in resolver to handle
1068   * this properly. Currently, we simply return the System Identifier if
1069   * present, and hope that it a usable URI or that our caller can
1070   * map it to one.
1071   * TODO: Resolve Public Identifiers... or consider changing function name.
1072   * <p>
1073   * If we find a relative URI
1074   * reference, XML expects it to be resolved in terms of the base URI
1075   * of the document. The DOM doesn't do that for us, and it isn't
1076   * entirely clear whether that should be done here; currently that's
1077   * pushed up to a higher levelof our application. (Note that DOM Level
1078   * 1 didn't store the document's base URI.)
1079   * TODO: Consider resolving Relative URIs.
1080   * <p>
1081   * (The DOM's statement that "An XML processor may choose to
1082   * completely expand entities before the structure model is passed
1083   * to the DOM" refers only to parsed entities, not unparsed, and hence
1084   * doesn't affect this function.)
1085   *
1086   * @param name A string containing the Entity Name of the unparsed
1087   * entity.
1088   * @param doc Document node for the document to be searched.
1089   *
1090   * @return String containing the URI of the Unparsed Entity, or an
1091   * empty string if no such entity exists.
1092   */

1093  public String JavaDoc getUnparsedEntityURI(String JavaDoc name, Document JavaDoc doc)
1094  {
1095
1096    String JavaDoc url = "";
1097    DocumentType JavaDoc doctype = doc.getDoctype();
1098
1099    if (null != doctype)
1100    {
1101      NamedNodeMap JavaDoc entities = doctype.getEntities();
1102      if(null == entities)
1103        return url;
1104      Entity JavaDoc entity = (Entity JavaDoc) entities.getNamedItem(name);
1105      if(null == entity)
1106        return url;
1107      
1108      String JavaDoc notationName = entity.getNotationName();
1109
1110      if (null != notationName) // then it's unparsed
1111
{
1112        // The draft says: "The XSLT processor may use the public
1113
// identifier to generate a URI for the entity instead of the URI
1114
// specified in the system identifier. If the XSLT processor does
1115
// not use the public identifier to generate the URI, it must use
1116
// the system identifier; if the system identifier is a relative
1117
// URI, it must be resolved into an absolute URI using the URI of
1118
// the resource containing the entity declaration as the base
1119
// URI [RFC2396]."
1120
// So I'm falling a bit short here.
1121
url = entity.getSystemId();
1122
1123        if (null == url)
1124        {
1125          url = entity.getPublicId();
1126        }
1127        else
1128        {
1129          // This should be resolved to an absolute URL, but that's hard
1130
// to do from here.
1131
}
1132      }
1133    }
1134
1135    return url;
1136  }
1137
1138  /**
1139   * Support for getParentOfNode; walks a DOM tree until it finds
1140   * the Element which owns the Attr. This is hugely expensive, and
1141   * if at all possible you should use the DOM Level 2 Attr.ownerElement()
1142   * method instead.
1143   * <p>
1144   * The DOM Level 1 developers expected that folks would keep track
1145   * of the last Element they'd seen and could recover the info from
1146   * that source. Obviously that doesn't work very well if the only
1147   * information you've been presented with is the Attr. The DOM Level 2
1148   * getOwnerElement() method fixes that, but only for Level 2 and
1149   * later DOMs.
1150   *
1151   * @param elem Element whose subtree is to be searched for this Attr
1152   * @param attr Attr whose owner is to be located.
1153   *
1154   * @return the first Element whose attribute list includes the provided
1155   * attr. In modern DOMs, this will also be the only such Element. (Early
1156   * DOMs had some hope that Attrs might be sharable, but this idea has
1157   * been abandoned.)
1158   */

1159  private static Node JavaDoc locateAttrParent(Element JavaDoc elem, Node JavaDoc attr)
1160  {
1161
1162    Node JavaDoc parent = null;
1163
1164        // This should only be called for Level 1 DOMs, so we don't have to
1165
// worry about namespace issues. In later levels, it's possible
1166
// for a DOM to have two Attrs with the same NodeName but
1167
// different namespaces, and we'd need to get getAttributeNodeNS...
1168
// but later levels also have Attr.getOwnerElement.
1169
Attr JavaDoc check=elem.getAttributeNode(attr.getNodeName());
1170        if(check==attr)
1171                parent = elem;
1172
1173    if (null == parent)
1174    {
1175      for (Node JavaDoc node = elem.getFirstChild(); null != node;
1176              node = node.getNextSibling())
1177      {
1178        if (Node.ELEMENT_NODE == node.getNodeType())
1179        {
1180          parent = locateAttrParent((Element JavaDoc) node, attr);
1181
1182          if (null != parent)
1183            break;
1184        }
1185      }
1186    }
1187
1188    return parent;
1189  }
1190
1191  /**
1192   * The factory object used for creating nodes
1193   * in the result tree.
1194   */

1195  protected Document JavaDoc m_DOMFactory = null;
1196
1197  /**
1198   * Store the factory object required to create DOM nodes
1199   * in the result tree. In fact, that's just the result tree's
1200   * Document node...
1201   *
1202   * @param domFactory The DOM Document Node within whose context
1203   * the result tree will be built.
1204   */

1205  public void setDOMFactory(Document JavaDoc domFactory)
1206  {
1207    this.m_DOMFactory = domFactory;
1208  }
1209
1210  /**
1211   * Retrieve the factory object required to create DOM nodes
1212   * in the result tree.
1213   *
1214   * @return The result tree's DOM Document Node.
1215   */

1216  public Document JavaDoc getDOMFactory()
1217  {
1218
1219    if (null == this.m_DOMFactory)
1220    {
1221      this.m_DOMFactory = createDocument();
1222    }
1223
1224    return this.m_DOMFactory;
1225  }
1226
1227  /**
1228   * Get the textual contents of the node. See
1229   * getNodeData(Node,FastStringBuffer) for discussion of how
1230   * whitespace nodes are handled.
1231   *
1232   * @param node DOM Node to be examined
1233   * @return String containing a concatenation of all the
1234   * textual content within that node.
1235   * @see #getNodeData(Node,FastStringBuffer)
1236   *
1237   */

1238  public static String JavaDoc getNodeData(Node JavaDoc node)
1239  {
1240
1241    FastStringBuffer buf = StringBufferPool.get();
1242    String JavaDoc s;
1243
1244    try
1245    {
1246      getNodeData(node, buf);
1247
1248      s = (buf.length() > 0) ? buf.toString() : "";
1249    }
1250    finally
1251    {
1252      StringBufferPool.free(buf);
1253    }
1254
1255    return s;
1256  }
1257
1258  /**
1259   * Retrieve the text content of a DOM subtree, appending it into a
1260   * user-supplied FastStringBuffer object. Note that attributes are
1261   * not considered part of the content of an element.
1262   * <p>
1263   * There are open questions regarding whitespace stripping.
1264   * Currently we make no special effort in that regard, since the standard
1265   * DOM doesn't yet provide DTD-based information to distinguish
1266   * whitespace-in-element-context from genuine #PCDATA. Note that we
1267   * should probably also consider xml:space if/when we address this.
1268   * DOM Level 3 may solve the problem for us.
1269   *
1270   * @param node Node whose subtree is to be walked, gathering the
1271   * contents of all Text or CDATASection nodes.
1272   * @param buf FastStringBuffer into which the contents of the text
1273   * nodes are to be concatenated.
1274   */

1275  public static void getNodeData(Node JavaDoc node, FastStringBuffer buf)
1276  {
1277
1278    switch (node.getNodeType())
1279    {
1280    case Node.DOCUMENT_FRAGMENT_NODE :
1281    case Node.DOCUMENT_NODE :
1282    case Node.ELEMENT_NODE :
1283    {
1284      for (Node JavaDoc child = node.getFirstChild(); null != child;
1285              child = child.getNextSibling())
1286      {
1287        getNodeData(child, buf);
1288      }
1289    }
1290    break;
1291    case Node.TEXT_NODE :
1292    case Node.CDATA_SECTION_NODE :
1293      buf.append(node.getNodeValue());
1294      break;
1295    case Node.ATTRIBUTE_NODE :
1296      buf.append(node.getNodeValue());
1297      break;
1298    case Node.PROCESSING_INSTRUCTION_NODE :
1299      // warning(XPATHErrorResources.WG_PARSING_AND_PREPARING);
1300
break;
1301    default :
1302      // ignore
1303
break;
1304    }
1305  }
1306}
1307
Popular Tags