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