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