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: TreeWalker.java 468654 2006-10-28 07:09:23Z minchau $
20 */
21package org.apache.xml.serializer;
22
23import java.io.File;
24
25import org.apache.xml.serializer.utils.AttList;
26import org.apache.xml.serializer.utils.DOM2Helper;
27import org.w3c.dom.Comment;
28import org.w3c.dom.Element;
29import org.w3c.dom.EntityReference;
30import org.w3c.dom.NamedNodeMap;
31import org.w3c.dom.Node;
32import org.w3c.dom.ProcessingInstruction;
33import org.w3c.dom.Text;
34import org.xml.sax.ContentHandler;
35import org.xml.sax.Locator;
36import org.xml.sax.ext.LexicalHandler;
37import org.xml.sax.helpers.LocatorImpl;
38
39
40/**
41 * This class does a pre-order walk of the DOM tree, calling a ContentHandler
42 * interface as it goes.
43 *
44 * This class is a copy of the one in org.apache.xml.utils.
45 * It exists to cut the serializers dependancy on that package.
46 *
47 * @xsl.usage internal
48 */
49
50public final class TreeWalker
51{
52
53  /** Local reference to a ContentHandler          */
54  final private ContentHandler m_contentHandler;
55  /**
56   * If m_contentHandler is a SerializationHandler, then this is
57   * a reference to the same object.
58   */
59  final private SerializationHandler m_Serializer;
60
61  // ARGHH!!  JAXP Uses Xerces without setting the namespace processing to ON!
62  // DOM2Helper m_dh = new DOM2Helper();
63
64  /** DomHelper for this TreeWalker          */
65  final protected DOM2Helper m_dh;
66
67  /** Locator object for this TreeWalker          */
68  final private LocatorImpl m_locator = new LocatorImpl();
69
70  /**
71   * Get the ContentHandler used for the tree walk.
72   *
73   * @return the ContentHandler used for the tree walk
74   */
75  public ContentHandler getContentHandler()
76  {
77    return m_contentHandler;
78  }
79
80  public TreeWalker(ContentHandler ch) {
81      this(ch,null);
82  }
83  /**
84   * Constructor.
85   * @param   contentHandler The implemention of the
86   * contentHandler operation (toXMLString, digest, ...)
87   */
88  public TreeWalker(ContentHandler contentHandler, String systemId)
89  {
90      // Set the content handler
91      m_contentHandler = contentHandler;
92      if (m_contentHandler instanceof SerializationHandler) {
93          m_Serializer = (SerializationHandler) m_contentHandler;
94      }
95      else
96          m_Serializer = null;
97
98      // Set the system ID, if it is given
99      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
528