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 468655 2006-10-28 07:12:06Z minchau $
20 */
21package org.apache.xml.utils;
22
23import java.io.File;
24
25import org.w3c.dom.Comment;
26import org.w3c.dom.Element;
27import org.w3c.dom.EntityReference;
28import org.w3c.dom.NamedNodeMap;
29import org.w3c.dom.Node;
30import org.w3c.dom.ProcessingInstruction;
31import org.w3c.dom.Text;
32
33import org.xml.sax.ContentHandler;
34import org.xml.sax.Locator;
35import org.xml.sax.ext.LexicalHandler;
36import org.xml.sax.helpers.LocatorImpl;
37
38/**
39 * This class does a pre-order walk of the DOM tree, calling a ContentHandler
40 * interface as it goes.
41 * @xsl.usage advanced
42 */
43
44public class TreeWalker
45{
46
47  /** Local reference to a ContentHandler          */
48  private ContentHandler m_contentHandler = null;
49
50  // ARGHH!!  JAXP Uses Xerces without setting the namespace processing to ON!
51  // DOM2Helper m_dh = new DOM2Helper();
52
53  /** DomHelper for this TreeWalker          */
54  protected DOMHelper m_dh;
55
56        /** Locator object for this TreeWalker          */
57        private LocatorImpl m_locator = new LocatorImpl();
58
59  /**
60   * Get the ContentHandler used for the tree walk.
61   *
62   * @return the ContentHandler used for the tree walk
63   */
64  public ContentHandler getContentHandler()
65  {
66    return m_contentHandler;
67  }
68
69  /**
70   * Get the ContentHandler used for the tree walk.
71   *
72   * @return the ContentHandler used for the tree walk
73   */
74  public void setContentHandler(ContentHandler ch)
75  {
76    m_contentHandler = ch;
77  }
78
79        /**
80   * Constructor.
81   * @param   contentHandler The implemention of the
82   * @param   systemId System identifier for the document.
83   * contentHandler operation (toXMLString, digest, ...)
84   */
85  public TreeWalker(ContentHandler contentHandler, DOMHelper dh, String systemId)
86  {
87    this.m_contentHandler = contentHandler;
88    m_contentHandler.setDocumentLocator(m_locator);
89    if (systemId != null)
90        m_locator.setSystemId(systemId);
91    else {
92        try {
93          // Bug see Bugzilla  26741
94          m_locator.setSystemId(System.getProperty("user.dir") + File.separator + "dummy.xsl");
95         }
96         catch (SecurityException se) {// user.dir not accessible from applet
97         }
98    }
99    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
509