1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.xml.internal.utils;
23
24import java.util.Stack;
25
26import com.sun.org.apache.xml.internal.res.XMLErrorResources;
27import com.sun.org.apache.xml.internal.res.XMLMessages;
28
29import org.w3c.dom.Document;
30import org.w3c.dom.DocumentFragment;
31import org.w3c.dom.Element;
32import org.w3c.dom.Node;
33import org.w3c.dom.Text;
34import org.w3c.dom.CDATASection;
35
36import org.xml.sax.Attributes;
37import org.xml.sax.ContentHandler;
38import org.xml.sax.Locator;
39import org.xml.sax.ext.LexicalHandler;
40/**
41 * This class takes SAX events (in addition to some extra events
42 * that SAX doesn't handle yet) and adds the result to a document
43 * or document fragment.
44 * @xsl.usage general
45 */
46public class DOMBuilder
47        implements ContentHandler, LexicalHandler
48{
49
50  /** Root document          */
51  public Document m_doc;
52
53  /** Current node           */
54  protected Node m_currentNode = null;
55
56  /** The root node          */
57  protected Node m_root = null;
58
59  /** The next sibling node  */
60  protected Node m_nextSibling = null;
61
62  /** First node of document fragment or null if not a DocumentFragment     */
63  public DocumentFragment m_docFrag = null;
64
65  /** Vector of element nodes          */
66  protected Stack m_elemStack = new Stack();
67
68  /**
69   * DOMBuilder instance constructor... it will add the DOM nodes
70   * to the document fragment.
71   *
72   * @param doc Root document
73   * @param node Current node
74   */
75  public DOMBuilder(Document doc, Node node)
76  {
77    m_doc = doc;
78    m_currentNode = m_root = node;
79
80    if (node instanceof Element)
81      m_elemStack.push(node);
82  }
83
84  /**
85   * DOMBuilder instance constructor... it will add the DOM nodes
86   * to the document fragment.
87   *
88   * @param doc Root document
89   * @param docFrag Document fragment
90   */
91  public DOMBuilder(Document doc, DocumentFragment docFrag)
92  {
93    m_doc = doc;
94    m_docFrag = docFrag;
95  }
96
97  /**
98   * DOMBuilder instance constructor... it will add the DOM nodes
99   * to the document.
100   *
101   * @param doc Root document
102   */
103  public DOMBuilder(Document doc)
104  {
105    m_doc = doc;
106  }
107
108  /**
109   * Get the root document or DocumentFragment of the DOM being created.
110   *
111   * @return The root document or document fragment if not null
112   */
113  public Node getRootDocument()
114  {
115    return (null != m_docFrag) ? (Node) m_docFrag : (Node) m_doc;
116  }
117
118  /**
119   * Get the root node of the DOM tree.
120   */
121  public Node getRootNode()
122  {
123    return m_root;
124  }
125
126  /**
127   * Get the node currently being processed.
128   *
129   * @return the current node being processed
130   */
131  public Node getCurrentNode()
132  {
133    return m_currentNode;
134  }
135
136  /**
137   * Set the next sibling node, which is where the result nodes
138   * should be inserted before.
139   *
140   * @param nextSibling the next sibling node.
141   */
142  public void setNextSibling(Node nextSibling)
143  {
144    m_nextSibling = nextSibling;
145  }
146
147  /**
148   * Return the next sibling node.
149   *
150   * @return the next sibling node.
151   */
152  public Node getNextSibling()
153  {
154    return m_nextSibling;
155  }
156
157  /**
158   * Return null since there is no Writer for this class.
159   *
160   * @return null
161   */
162  public java.io.Writer getWriter()
163  {
164    return null;
165  }
166
167  /**
168   * Append a node to the current container.
169   *
170   * @param newNode New node to append
171   */
172  protected void append(Node newNode) throws org.xml.sax.SAXException
173  {
174
175    Node currentNode = m_currentNode;
176
177    if (null != currentNode)
178    {
179      if (currentNode == m_root && m_nextSibling != null)
180        currentNode.insertBefore(newNode, m_nextSibling);
181      else
182        currentNode.appendChild(newNode);
183
184      // System.out.println(newNode.getNodeName());
185    }
186    else if (null != m_docFrag)
187    {
188      if (m_nextSibling != null)
189        m_docFrag.insertBefore(newNode, m_nextSibling);
190      else
191        m_docFrag.appendChild(newNode);
192    }
193    else
194    {
195      boolean ok = true;
196      short type = newNode.getNodeType();
197
198      if (type == Node.TEXT_NODE)
199      {
200        String data = newNode.getNodeValue();
201
202        if ((null != data) && (data.trim().length() > 0))
203        {
204          throw new org.xml.sax.SAXException(
205            XMLMessages.createXMLMessage(
206              XMLErrorResources.ER_CANT_OUTPUT_TEXT_BEFORE_DOC, null));  //"Warning: can't output text before document element!  Ignoring...");
207        }
208
209        ok = false;
210      }
211      else if (type == Node.ELEMENT_NODE)
212      {
213        if (m_doc.getDocumentElement() != null)
214        {
215          ok = false;
216
217          throw new org.xml.sax.SAXException(
218            XMLMessages.createXMLMessage(
219              XMLErrorResources.ER_CANT_HAVE_MORE_THAN_ONE_ROOT, null));  //"Can't have more than one root on a DOM!");
220        }
221      }
222
223      if (ok)
224      {
225        if (m_nextSibling != null)
226          m_doc.insertBefore(newNode, m_nextSibling);
227        else
228          m_doc.appendChild(newNode);
229      }
230    }
231  }
232
233  /**
234   * Receive an object for locating the origin of SAX document events.
235   *
236   * <p>SAX parsers are strongly encouraged (though not absolutely
237   * required) to supply a locator: if it does so, it must supply
238   * the locator to the application by invoking this method before
239   * invoking any of the other methods in the ContentHandler
240   * interface.</p>
241   *
242   * <p>The locator allows the application to determine the end
243   * position of any document-related event, even if the parser is
244   * not reporting an error.  Typically, the application will
245   * use this information for reporting its own errors (such as
246   * character content that does not match an application's
247   * business rules).  The information returned by the locator
248   * is probably not sufficient for use with a search engine.</p>
249   *
250   * <p>Note that the locator will return correct information only
251   * during the invocation of the events in this interface.  The
252   * application should not attempt to use it at any other time.</p>
253   *
254   * @param locator An object that can return the location of
255   *                any SAX document event.
256   * @see org.xml.sax.Locator
257   */
258  public void setDocumentLocator(Locator locator)
259  {
260
261    // No action for the moment.
262  }
263
264  /**
265   * Receive notification of the beginning of a document.
266   *
267   * <p>The SAX parser will invoke this method only once, before any
268   * other methods in this interface or in DTDHandler (except for
269   * setDocumentLocator).</p>
270   */
271  public void startDocument() throws org.xml.sax.SAXException
272  {
273
274    // No action for the moment.
275  }
276
277  /**
278   * Receive notification of the end of a document.
279   *
280   * <p>The SAX parser will invoke this method only once, and it will
281   * be the last method invoked during the parse.  The parser shall
282   * not invoke this method until it has either abandoned parsing
283   * (because of an unrecoverable error) or reached the end of
284   * input.</p>
285   */
286  public void endDocument() throws org.xml.sax.SAXException
287  {
288
289    // No action for the moment.
290  }
291
292  /**
293   * Receive notification of the beginning of an element.
294   *
295   * <p>The Parser will invoke this method at the beginning of every
296   * element in the XML document; there will be a corresponding
297   * endElement() event for every startElement() event (even when the
298   * element is empty). All of the element's content will be
299   * reported, in order, before the corresponding endElement()
300   * event.</p>
301   *
302   * <p>If the element name has a namespace prefix, the prefix will
303   * still be attached.  Note that the attribute list provided will
304   * contain only attributes with explicit values (specified or
305   * defaulted): #IMPLIED attributes will be omitted.</p>
306   *
307   *
308   * @param ns The namespace of the node
309   * @param localName The local part of the qualified name
310   * @param name The element name.
311   * @param atts The attributes attached to the element, if any.
312   * @see #endElement
313   * @see org.xml.sax.Attributes
314   */
315  public void startElement(
316          String ns, String localName, String name, Attributes atts)
317            throws org.xml.sax.SAXException
318  {
319
320    Element elem;
321
322        // Note that the namespace-aware call must be used to correctly
323        // construct a Level 2 DOM, even for non-namespaced nodes.
324    if ((null == ns) || (ns.length() == 0))
325      elem = m_doc.createElementNS(null,name);
326    else
327      elem = m_doc.createElementNS(ns, name);
328
329    append(elem);
330
331    try
332    {
333      int nAtts = atts.getLength();
334
335      if (0 != nAtts)
336      {
337        for (int i = 0; i < nAtts; i++)
338        {
339
340          //System.out.println("type " + atts.getType(i) + " name " + atts.getLocalName(i) );
341          // First handle a possible ID attribute
342          if (atts.getType(i).equalsIgnoreCase("ID"))
343            setIDAttribute(atts.getValue(i), elem);
344
345          String attrNS = atts.getURI(i);
346
347          if("".equals(attrNS))
348            attrNS = null; // DOM represents no-namespace as null
349
350          // System.out.println("attrNS: "+attrNS+", localName: "+atts.getQName(i)
351          //                   +", qname: "+atts.getQName(i)+", value: "+atts.getValue(i));
352          // Crimson won't let us set an xmlns: attribute on the DOM.
353          String attrQName = atts.getQName(i);
354
355          // In SAX, xmlns[:] attributes have an empty namespace, while in DOM they
356          // should have the xmlns namespace
357          if (attrQName.startsWith("xmlns:") || attrQName.equals("xmlns")) {
358            attrNS = "http://www.w3.org/2000/xmlns/";
359          }
360
361          // ALWAYS use the DOM Level 2 call!
362          elem.setAttributeNS(attrNS,attrQName, atts.getValue(i));
363        }
364      }
365
366      // append(elem);
367
368      m_elemStack.push(elem);
369
370      m_currentNode = elem;
371
372      // append(elem);
373    }
374    catch(java.lang.Exception de)
375    {
376      // de.printStackTrace();
377      throw new org.xml.sax.SAXException(de);
378    }
379
380  }
381
382  /**
383
384
385
386   * Receive notification of the end of an element.
387   *
388   * <p>The SAX parser will invoke this method at the end of every
389   * element in the XML document; there will be a corresponding
390   * startElement() event for every endElement() event (even when the
391   * element is empty).</p>
392   *
393   * <p>If the element name has a namespace prefix, the prefix will
394   * still be attached to the name.</p>
395   *
396   *
397   * @param ns the namespace of the element
398   * @param localName The local part of the qualified name of the element
399   * @param name The element name
400   */
401  public void endElement(String ns, String localName, String name)
402          throws org.xml.sax.SAXException
403  {
404    m_elemStack.pop();
405    m_currentNode = m_elemStack.isEmpty() ? null : (Node)m_elemStack.peek();
406  }
407
408  /**
409   * Set an ID string to node association in the ID table.
410   *
411   * @param id The ID string.
412   * @param elem The associated ID.
413   */
414  public void setIDAttribute(String id, Element elem)
415  {
416
417    // Do nothing. This method is meant to be overiden.
418  }
419
420  /**
421   * Receive notification of character data.
422   *
423   * <p>The Parser will call this method to report each chunk of
424   * character data.  SAX parsers may return all contiguous character
425   * data in a single chunk, or they may split it into several
426   * chunks; however, all of the characters in any single event
427   * must come from the same external entity, so that the Locator
428   * provides useful information.</p>
429   *
430   * <p>The application must not attempt to read from the array
431   * outside of the specified range.</p>
432   *
433   * <p>Note that some parsers will report whitespace using the
434   * ignorableWhitespace() method rather than this one (validating
435   * parsers must do so).</p>
436   *
437   * @param ch The characters from the XML document.
438   * @param start The start position in the array.
439   * @param length The number of characters to read from the array.
440   * @see #ignorableWhitespace
441   * @see org.xml.sax.Locator
442   */
443  public void characters(char ch[], int start, int length) throws org.xml.sax.SAXException
444  {
445    if(isOutsideDocElem()
446       && com.sun.org.apache.xml.internal.utils.XMLCharacterRecognizer.isWhiteSpace(ch, start, length))
447      return;  // avoid DOM006 Hierarchy request error
448
449    if (m_inCData)
450    {
451      cdata(ch, start, length);
452
453      return;
454    }
455
456    String s = new String(ch, start, length);
457    Node childNode;
458    childNode =  m_currentNode != null ? m_currentNode.getLastChild(): null;
459    if( childNode != null && childNode.getNodeType() == Node.TEXT_NODE ){
460       ((Text)childNode).appendData(s);
461    }
462    else{
463       Text text = m_doc.createTextNode(s);
464       append(text);
465    }
466  }
467
468  /**
469   * If available, when the disable-output-escaping attribute is used,
470   * output raw text without escaping.  A PI will be inserted in front
471   * of the node with the name "lotusxsl-next-is-raw" and a value of
472   * "formatter-to-dom".
473   *
474   * @param ch Array containing the characters
475   * @param start Index to start of characters in the array
476   * @param length Number of characters in the array
477   */
478  public void charactersRaw(char ch[], int start, int length)
479          throws org.xml.sax.SAXException
480  {
481    if(isOutsideDocElem()
482       && com.sun.org.apache.xml.internal.utils.XMLCharacterRecognizer.isWhiteSpace(ch, start, length))
483      return;  // avoid DOM006 Hierarchy request error
484
485
486    String s = new String(ch, start, length);
487
488    append(m_doc.createProcessingInstruction("xslt-next-is-raw",
489                                             "formatter-to-dom"));
490    append(m_doc.createTextNode(s));
491  }
492
493  /**
494   * Report the beginning of an entity.
495   *
496   * The start and end of the document entity are not reported.
497   * The start and end of the external DTD subset are reported
498   * using the pseudo-name "[dtd]".  All other events must be
499   * properly nested within start/end entity events.
500   *
501   * @param name The name of the entity.  If it is a parameter
502   *        entity, the name will begin with '%'.
503   * @see #endEntity
504   * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
505   * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
506   */
507  public void startEntity(String name) throws org.xml.sax.SAXException
508  {
509
510    // Almost certainly the wrong behavior...
511    // entityReference(name);
512  }
513
514  /**
515   * Report the end of an entity.
516   *
517   * @param name The name of the entity that is ending.
518   * @see #startEntity
519   */
520  public void endEntity(String name) throws org.xml.sax.SAXException{}
521
522  /**
523   * Receive notivication of a entityReference.
524   *
525   * @param name name of the entity reference
526   */
527  public void entityReference(String name) throws org.xml.sax.SAXException
528  {
529    append(m_doc.createEntityReference(name));
530  }
531
532  /**
533   * Receive notification of ignorable whitespace in element content.
534   *
535   * <p>Validating Parsers must use this method to report each chunk
536   * of ignorable whitespace (see the W3C XML 1.0 recommendation,
537   * section 2.10): non-validating parsers may also use this method
538   * if they are capable of parsing and using content models.</p>
539   *
540   * <p>SAX parsers may return all contiguous whitespace in a single
541   * chunk, or they may split it into several chunks; however, all of
542   * the characters in any single event must come from the same
543   * external entity, so that the Locator provides useful
544   * information.</p>
545   *
546   * <p>The application must not attempt to read from the array
547   * outside of the specified range.</p>
548   *
549   * @param ch The characters from the XML document.
550   * @param start The start position in the array.
551   * @param length The number of characters to read from the array.
552   * @see #characters
553   */
554  public void ignorableWhitespace(char ch[], int start, int length)
555          throws org.xml.sax.SAXException
556  {
557    if(isOutsideDocElem())
558      return;  // avoid DOM006 Hierarchy request error
559
560    String s = new String(ch, start, length);
561
562    append(m_doc.createTextNode(s));
563  }
564
565  /**
566   * Tell if the current node is outside the document element.
567   *
568   * @return true if the current node is outside the document element.
569   */
570   private boolean isOutsideDocElem()
571   {
572      return (null == m_docFrag) && m_elemStack.size() == 0 && (null == m_currentNode || m_currentNode.getNodeType() == Node.DOCUMENT_NODE);
573   }
574
575  /**
576   * Receive notification of a processing instruction.
577   *
578   * <p>The Parser will invoke this method once for each processing
579   * instruction found: note that processing instructions may occur
580   * before or after the main document element.</p>
581   *
582   * <p>A SAX parser should never report an XML declaration (XML 1.0,
583   * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
584   * using this method.</p>
585   *
586   * @param target The processing instruction target.
587   * @param data The processing instruction data, or null if
588   *        none was supplied.
589   */
590  public void processingInstruction(String target, String data)
591          throws org.xml.sax.SAXException
592  {
593    append(m_doc.createProcessingInstruction(target, data));
594  }
595
596  /**
597   * Report an XML comment anywhere in the document.
598   *
599   * This callback will be used for comments inside or outside the
600   * document element, including comments in the external DTD
601   * subset (if read).
602   *
603   * @param ch An array holding the characters in the comment.
604   * @param start The starting position in the array.
605   * @param length The number of characters to use from the array.
606   */
607  public void comment(char ch[], int start, int length) throws org.xml.sax.SAXException
608  {
609    append(m_doc.createComment(new String(ch, start, length)));
610  }
611
612  /** Flag indicating that we are processing a CData section          */
613  protected boolean m_inCData = false;
614
615  /**
616   * Report the start of a CDATA section.
617   *
618   * @see #endCDATA
619   */
620  public void startCDATA() throws org.xml.sax.SAXException
621  {
622    m_inCData = true;
623    append(m_doc.createCDATASection(""));
624  }
625
626  /**
627   * Report the end of a CDATA section.
628   *
629   * @see #startCDATA
630   */
631  public void endCDATA() throws org.xml.sax.SAXException
632  {
633    m_inCData = false;
634  }
635
636  /**
637   * Receive notification of cdata.
638   *
639   * <p>The Parser will call this method to report each chunk of
640   * character data.  SAX parsers may return all contiguous character
641   * data in a single chunk, or they may split it into several
642   * chunks; however, all of the characters in any single event
643   * must come from the same external entity, so that the Locator
644   * provides useful information.</p>
645   *
646   * <p>The application must not attempt to read from the array
647   * outside of the specified range.</p>
648   *
649   * <p>Note that some parsers will report whitespace using the
650   * ignorableWhitespace() method rather than this one (validating
651   * parsers must do so).</p>
652   *
653   * @param ch The characters from the XML document.
654   * @param start The start position in the array.
655   * @param length The number of characters to read from the array.
656   * @see #ignorableWhitespace
657   * @see org.xml.sax.Locator
658   */
659  public void cdata(char ch[], int start, int length) throws org.xml.sax.SAXException
660  {
661    if(isOutsideDocElem()
662       && com.sun.org.apache.xml.internal.utils.XMLCharacterRecognizer.isWhiteSpace(ch, start, length))
663      return;  // avoid DOM006 Hierarchy request error
664
665    String s = new String(ch, start, length);
666
667    CDATASection section  =(CDATASection) m_currentNode.getLastChild();
668    section.appendData(s);
669  }
670
671  /**
672   * Report the start of DTD declarations, if any.
673   *
674   * Any declarations are assumed to be in the internal subset
675   * unless otherwise indicated.
676   *
677   * @param name The document type name.
678   * @param publicId The declared public identifier for the
679   *        external DTD subset, or null if none was declared.
680   * @param systemId The declared system identifier for the
681   *        external DTD subset, or null if none was declared.
682   * @see #endDTD
683   * @see #startEntity
684   */
685  public void startDTD(String name, String publicId, String systemId)
686          throws org.xml.sax.SAXException
687  {
688
689    // Do nothing for now.
690  }
691
692  /**
693   * Report the end of DTD declarations.
694   *
695   * @see #startDTD
696   */
697  public void endDTD() throws org.xml.sax.SAXException
698  {
699
700    // Do nothing for now.
701  }
702
703  /**
704   * Begin the scope of a prefix-URI Namespace mapping.
705   *
706   * <p>The information from this event is not necessary for
707   * normal Namespace processing: the SAX XML reader will
708   * automatically replace prefixes for element and attribute
709   * names when the http://xml.org/sax/features/namespaces
710   * feature is true (the default).</p>
711   *
712   * <p>There are cases, however, when applications need to
713   * use prefixes in character data or in attribute values,
714   * where they cannot safely be expanded automatically; the
715   * start/endPrefixMapping event supplies the information
716   * to the application to expand prefixes in those contexts
717   * itself, if necessary.</p>
718   *
719   * <p>Note that start/endPrefixMapping events are not
720   * guaranteed to be properly nested relative to each-other:
721   * all startPrefixMapping events will occur before the
722   * corresponding startElement event, and all endPrefixMapping
723   * events will occur after the corresponding endElement event,
724   * but their order is not guaranteed.</p>
725   *
726   * @param prefix The Namespace prefix being declared.
727   * @param uri The Namespace URI the prefix is mapped to.
728   * @see #endPrefixMapping
729   * @see #startElement
730   */
731  public void startPrefixMapping(String prefix, String uri)
732          throws org.xml.sax.SAXException
733  {
734
735    /*
736    // Not sure if this is needed or wanted
737    // Also, it fails in the stree.
738    if((null != m_currentNode)
739       && (m_currentNode.getNodeType() == Node.ELEMENT_NODE))
740    {
741      String qname;
742      if(((null != prefix) && (prefix.length() == 0))
743         || (null == prefix))
744        qname = "xmlns";
745      else
746        qname = "xmlns:"+prefix;
747
748      Element elem = (Element)m_currentNode;
749      String val = elem.getAttribute(qname); // Obsolete, should be DOM2...?
750      if(val == null)
751      {
752        elem.setAttributeNS("http://www.w3.org/XML/1998/namespace",
753                            qname, uri);
754      }
755    }
756    */
757  }
758
759  /**
760   * End the scope of a prefix-URI mapping.
761   *
762   * <p>See startPrefixMapping for details.  This event will
763   * always occur after the corresponding endElement event,
764   * but the order of endPrefixMapping events is not otherwise
765   * guaranteed.</p>
766   *
767   * @param prefix The prefix that was being mapping.
768   * @see #startPrefixMapping
769   * @see #endElement
770   */
771  public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException{}
772
773  /**
774   * Receive notification of a skipped entity.
775   *
776   * <p>The Parser will invoke this method once for each entity
777   * skipped.  Non-validating processors may skip entities if they
778   * have not seen the declarations (because, for example, the
779   * entity was declared in an external DTD subset).  All processors
780   * may skip external entities, depending on the values of the
781   * http://xml.org/sax/features/external-general-entities and the
782   * http://xml.org/sax/features/external-parameter-entities
783   * properties.</p>
784   *
785   * @param name The name of the skipped entity.  If it is a
786   *        parameter entity, the name will begin with '%'.
787   */
788  public void skippedEntity(String name) throws org.xml.sax.SAXException{}
789}
790