001: /*--
002:
003: $Id: JDOMResult.java,v 1.1 2005/04/27 09:32:43 wittek Exp $
004:
005: Copyright (C) 2001-2004 Jason Hunter & Brett McLaughlin.
006: All rights reserved.
007:
008: Redistribution and use in source and binary forms, with or without
009: modification, are permitted provided that the following conditions
010: are met:
011:
012: 1. Redistributions of source code must retain the above copyright
013: notice, this list of conditions, and the following disclaimer.
014:
015: 2. Redistributions in binary form must reproduce the above copyright
016: notice, this list of conditions, and the disclaimer that follows
017: these conditions in the documentation and/or other materials
018: provided with the distribution.
019:
020: 3. The name "JDOM" must not be used to endorse or promote products
021: derived from this software without prior written permission. For
022: written permission, please contact <request_AT_jdom_DOT_org>.
023:
024: 4. Products derived from this software may not be called "JDOM", nor
025: may "JDOM" appear in their name, without prior written permission
026: from the JDOM Project Management <request_AT_jdom_DOT_org>.
027:
028: In addition, we request (but do not require) that you include in the
029: end-user documentation provided with the redistribution and/or in the
030: software itself an acknowledgement equivalent to the following:
031: "This product includes software developed by the
032: JDOM Project (http://www.jdom.org/)."
033: Alternatively, the acknowledgment may be graphical using the logos
034: available at http://www.jdom.org/images/logos.
035:
036: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
040: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: SUCH DAMAGE.
048:
049: This software consists of voluntary contributions made by many
050: individuals on behalf of the JDOM Project and was originally
051: created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
052: Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
053: on the JDOM Project, please see <http://www.jdom.org/>.
054:
055: */
056:
057: package org.jdom.transform;
058:
059: import java.util.*;
060:
061: import javax.xml.transform.sax.*;
062:
063: import org.jdom.*;
064: import org.jdom.input.*;
065: import org.xml.sax.*;
066: import org.xml.sax.ext.*;
067: import org.xml.sax.helpers.*;
068:
069: /**
070: * A holder for an XSL Transformation result, generally a list of nodes
071: * although it can be a JDOM Document also. As stated by the XSLT 1.0
072: * specification, the result tree generated by an XSL transformation is not
073: * required to be a well-formed XML document. The result tree may have "any
074: * sequence of nodes as children that would be possible for an
075: * element node".
076: * <p>
077: * The following example shows how to apply an XSL Transformation
078: * to a JDOM document and get the transformation result in the form
079: * of a list of JDOM nodes:
080: * <pre><code>
081: * public static List transform(Document doc, String stylesheet)
082: * throws JDOMException {
083: * try {
084: * Transformer transformer = TransformerFactory.newInstance()
085: * .newTransformer(new StreamSource(stylesheet));
086: * JDOMSource in = new JDOMSource(doc);
087: * JDOMResult out = new JDOMResult();
088: * transformer.transform(in, out);
089: * return out.getResult();
090: * }
091: * catch (TransformerException e) {
092: * throw new JDOMException("XSLT Transformation failed", e);
093: * }
094: * }
095: * </code></pre>
096: *
097: * @see org.jdom.transform.JDOMSource
098: *
099: * @version $Revision: 1.1 $, $Date: 2005/04/27 09:32:43 $
100: * @author Laurent Bihanic
101: * @author Jason Hunter
102: */
103: public class JDOMResult extends SAXResult {
104:
105: private static final String CVS_ID = "@(#) $RCSfile: JDOMResult.java,v $ $Revision: 1.1 $ $Date: 2005/04/27 09:32:43 $ $Name: $";
106:
107: /**
108: * If {@link javax.xml.transform.TransformerFactory#getFeature}
109: * returns <code>true</code> when passed this value as an
110: * argument, the Transformer natively supports JDOM.
111: * <p>
112: * <strong>Note</strong>: This implementation does not override
113: * the {@link SAXResult#FEATURE} value defined by its superclass
114: * to be considered as a SAXResult by Transformer implementations
115: * not natively supporting JDOM.</p>
116: */
117: public final static String JDOM_FEATURE = "http://org.jdom.transform.JDOMResult/feature";
118:
119: /**
120: * The result of a transformation, as set by Transformer
121: * implementations that natively support JDOM, as a JDOM document
122: * or a list of JDOM nodes.
123: */
124: private Object result = null;
125:
126: /**
127: * Whether the application queried the result (as a list or a
128: * document) since it was last set.
129: */
130: private boolean queried = false;
131:
132: /**
133: * The custom JDOM factory to use when building the transformation
134: * result or <code>null</code> to use the default JDOM classes.
135: */
136: private JDOMFactory factory = null;
137:
138: /**
139: * Public default constructor.
140: */
141: public JDOMResult() {
142: // Allocate custom builder object...
143: DocumentBuilder builder = new DocumentBuilder();
144:
145: // And use it as ContentHandler and LexicalHandler.
146: super .setHandler(builder);
147: super .setLexicalHandler(builder);
148: }
149:
150: /**
151: * Sets the object(s) produced as result of an XSL Transformation.
152: * <p>
153: * <strong>Note</strong>: This method shall be used by the
154: * {@link javax.xml.transform.Transformer} implementations that
155: * natively support JDOM to directly set the transformation
156: * result rather than considering this object as a
157: * {@link SAXResult}. Applications should <i>not</i> use this
158: * method.</p>
159: *
160: * @param result the result of a transformation as a
161: * {@link java.util.List list} of JDOM nodes
162: * (Elements, Texts, Comments, PIs...).
163: *
164: * @see #getResult
165: */
166: public void setResult(List result) {
167: this .result = result;
168: this .queried = false;
169: }
170:
171: /**
172: * Returns the result of an XSL Transformation as a list of JDOM
173: * nodes.
174: * <p>
175: * If the result of the transformation is a JDOM document,
176: * this method converts it into a list of JDOM nodes; any
177: * subsequent call to {@link #getDocument} will return
178: * <code>null</code>.</p>
179: *
180: * @return the transformation result as a (possibly empty) list of
181: * JDOM nodes (Elements, Texts, Comments, PIs...).
182: */
183: public List getResult() {
184: List nodes = Collections.EMPTY_LIST;
185:
186: // Retrieve result from the document builder if not set.
187: this .retrieveResult();
188:
189: if (result instanceof List) {
190: nodes = (List) result;
191: } else {
192: if ((result instanceof Document) && (queried == false)) {
193: List content = ((Document) result).getContent();
194: nodes = new ArrayList(content.size());
195:
196: while (content.size() != 0) {
197: Object o = content.remove(0);
198: nodes.add(o);
199: }
200: result = nodes;
201: }
202: }
203: queried = true;
204:
205: return (nodes);
206: }
207:
208: /**
209: * Sets the document produced as result of an XSL Transformation.
210: * <p>
211: * <strong>Note</strong>: This method shall be used by the
212: * {@link javax.xml.transform.Transformer} implementations that
213: * natively support JDOM to directly set the transformation
214: * result rather than considering this object as a
215: * {@link SAXResult}. Applications should <i>not</i> use this
216: * method.</p>
217: *
218: * @param document the JDOM document result of a transformation.
219: *
220: * @see #setResult
221: * @see #getDocument
222: */
223: public void setDocument(Document document) {
224: this .result = document;
225: this .queried = false;
226: }
227:
228: /**
229: * Returns the result of an XSL Transformation as a JDOM document.
230: * <p>
231: * If the result of the transformation is a list of nodes,
232: * this method attempts to convert it into a JDOM document. If
233: * successful, any subsequent call to {@link #getResult} will
234: * return an empty list.</p>
235: * <p>
236: * <strong>Warning</strong>: The XSLT 1.0 specification states that
237: * the output of an XSL transformation is not a well-formed XML
238: * document but a list of nodes. Applications should thus use
239: * {@link #getResult} instead of this method or at least expect
240: * <code>null</code> documents to be returned.
241: *
242: * @return the transformation result as a JDOM document or
243: * <code>null</code> if the result of the transformation
244: * can not be converted into a well-formed document.
245: *
246: * @see #getResult
247: */
248: public Document getDocument() {
249: Document doc = null;
250:
251: // Retrieve result from the document builder if not set.
252: this .retrieveResult();
253:
254: if (result instanceof Document) {
255: doc = (Document) result;
256: } else {
257: if ((result instanceof List) && (queried == false)) {
258: // Try to create a document from the result nodes
259: try {
260: JDOMFactory f = this .getFactory();
261: if (f == null) {
262: f = new DefaultJDOMFactory();
263: }
264:
265: doc = f.document(null);
266: doc.setContent((List) result);
267:
268: result = doc;
269: } catch (RuntimeException ex1) {
270: // Some of the result nodes are not valid children of a
271: // Document node. => return null.
272: }
273: }
274: }
275: queried = true;
276:
277: return (doc);
278: }
279:
280: /**
281: * Sets a custom JDOMFactory to use when building the
282: * transformation result. Use a custom factory to build the tree
283: * with your own subclasses of the JDOM classes.
284: *
285: * @param factory the custom <code>JDOMFactory</code> to use or
286: * <code>null</code> to use the default JDOM
287: * classes.
288: *
289: * @see #getFactory
290: */
291: public void setFactory(JDOMFactory factory) {
292: this .factory = factory;
293: }
294:
295: /**
296: * Returns the custom JDOMFactory used to build the transformation
297: * result.
298: *
299: * @return the custom <code>JDOMFactory</code> used to build the
300: * transformation result or <code>null</code> if the
301: * default JDOM classes are being used.
302: *
303: * @see #setFactory
304: */
305: public JDOMFactory getFactory() {
306: return this .factory;
307: }
308:
309: /**
310: * Checks whether a transformation result has been set and, if not,
311: * retrieves the result tree being built by the document builder.
312: */
313: private void retrieveResult() {
314: if (result == null) {
315: this .setResult(((DocumentBuilder) this .getHandler())
316: .getResult());
317: }
318: }
319:
320: //-------------------------------------------------------------------------
321: // SAXResult overwritten methods
322: //-------------------------------------------------------------------------
323:
324: /**
325: * Sets the target to be a SAX2 ContentHandler.
326: *
327: * @param handler Must be a non-null ContentHandler reference.
328: */
329: public void setHandler(ContentHandler handler) {
330: }
331:
332: /**
333: * Sets the SAX2 LexicalHandler for the output.
334: * <p>
335: * This is needed to handle XML comments and the like. If the
336: * lexical handler is not set, an attempt should be made by the
337: * transformer to cast the ContentHandler to a LexicalHandler.</p>
338: *
339: * @param handler A non-null LexicalHandler for
340: * handling lexical parse events.
341: */
342: public void setLexicalHandler(LexicalHandler handler) {
343: }
344:
345: //=========================================================================
346: // FragmentHandler nested class
347: //=========================================================================
348:
349: private static class FragmentHandler extends SAXHandler {
350: /**
351: * A dummy root element required by SAXHandler that can only
352: * cope with well-formed documents.
353: */
354: private Element dummyRoot = new Element("root", null, null);
355:
356: /**
357: * Public constructor.
358: */
359: public FragmentHandler(JDOMFactory factory) {
360: super (factory);
361:
362: // Add a dummy root element to the being-built document as XSL
363: // transformation can output node lists instead of well-formed
364: // documents.
365: this .pushElement(dummyRoot);
366: }
367:
368: /**
369: * Returns the result of an XSL Transformation.
370: *
371: * @return the transformation result as a (possibly empty) list of
372: * JDOM nodes (Elements, Texts, Comments, PIs...).
373: */
374: public List getResult() {
375: // Flush remaining text content in case the last text segment is
376: // outside an element.
377: try {
378: this .flushCharacters();
379: } catch (SAXException e) { /* Ignore... */
380: }
381: return this .getDetachedContent(dummyRoot);
382: }
383:
384: /**
385: * Returns the content of a JDOM Element detached from it.
386: *
387: * @param elt the element to get the content from.
388: *
389: * @return a (possibly empty) list of JDOM nodes, detached from
390: * their parent.
391: */
392: private List getDetachedContent(Element elt) {
393: List content = elt.getContent();
394: List nodes = new ArrayList(content.size());
395:
396: while (content.size() != 0) {
397: Object o = content.remove(0);
398: nodes.add(o);
399: }
400: return (nodes);
401: }
402: }
403:
404: //=========================================================================
405: // DocumentBuilder inner class
406: //=========================================================================
407:
408: private class DocumentBuilder extends XMLFilterImpl implements
409: LexicalHandler {
410: /**
411: * The actual JDOM document builder.
412: */
413: private FragmentHandler saxHandler = null;
414:
415: /**
416: * Whether the startDocument event was received. Some XSLT
417: * processors such as Oracle's do not fire this event.
418: */
419: private boolean startDocumentReceived = false;
420:
421: /**
422: * Public default constructor.
423: */
424: public DocumentBuilder() {
425: }
426:
427: /**
428: * Returns the result of an XSL Transformation.
429: *
430: * @return the transformation result as a (possibly empty) list of
431: * JDOM nodes (Elements, Texts, Comments, PIs...) or
432: * <code>null</code> if no new transformation occurred
433: * since the result of the previous one was returned.
434: */
435: public List getResult() {
436: List result = null;
437:
438: if (this .saxHandler != null) {
439: // Retrieve result from SAX content handler.
440: result = this .saxHandler.getResult();
441:
442: // Detach the (non-reusable) SAXHandler instance.
443: this .saxHandler = null;
444:
445: // And get ready for the next transformation.
446: this .startDocumentReceived = false;
447: }
448: return result;
449: }
450:
451: private void ensureInitialization() throws SAXException {
452: // Trigger document initialization if XSLT processor failed to
453: // fire the startDocument event.
454: if (this .startDocumentReceived == false) {
455: this .startDocument();
456: }
457: }
458:
459: //-----------------------------------------------------------------------
460: // XMLFilterImpl overwritten methods
461: //-----------------------------------------------------------------------
462:
463: /**
464: * <i>[SAX ContentHandler interface support]</i> Processes a
465: * start of document event.
466: * <p>
467: * This implementation creates a new JDOM document builder and
468: * marks the current result as "under construction".</p>
469: *
470: * @throws SAXException if any error occurred while creating
471: * the document builder.
472: */
473: public void startDocument() throws SAXException {
474: this .startDocumentReceived = true;
475:
476: // Reset any previously set result.
477: setResult(null);
478:
479: // Create the actual JDOM document builder and register it as
480: // ContentHandler on the superclass (XMLFilterImpl): this
481: // implementation will take care of propagating the LexicalHandler
482: // events.
483: this .saxHandler = new FragmentHandler(getFactory());
484: super .setContentHandler(this .saxHandler);
485:
486: // And propagate event.
487: super .startDocument();
488: }
489:
490: /**
491: * <i>[SAX ContentHandler interface support]</i> Receives
492: * notification of the beginning of an element.
493: * <p>
494: * This implementation ensures that startDocument() has been
495: * called prior processing an element.
496: *
497: * @param nsURI the Namespace URI, or the empty string if
498: * the element has no Namespace URI or if
499: * Namespace processing is not being performed.
500: * @param localName the local name (without prefix), or the
501: * empty string if Namespace processing is
502: * not being performed.
503: * @param qName the qualified name (with prefix), or the
504: * empty string if qualified names are not
505: * available.
506: * @param atts The attributes attached to the element. If
507: * there are no attributes, it shall be an
508: * empty Attributes object.
509: *
510: * @throws SAXException if any error occurred while creating
511: * the document builder.
512: */
513: public void startElement(String nsURI, String localName,
514: String qName, Attributes atts) throws SAXException {
515: this .ensureInitialization();
516: super .startElement(nsURI, localName, qName, atts);
517: }
518:
519: /**
520: * <i>[SAX ContentHandler interface support]</i> Begins the
521: * scope of a prefix-URI Namespace mapping.
522: */
523: public void startPrefixMapping(String prefix, String uri)
524: throws SAXException {
525: this .ensureInitialization();
526: super .startPrefixMapping(prefix, uri);
527: }
528:
529: /**
530: * <i>[SAX ContentHandler interface support]</i> Receives
531: * notification of character data.
532: */
533: public void characters(char ch[], int start, int length)
534: throws SAXException {
535: this .ensureInitialization();
536: super .characters(ch, start, length);
537: }
538:
539: /**
540: * <i>[SAX ContentHandler interface support]</i> Receives
541: * notification of ignorable whitespace in element content.
542: */
543: public void ignorableWhitespace(char ch[], int start, int length)
544: throws SAXException {
545: this .ensureInitialization();
546: super .ignorableWhitespace(ch, start, length);
547: }
548:
549: /**
550: * <i>[SAX ContentHandler interface support]</i> Receives
551: * notification of a processing instruction.
552: */
553: public void processingInstruction(String target, String data)
554: throws SAXException {
555: this .ensureInitialization();
556: super .processingInstruction(target, data);
557: }
558:
559: /**
560: * <i>[SAX ContentHandler interface support]</i> Receives
561: * notification of a skipped entity.
562: */
563: public void skippedEntity(String name) throws SAXException {
564: this .ensureInitialization();
565: super .skippedEntity(name);
566: }
567:
568: //-----------------------------------------------------------------------
569: // LexicalHandler interface support
570: //-----------------------------------------------------------------------
571:
572: /**
573: * <i>[SAX LexicalHandler interface support]</i> Reports the
574: * start of DTD declarations, if any.
575: *
576: * @param name the document type name.
577: * @param publicId the declared public identifier for the
578: * external DTD subset, or <code>null</code>
579: * if none was declared.
580: * @param systemId the declared system identifier for the
581: * external DTD subset, or <code>null</code>
582: * if none was declared.
583: *
584: * @throws SAXException The application may raise an exception.
585: */
586: public void startDTD(String name, String publicId,
587: String systemId) throws SAXException {
588: this .ensureInitialization();
589: this .saxHandler.startDTD(name, publicId, systemId);
590: }
591:
592: /**
593: * <i>[SAX LexicalHandler interface support]</i> Reports the end
594: * of DTD declarations.
595: *
596: * @throws SAXException The application may raise an exception.
597: */
598: public void endDTD() throws SAXException {
599: this .saxHandler.endDTD();
600: }
601:
602: /**
603: * <i>[SAX LexicalHandler interface support]</i> Reports the
604: * beginning of some internal and external XML entities.
605: *
606: * @param name the name of the entity. If it is a parameter
607: * entity, the name will begin with '%', and if it
608: * is the external DTD subset, it will be "[dtd]".
609: *
610: * @throws SAXException The application may raise an exception.
611: */
612: public void startEntity(String name) throws SAXException {
613: this .ensureInitialization();
614: this .saxHandler.startEntity(name);
615: }
616:
617: /**
618: * <i>[SAX LexicalHandler interface support]</i> Reports the end
619: * of an entity.
620: *
621: * @param name the name of the entity that is ending.
622: *
623: * @throws SAXException The application may raise an exception.
624: */
625: public void endEntity(String name) throws SAXException {
626: this .saxHandler.endEntity(name);
627: }
628:
629: /**
630: * <i>[SAX LexicalHandler interface support]</i> Reports the
631: * start of a CDATA section.
632: *
633: * @throws SAXException The application may raise an exception.
634: */
635: public void startCDATA() throws SAXException {
636: this .ensureInitialization();
637: this .saxHandler.startCDATA();
638: }
639:
640: /**
641: * <i>[SAX LexicalHandler interface support]</i> Reports the end
642: * of a CDATA section.
643: *
644: * @throws SAXException The application may raise an exception.
645: */
646: public void endCDATA() throws SAXException {
647: this .saxHandler.endCDATA();
648: }
649:
650: /**
651: * <i>[SAX LexicalHandler interface support]</i> Reports an XML
652: * comment anywhere in the document.
653: *
654: * @param ch an array holding the characters in the comment.
655: * @param start the starting position in the array.
656: * @param length the number of characters to use from the array.
657: *
658: * @throws SAXException The application may raise an exception.
659: */
660: public void comment(char ch[], int start, int length)
661: throws SAXException {
662: this.ensureInitialization();
663: this.saxHandler.comment(ch, start, length);
664: }
665: }
666: }
|