001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: TreeWalker.java 468655 2006-10-28 07:12:06Z minchau $
020     */
021    package org.apache.xml.utils;
022    
023    import java.io.File;
024    
025    import org.w3c.dom.Comment;
026    import org.w3c.dom.Element;
027    import org.w3c.dom.EntityReference;
028    import org.w3c.dom.NamedNodeMap;
029    import org.w3c.dom.Node;
030    import org.w3c.dom.ProcessingInstruction;
031    import org.w3c.dom.Text;
032    
033    import org.xml.sax.ContentHandler;
034    import org.xml.sax.Locator;
035    import org.xml.sax.ext.LexicalHandler;
036    import org.xml.sax.helpers.LocatorImpl;
037    
038    /**
039     * This class does a pre-order walk of the DOM tree, calling a ContentHandler
040     * interface as it goes.
041     * @xsl.usage advanced
042     */
043    
044    public class TreeWalker
045    {
046    
047      /** Local reference to a ContentHandler          */
048      private ContentHandler m_contentHandler = null;
049    
050      // ARGHH!!  JAXP Uses Xerces without setting the namespace processing to ON!
051      // DOM2Helper m_dh = new DOM2Helper();
052    
053      /** DomHelper for this TreeWalker          */
054      protected DOMHelper m_dh;
055            
056            /** Locator object for this TreeWalker          */
057            private LocatorImpl m_locator = new LocatorImpl();
058    
059      /**
060       * Get the ContentHandler used for the tree walk.
061       *
062       * @return the ContentHandler used for the tree walk
063       */
064      public ContentHandler getContentHandler()
065      {
066        return m_contentHandler;
067      }
068    
069      /**
070       * Get the ContentHandler used for the tree walk.
071       *
072       * @return the ContentHandler used for the tree walk
073       */
074      public void setContentHandler(ContentHandler ch)
075      {
076        m_contentHandler = ch;
077      }
078            
079            /**
080       * Constructor.
081       * @param   contentHandler The implemention of the
082       * @param   systemId System identifier for the document.
083       * contentHandler operation (toXMLString, digest, ...)
084       */
085      public TreeWalker(ContentHandler contentHandler, DOMHelper dh, String systemId)
086      {
087        this.m_contentHandler = contentHandler;
088        m_contentHandler.setDocumentLocator(m_locator);
089        if (systemId != null)
090            m_locator.setSystemId(systemId);
091        else {
092            try {
093              // Bug see Bugzilla  26741
094              m_locator.setSystemId(System.getProperty("user.dir") + File.separator + "dummy.xsl");
095             }
096             catch (SecurityException se) {// user.dir not accessible from applet             
097             }
098        }
099        m_dh = dh;
100      }
101    
102      /**
103       * Constructor.
104       * @param   contentHandler The implemention of the
105       * contentHandler operation (toXMLString, digest, ...)
106       */
107      public TreeWalker(ContentHandler contentHandler, DOMHelper dh)
108      {
109        this.m_contentHandler = contentHandler;
110        m_contentHandler.setDocumentLocator(m_locator);
111        try {
112           // Bug see Bugzilla  26741
113          m_locator.setSystemId(System.getProperty("user.dir") + File.separator + "dummy.xsl");
114        } 
115        catch (SecurityException se){// user.dir not accessible from applet      
116        }
117        m_dh = dh;
118      }
119      
120      /**
121       * Constructor.
122       * @param   contentHandler The implemention of the
123       * contentHandler operation (toXMLString, digest, ...)
124       */
125      public TreeWalker(ContentHandler contentHandler)
126      {
127        this.m_contentHandler = contentHandler;
128                    if (m_contentHandler != null)
129                            m_contentHandler.setDocumentLocator(m_locator);
130                    try {
131                       // Bug see Bugzilla  26741
132                      m_locator.setSystemId(System.getProperty("user.dir") + File.separator + "dummy.xsl");
133                    } 
134                    catch (SecurityException se){// user.dir not accessible from applet
135                      
136        }
137        m_dh = new DOM2Helper();
138      }
139    
140      /**
141       * Perform a pre-order traversal non-recursive style.  
142       *
143       * Note that TreeWalker assumes that the subtree is intended to represent 
144       * a complete (though not necessarily well-formed) document and, during a 
145       * traversal, startDocument and endDocument will always be issued to the 
146       * SAX listener.
147       *  
148       * @param pos Node in the tree where to start traversal
149       *
150       * @throws TransformerException
151       */
152      public void traverse(Node pos) throws org.xml.sax.SAXException
153      {
154            this.m_contentHandler.startDocument();
155            
156            traverseFragment(pos);
157    
158            this.m_contentHandler.endDocument();
159      }
160        
161      /**
162       * Perform a pre-order traversal non-recursive style.  
163       *
164       * In contrast to the traverse() method this method will not issue 
165       * startDocument() and endDocument() events to the SAX listener.
166       *  
167       * @param pos Node in the tree where to start traversal
168       *
169       * @throws TransformerException
170       */
171      public void traverseFragment(Node pos) throws org.xml.sax.SAXException
172      {
173        Node top = pos;
174    
175        while (null != pos)
176        {
177          startNode(pos);
178    
179          Node nextNode = pos.getFirstChild();
180    
181          while (null == nextNode)
182          {
183            endNode(pos);
184    
185            if (top.equals(pos))
186              break;
187    
188            nextNode = pos.getNextSibling();
189    
190            if (null == nextNode)
191            {
192              pos = pos.getParentNode();
193    
194              if ((null == pos) || (top.equals(pos)))
195              {
196                if (null != pos)
197                  endNode(pos);
198    
199                nextNode = null;
200    
201                break;
202              }
203            }
204          }
205    
206          pos = nextNode;
207        }
208      }
209    
210      /**
211       * Perform a pre-order traversal non-recursive style.
212    
213       * Note that TreeWalker assumes that the subtree is intended to represent 
214       * a complete (though not necessarily well-formed) document and, during a 
215       * traversal, startDocument and endDocument will always be issued to the 
216       * SAX listener.
217       *
218       * @param pos Node in the tree where to start traversal
219       * @param top Node in the tree where to end traversal
220       *
221       * @throws TransformerException
222       */
223      public void traverse(Node pos, Node top) throws org.xml.sax.SAXException
224      {
225    
226            this.m_contentHandler.startDocument();
227            
228        while (null != pos)
229        {
230          startNode(pos);
231    
232          Node nextNode = pos.getFirstChild();
233    
234          while (null == nextNode)
235          {
236            endNode(pos);
237    
238            if ((null != top) && top.equals(pos))
239              break;
240    
241            nextNode = pos.getNextSibling();
242    
243            if (null == nextNode)
244            {
245              pos = pos.getParentNode();
246    
247              if ((null == pos) || ((null != top) && top.equals(pos)))
248              {
249                nextNode = null;
250    
251                break;
252              }
253            }
254          }
255    
256          pos = nextNode;
257        }
258        this.m_contentHandler.endDocument();
259      }
260    
261      /** Flag indicating whether following text to be processed is raw text          */
262      boolean nextIsRaw = false;
263      
264      /**
265       * Optimized dispatch of characters.
266       */
267      private final void dispatachChars(Node node)
268         throws org.xml.sax.SAXException
269      {
270        if(m_contentHandler instanceof org.apache.xml.dtm.ref.dom2dtm.DOM2DTM.CharacterNodeHandler)
271        {
272          ((org.apache.xml.dtm.ref.dom2dtm.DOM2DTM.CharacterNodeHandler)m_contentHandler).characters(node);
273        }
274        else
275        {
276          String data = ((Text) node).getData();
277          this.m_contentHandler.characters(data.toCharArray(), 0, data.length());
278        }
279      }
280    
281      /**
282       * Start processing given node
283       *
284       *
285       * @param node Node to process
286       *
287       * @throws org.xml.sax.SAXException
288       */
289      protected void startNode(Node node) throws org.xml.sax.SAXException
290      {
291    
292        if (m_contentHandler instanceof NodeConsumer)
293        {
294          ((NodeConsumer) m_contentHandler).setOriginatingNode(node);
295        }
296                    
297                    if (node instanceof Locator)
298                    {
299                            Locator loc = (Locator)node;
300                            m_locator.setColumnNumber(loc.getColumnNumber());
301                            m_locator.setLineNumber(loc.getLineNumber());
302                            m_locator.setPublicId(loc.getPublicId());
303                            m_locator.setSystemId(loc.getSystemId());
304                    }
305                    else
306                    {
307                            m_locator.setColumnNumber(0);
308          m_locator.setLineNumber(0);
309                    }
310    
311        switch (node.getNodeType())
312        {
313        case Node.COMMENT_NODE :
314        {
315          String data = ((Comment) node).getData();
316    
317          if (m_contentHandler instanceof LexicalHandler)
318          {
319            LexicalHandler lh = ((LexicalHandler) this.m_contentHandler);
320    
321            lh.comment(data.toCharArray(), 0, data.length());
322          }
323        }
324        break;
325        case Node.DOCUMENT_FRAGMENT_NODE :
326    
327          // ??;
328          break;
329        case Node.DOCUMENT_NODE :
330        
331          break;
332        case Node.ELEMENT_NODE :
333          NamedNodeMap atts = ((Element) node).getAttributes();
334          int nAttrs = atts.getLength();
335          // System.out.println("TreeWalker#startNode: "+node.getNodeName());
336    
337          for (int i = 0; i < nAttrs; i++)
338          {
339            Node attr = atts.item(i);
340            String attrName = attr.getNodeName();
341    
342            // System.out.println("TreeWalker#startNode: attr["+i+"] = "+attrName+", "+attr.getNodeValue());
343            if (attrName.equals("xmlns") || attrName.startsWith("xmlns:"))
344            {
345              // System.out.println("TreeWalker#startNode: attr["+i+"] = "+attrName+", "+attr.getNodeValue());
346              int index;
347              // Use "" instead of null, as Xerces likes "" for the 
348              // name of the default namespace.  Fix attributed 
349              // to "Steven Murray" <smurray@ebt.com>.
350              String prefix = (index = attrName.indexOf(":")) < 0
351                              ? "" : attrName.substring(index + 1);
352    
353              this.m_contentHandler.startPrefixMapping(prefix,
354                                                       attr.getNodeValue());
355            }
356            
357          }
358    
359          // System.out.println("m_dh.getNamespaceOfNode(node): "+m_dh.getNamespaceOfNode(node));
360          // System.out.println("m_dh.getLocalNameOfNode(node): "+m_dh.getLocalNameOfNode(node));
361          String ns = m_dh.getNamespaceOfNode(node);
362          if(null == ns)
363            ns = "";
364          this.m_contentHandler.startElement(ns,
365                                             m_dh.getLocalNameOfNode(node),
366                                             node.getNodeName(),
367                                             new AttList(atts, m_dh));
368          break;
369        case Node.PROCESSING_INSTRUCTION_NODE :
370        {
371          ProcessingInstruction pi = (ProcessingInstruction) node;
372          String name = pi.getNodeName();
373    
374          // String data = pi.getData();
375          if (name.equals("xslt-next-is-raw"))
376          {
377            nextIsRaw = true;
378          }
379          else
380          {
381            this.m_contentHandler.processingInstruction(pi.getNodeName(),
382                                                        pi.getData());
383          }
384        }
385        break;
386        case Node.CDATA_SECTION_NODE :
387        {
388          boolean isLexH = (m_contentHandler instanceof LexicalHandler);
389          LexicalHandler lh = isLexH
390                              ? ((LexicalHandler) this.m_contentHandler) : null;
391    
392          if (isLexH)
393          {
394            lh.startCDATA();
395          }
396          
397          dispatachChars(node);
398    
399          {
400            if (isLexH)
401            {
402              lh.endCDATA();
403            }
404          }
405        }
406        break;
407        case Node.TEXT_NODE :
408        {
409          //String data = ((Text) node).getData();
410    
411          if (nextIsRaw)
412          {
413            nextIsRaw = false;
414    
415            m_contentHandler.processingInstruction(javax.xml.transform.Result.PI_DISABLE_OUTPUT_ESCAPING, "");
416            dispatachChars(node);
417            m_contentHandler.processingInstruction(javax.xml.transform.Result.PI_ENABLE_OUTPUT_ESCAPING, "");
418          }
419          else
420          {
421            dispatachChars(node);
422          }
423        }
424        break;
425        case Node.ENTITY_REFERENCE_NODE :
426        {
427          EntityReference eref = (EntityReference) node;
428    
429          if (m_contentHandler instanceof LexicalHandler)
430          {
431            ((LexicalHandler) this.m_contentHandler).startEntity(
432              eref.getNodeName());
433          }
434          else
435          {
436    
437            // warning("Can not output entity to a pure SAX ContentHandler");
438          }
439        }
440        break;
441        default :
442        }
443      }
444    
445      /**
446       * End processing of given node 
447       *
448       *
449       * @param node Node we just finished processing
450       *
451       * @throws org.xml.sax.SAXException
452       */
453      protected void endNode(Node node) throws org.xml.sax.SAXException
454      {
455    
456        switch (node.getNodeType())
457        {
458        case Node.DOCUMENT_NODE :
459          break;
460          
461        case Node.ELEMENT_NODE :
462          String ns = m_dh.getNamespaceOfNode(node);
463          if(null == ns)
464            ns = "";
465          this.m_contentHandler.endElement(ns,
466                                             m_dh.getLocalNameOfNode(node),
467                                             node.getNodeName());
468    
469          NamedNodeMap atts = ((Element) node).getAttributes();
470          int nAttrs = atts.getLength();
471    
472          for (int i = 0; i < nAttrs; i++)
473          {
474            Node attr = atts.item(i);
475            String attrName = attr.getNodeName();
476    
477            if (attrName.equals("xmlns") || attrName.startsWith("xmlns:"))
478            {
479              int index;
480              // Use "" instead of null, as Xerces likes "" for the 
481              // name of the default namespace.  Fix attributed 
482              // to "Steven Murray" <smurray@ebt.com>.
483              String prefix = (index = attrName.indexOf(":")) < 0
484                              ? "" : attrName.substring(index + 1);
485    
486              this.m_contentHandler.endPrefixMapping(prefix);
487            }
488          }
489          break;
490        case Node.CDATA_SECTION_NODE :
491          break;
492        case Node.ENTITY_REFERENCE_NODE :
493        {
494          EntityReference eref = (EntityReference) node;
495    
496          if (m_contentHandler instanceof LexicalHandler)
497          {
498            LexicalHandler lh = ((LexicalHandler) this.m_contentHandler);
499    
500            lh.endEntity(eref.getNodeName());
501          }
502        }
503        break;
504        default :
505        }
506      }
507    }  //TreeWalker
508