1   
2   
3   
4   
5   
6   
7   
8   package org.dom4j.io;
9   
10  import java.io.File;
11  import java.io.FileWriter;
12  import java.io.IOException;
13  import java.io.OutputStream;
14  import java.io.StringWriter;
15  import java.io.Writer;
16  import java.util.Iterator;
17  
18  import javax.xml.namespace.QName;
19  import javax.xml.stream.XMLEventFactory;
20  import javax.xml.stream.XMLOutputFactory;
21  import javax.xml.stream.XMLStreamException;
22  import javax.xml.stream.events.Characters;
23  import javax.xml.stream.events.DTD;
24  import javax.xml.stream.events.EndDocument;
25  import javax.xml.stream.events.EndElement;
26  import javax.xml.stream.events.EntityReference;
27  import javax.xml.stream.events.ProcessingInstruction;
28  import javax.xml.stream.events.StartDocument;
29  import javax.xml.stream.events.StartElement;
30  import javax.xml.stream.util.XMLEventConsumer;
31  
32  import org.dom4j.Attribute;
33  import org.dom4j.Branch;
34  import org.dom4j.CDATA;
35  import org.dom4j.Comment;
36  import org.dom4j.Document;
37  import org.dom4j.DocumentType;
38  import org.dom4j.Element;
39  import org.dom4j.Entity;
40  import org.dom4j.Namespace;
41  import org.dom4j.Node;
42  import org.dom4j.Text;
43  
44  /***
45   * Writes DOM4J {@link Node}s to a StAX event stream. In addition the
46   * <code>createXXX</code> methods are provided to directly create STAX events
47   * from DOM4J nodes.
48   * 
49   * @author Christian Niles
50   */
51  public class STAXEventWriter {
52      /*** The event stream to which events are written. */
53      private XMLEventConsumer consumer;
54  
55      /*** The event factory used to construct events. */
56      private XMLEventFactory factory = XMLEventFactory.newInstance();
57  
58      private XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
59  
60      public STAXEventWriter() {
61      }
62  
63      /***
64       * Constructs a <code>STAXEventWriter</code> that writes events to the
65       * provided file.
66       * 
67       * @param file
68       *            The file to which events will be written.
69       * 
70       * @throws XMLStreamException
71       *             If an error occurs creating an event writer from the file.
72       * @throws IOException
73       *             If an error occurs openin the file for writing.
74       */
75      public STAXEventWriter(File file) throws XMLStreamException, IOException {
76          consumer = outputFactory.createXMLEventWriter(new FileWriter(file));
77      }
78  
79      /***
80       * Constructs a <code>STAXEventWriter</code> that writes events to the
81       * provided character stream.
82       * 
83       * @param writer
84       *            The character stream to which events will be written.
85       * 
86       * @throws XMLStreamException
87       *             If an error occurs constructing an event writer from the
88       *             character stream.
89       */
90      public STAXEventWriter(Writer writer) throws XMLStreamException {
91          consumer = outputFactory.createXMLEventWriter(writer);
92      }
93  
94      /***
95       * Constructs a <code>STAXEventWriter</code> that writes events to the
96       * provided stream.
97       * 
98       * @param stream
99       *            The output stream to which events will be written.
100      * 
101      * @throws XMLStreamException
102      *             If an error occurs constructing an event writer from the
103      *             stream.
104      */
105     public STAXEventWriter(OutputStream stream) throws XMLStreamException {
106         consumer = outputFactory.createXMLEventWriter(stream);
107     }
108 
109     /***
110      * Constructs a <code>STAXEventWriter</code> that writes events to the
111      * provided event stream.
112      * 
113      * @param consumer
114      *            The event stream to which events will be written.
115      */
116     public STAXEventWriter(XMLEventConsumer consumer) {
117         this.consumer = consumer;
118     }
119 
120     /***
121      * Returns a reference to the underlying event consumer to which events are
122      * written.
123      * 
124      * @return The underlying event consumer to which events are written.
125      */
126     public XMLEventConsumer getConsumer() {
127         return consumer;
128     }
129 
130     /***
131      * Sets the underlying event consumer to which events are written.
132      * 
133      * @param consumer
134      *            The event consumer to which events should be written.
135      */
136     public void setConsumer(XMLEventConsumer consumer) {
137         this.consumer = consumer;
138     }
139 
140     /***
141      * Returns a reference to the event factory used to construct STAX events.
142      * 
143      * @return The event factory used to construct STAX events.
144      */
145     public XMLEventFactory getEventFactory() {
146         return factory;
147     }
148 
149     /***
150      * Sets the event factory used to construct STAX events.
151      * 
152      * @param eventFactory
153      *            The new event factory.
154      */
155     public void setEventFactory(XMLEventFactory eventFactory) {
156         this.factory = eventFactory;
157     }
158 
159     /***
160      * Writes a DOM4J {@link Node}to the stream. This method is simply a
161      * gateway to the overloaded methods such as {@link#writeElement(Element)}.
162      * 
163      * @param n
164      *            The DOM4J {@link Node}to write to the stream.
165      * 
166      * @throws XMLStreamException
167      *             If an error occurs writing to the stream.
168      */
169     public void writeNode(Node n) throws XMLStreamException {
170         switch (n.getNodeType()) {
171             case Node.ELEMENT_NODE:
172                 writeElement((Element) n);
173 
174                 break;
175 
176             case Node.TEXT_NODE:
177                 writeText((Text) n);
178 
179                 break;
180 
181             case Node.ATTRIBUTE_NODE:
182                 writeAttribute((Attribute) n);
183 
184                 break;
185 
186             case Node.NAMESPACE_NODE:
187                 writeNamespace((Namespace) n);
188 
189                 break;
190 
191             case Node.COMMENT_NODE:
192                 writeComment((Comment) n);
193 
194                 break;
195 
196             case Node.CDATA_SECTION_NODE:
197                 writeCDATA((CDATA) n);
198 
199                 break;
200 
201             case Node.PROCESSING_INSTRUCTION_NODE:
202                 writeProcessingInstruction((org.dom4j.ProcessingInstruction) n);
203 
204                 break;
205 
206             case Node.ENTITY_REFERENCE_NODE:
207                 writeEntity((Entity) n);
208 
209                 break;
210 
211             case Node.DOCUMENT_NODE:
212                 writeDocument((Document) n);
213 
214                 break;
215 
216             case Node.DOCUMENT_TYPE_NODE:
217                 writeDocumentType((DocumentType) n);
218 
219                 break;
220 
221             default:
222                 throw new XMLStreamException("Unsupported DOM4J Node: " + n);
223         }
224     }
225 
226     /***
227      * Writes each child node within the provided {@link Branch}instance. This
228      * method simply iterates through the {@link Branch}'s nodes and calls
229      * {@link #writeNode(Node)}.
230      * 
231      * @param branch
232      *            The node whose children will be written to the stream.
233      * 
234      * @throws XMLStreamException
235      *             If an error occurs writing to the stream.
236      */
237     public void writeChildNodes(Branch branch) throws XMLStreamException {
238         for (int i = 0, s = branch.nodeCount(); i < s; i++) {
239             Node n = branch.node(i);
240             writeNode(n);
241         }
242     }
243 
244     /***
245      * Writes a DOM4J {@link Element}node and its children to the stream.
246      * 
247      * @param elem
248      *            The {@link Element}node to write to the stream.
249      * 
250      * @throws XMLStreamException
251      *             If an error occurs writing to the stream.
252      */
253     public void writeElement(Element elem) throws XMLStreamException {
254         consumer.add(createStartElement(elem));
255         writeChildNodes(elem);
256         consumer.add(createEndElement(elem));
257     }
258 
259     /***
260      * Constructs a STAX {@link StartElement}event from a DOM4J {@link
261      * Element}.
262      * 
263      * @param elem
264      *            The {@link Element}from which to construct the event.
265      * 
266      * @return The newly constructed {@link StartElement}event.
267      */
268     public StartElement createStartElement(Element elem) {
269         
270         QName tagName = createQName(elem.getQName());
271 
272         
273         Iterator attrIter = new AttributeIterator(elem.attributeIterator());
274         Iterator nsIter = new NamespaceIterator(elem.declaredNamespaces()
275                 .iterator());
276 
277         
278         return factory.createStartElement(tagName, attrIter, nsIter);
279     }
280 
281     /***
282      * Constructs a STAX {@link EndElement}event from a DOM4J {@link Element}.
283      * 
284      * @param elem
285      *            The {@link Element}from which to construct the event.
286      * 
287      * @return The newly constructed {@link EndElement}event.
288      */
289     public EndElement createEndElement(Element elem) {
290         QName tagName = createQName(elem.getQName());
291         Iterator nsIter = new NamespaceIterator(elem.declaredNamespaces()
292                 .iterator());
293 
294         return factory.createEndElement(tagName, nsIter);
295     }
296 
297     /***
298      * Writes a DOM4J {@link Attribute}to the stream.
299      * 
300      * @param attr
301      *            The {@link Attribute}to write to the stream.
302      * 
303      * @throws XMLStreamException
304      *             If an error occurs writing to the stream.
305      */
306     public void writeAttribute(Attribute attr) throws XMLStreamException {
307         consumer.add(createAttribute(attr));
308     }
309 
310     /***
311      * Constructs a STAX {@link javax.xml.stream.events.Attribute}event from a
312      * DOM4J {@link Attribute}.
313      * 
314      * @param attr
315      *            The {@link Attribute}from which to construct the event.
316      * 
317      * @return The newly constructed {@link javax.xml.stream.events.Attribute}
318      *         event.
319      */
320     public javax.xml.stream.events.Attribute createAttribute(Attribute attr) {
321         QName attrName = createQName(attr.getQName());
322         String value = attr.getValue();
323 
324         return factory.createAttribute(attrName, value);
325     }
326 
327     /***
328      * Writes a DOM4J {@link Namespace}to the stream.
329      * 
330      * @param ns
331      *            The {@link Namespace}to write to the stream.
332      * 
333      * @throws XMLStreamException
334      *             If an error occurs writing to the stream.
335      */
336     public void writeNamespace(Namespace ns) throws XMLStreamException {
337         consumer.add(createNamespace(ns));
338     }
339 
340     /***
341      * Constructs a STAX {@link javax.xml.stream.events.Namespace}event from a
342      * DOM4J {@link Namespace}.
343      * 
344      * @param ns
345      *            The {@link Namespace}from which to construct the event.
346      * 
347      * @return The constructed {@link javax.xml.stream.events.Namespace}event.
348      */
349     public javax.xml.stream.events.Namespace createNamespace(Namespace ns) {
350         String prefix = ns.getPrefix();
351         String uri = ns.getURI();
352 
353         return factory.createNamespace(prefix, uri);
354     }
355 
356     /***
357      * Writes a DOM4J {@link Text}to the stream.
358      * 
359      * @param text
360      *            The {@link Text}to write to the stream.
361      * 
362      * @throws XMLStreamException
363      *             If an error occurs writing to the stream.
364      */
365     public void writeText(Text text) throws XMLStreamException {
366         consumer.add(createCharacters(text));
367     }
368 
369     /***
370      * Constructs a STAX {@link Characters}event from a DOM4J {@link Text}.
371      * 
372      * @param text
373      *            The {@link Text}from which to construct the event.
374      * 
375      * @return The constructed {@link Characters}event.
376      */
377     public Characters createCharacters(Text text) {
378         return factory.createCharacters(text.getText());
379     }
380 
381     /***
382      * Writes a DOM4J {@link CDATA}to the event stream.
383      * 
384      * @param cdata
385      *            The {@link CDATA}to write to the stream.
386      * 
387      * @throws XMLStreamException
388      *             If an error occurs writing to the stream.
389      */
390     public void writeCDATA(CDATA cdata) throws XMLStreamException {
391         consumer.add(createCharacters(cdata));
392     }
393 
394     /***
395      * Constructs a STAX {@link Characters}event from a DOM4J {@link CDATA}.
396      * 
397      * @param cdata
398      *            The {@link CDATA}from which to construct the event.
399      * 
400      * @return The newly constructed {@link Characters}event.
401      */
402     public Characters createCharacters(CDATA cdata) {
403         return factory.createCData(cdata.getText());
404     }
405 
406     /***
407      * Writes a DOM4J {@link Comment}to the stream.
408      * 
409      * @param comment
410      *            The {@link Comment}to write to the stream.
411      * 
412      * @throws XMLStreamException
413      *             If an error occurs writing to the stream.
414      */
415     public void writeComment(Comment comment) throws XMLStreamException {
416         consumer.add(createComment(comment));
417     }
418 
419     /***
420      * Constructs a STAX {@link javax.xml.stream.events.Comment}event from a
421      * DOM4J {@link Comment}.
422      * 
423      * @param comment
424      *            The {@link Comment}from which to construct the event.
425      * 
426      * @return The constructed {@link javax.xml.stream.events.Comment}event.
427      */
428     public javax.xml.stream.events.Comment createComment(Comment comment) {
429         return factory.createComment(comment.getText());
430     }
431 
432     /***
433      * Writes a DOM4J {@link ProcessingInstruction}to the stream.
434      * 
435      * @param pi
436      *            The {@link ProcessingInstruction}to write to the stream.
437      * 
438      * @throws XMLStreamException
439      *             If an error occurs writing to the stream.
440      */
441     public void writeProcessingInstruction(org.dom4j.ProcessingInstruction pi)
442             throws XMLStreamException {
443         consumer.add(createProcessingInstruction(pi));
444     }
445 
446     /***
447      * Constructs a STAX {@link javax.xml.stream.events.ProcessingInstruction}
448      * event from a DOM4J {@link ProcessingInstruction}.
449      * 
450      * @param pi
451      *            The {@link ProcessingInstruction}from which to construct the
452      *            event.
453      * 
454      * @return The constructed {@link
455      *         javax.xml.stream.events.ProcessingInstruction} event.
456      */
457     public ProcessingInstruction createProcessingInstruction(
458             org.dom4j.ProcessingInstruction pi) {
459         String target = pi.getTarget();
460         String data = pi.getText();
461 
462         return factory.createProcessingInstruction(target, data);
463     }
464 
465     /***
466      * Writes a DOM4J {@link Entity}to the stream.
467      * 
468      * @param entity
469      *            The {@link Entity}to write to the stream.
470      * 
471      * @throws XMLStreamException
472      *             If an error occurs writing to the stream.
473      */
474     public void writeEntity(Entity entity) throws XMLStreamException {
475         consumer.add(createEntityReference(entity));
476     }
477 
478     /***
479      * Constructs a STAX {@link EntityReference}event from a DOM4J {@link
480      * Entity}.
481      * 
482      * @param entity
483      *            The {@link Entity}from which to construct the event.
484      * 
485      * @return The constructed {@link EntityReference}event.
486      */
487     private EntityReference createEntityReference(Entity entity) {
488         return factory.createEntityReference(entity.getName(), null);
489     }
490 
491     /***
492      * Writes a DOM4J {@link DocumentType}to the stream.
493      * 
494      * @param docType
495      *            The {@link DocumentType}to write to the stream.
496      * 
497      * @throws XMLStreamException
498      *             If an error occurs writing to the stream.
499      */
500     public void writeDocumentType(DocumentType docType)
501             throws XMLStreamException {
502         consumer.add(createDTD(docType));
503     }
504 
505     /***
506      * Constructs a STAX {@link DTD}event from a DOM4J {@link DocumentType}.
507      * 
508      * @param docType
509      *            The {@link DocumentType}from which to construct the event.
510      * 
511      * @return The constructed {@link DTD}event.
512      * 
513      * @throws RuntimeException
514      *             DOCUMENT ME!
515      */
516     public DTD createDTD(DocumentType docType) {
517         StringWriter decl = new StringWriter();
518 
519         try {
520             docType.write(decl);
521         } catch (IOException e) {
522             throw new RuntimeException("Error writing DTD", e);
523         }
524 
525         return factory.createDTD(decl.toString());
526     }
527 
528     /***
529      * Writes a DOM4J {@link Document}node, and all its contents, to the
530      * stream.
531      * 
532      * @param doc
533      *            The {@link Document}to write to the stream.
534      * 
535      * @throws XMLStreamException
536      *             If an error occurs writing to the stream.
537      */
538     public void writeDocument(Document doc) throws XMLStreamException {
539         consumer.add(createStartDocument(doc));
540 
541         writeChildNodes(doc);
542 
543         consumer.add(createEndDocument(doc));
544     }
545 
546     /***
547      * Constructs a STAX {@link StartDocument}event from a DOM4J {@link
548      * Document}.
549      * 
550      * @param doc
551      *            The {@link Document}from which to construct the event.
552      * 
553      * @return The constructed {@link StartDocument}event.
554      */
555     public StartDocument createStartDocument(Document doc) {
556         String encoding = doc.getXMLEncoding();
557 
558         if (encoding != null) {
559             return factory.createStartDocument(encoding);
560         } else {
561             return factory.createStartDocument();
562         }
563     }
564 
565     /***
566      * Constructs a STAX {@link EndDocument}event from a DOM4J {@link
567      * Document}.
568      * 
569      * @param doc
570      *            The {@link Document}from which to construct the event.
571      * 
572      * @return The constructed {@link EndDocument}event.
573      */
574     public EndDocument createEndDocument(Document doc) {
575         return factory.createEndDocument();
576     }
577 
578     /***
579      * Constructs a STAX {@link QName}from a DOM4J {@link org.dom4j.QName}.
580      * 
581      * @param qname
582      *            The {@link org.dom4j.QName}from which to construct the STAX
583      *            {@link QName}.
584      * 
585      * @return The constructed {@link QName}.
586      */
587     public QName createQName(org.dom4j.QName qname) {
588         return new QName(qname.getNamespaceURI(), qname.getName(), qname
589                 .getNamespacePrefix());
590     }
591 
592     /***
593      * Internal {@link Iterator}implementation used to pass DOM4J {@link
594      * Attribute}s to the stream.
595      */
596     private class AttributeIterator implements Iterator {
597         /*** The underlying DOm4J attribute iterator. */
598         private Iterator iter;
599 
600         public AttributeIterator(Iterator iter) {
601             this.iter = iter;
602         }
603 
604         public boolean hasNext() {
605             return iter.hasNext();
606         }
607 
608         public Object next() {
609             Attribute attr = (Attribute) iter.next();
610             QName attrName = createQName(attr.getQName());
611             String value = attr.getValue();
612 
613             return factory.createAttribute(attrName, value);
614         }
615 
616         public void remove() {
617             throw new UnsupportedOperationException();
618         }
619     }
620 
621     /***
622      * Internal {@link Iterator}implementation used to pass DOM4J {@link
623      * Namespace}s to the stream.
624      */
625     private class NamespaceIterator implements Iterator {
626         private Iterator iter;
627 
628         public NamespaceIterator(Iterator iter) {
629             this.iter = iter;
630         }
631 
632         public boolean hasNext() {
633             return iter.hasNext();
634         }
635 
636         public Object next() {
637             Namespace ns = (Namespace) iter.next();
638             String prefix = ns.getPrefix();
639             String nsURI = ns.getURI();
640 
641             return factory.createNamespace(prefix, nsURI);
642         }
643 
644         public void remove() {
645             throw new UnsupportedOperationException();
646         }
647     }
648 }
649 
650 
651 
652 
653 
654 
655 
656 
657 
658 
659 
660 
661 
662 
663 
664 
665 
666 
667 
668 
669 
670 
671 
672 
673 
674 
675 
676 
677 
678 
679 
680 
681 
682 
683 
684 
685