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