1 /*
2 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
3 *
4 * This software is open source.
5 * See the bottom of this file for the licence.
6 */
7
8 package org.dom4j.io;
9
10 import java.io.Externalizable;
11 import java.io.IOException;
12 import java.io.ObjectInput;
13 import java.io.ObjectOutput;
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Map;
19
20 import org.dom4j.Namespace;
21 import org.dom4j.QName;
22 import org.xml.sax.Attributes;
23 import org.xml.sax.ContentHandler;
24 import org.xml.sax.DTDHandler;
25 import org.xml.sax.SAXException;
26 import org.xml.sax.ext.DeclHandler;
27 import org.xml.sax.ext.LexicalHandler;
28 import org.xml.sax.helpers.AttributesImpl;
29 import org.xml.sax.helpers.DefaultHandler;
30
31 /**
32 * <p>
33 * Records SAX events such that they may be "replayed" at a later time. Provides
34 * an alternative serialization approach when externalizing a DOM4J document.
35 * Rather than serializing a document as text and re-parsing, the sax events may
36 * be serialized instead.
37 * </p>
38 * Example usage:
39 *
40 * <pre>
41 *
42 *
43 *
44 * SAXEventRecorder recorder = new SAXEventRecorder();
45 * SAXWriter saxWriter = new SAXWriter(recorder, recorder);
46 * saxWriter.write(document);
47 * out.writeObject(recorder);
48 * ...
49 * SAXEventRecorder recorder = (SAXEventRecorder)in.readObject();
50 * SAXContentHandler saxContentHandler = new SAXContentHandler();
51 * recorder.replay(saxContentHandler);
52 * Document document = saxContentHandler.getDocument();
53 *
54 *
55 *
56 * </pre>
57 *
58 * @author Todd Wolff (Bluestem Software)
59 */
60 public class SAXEventRecorder extends DefaultHandler implements LexicalHandler,
61 DeclHandler, DTDHandler, Externalizable {
62 public static final long serialVersionUID = 1;
63
64 private static final byte STRING = 0;
65
66 private static final byte OBJECT = 1;
67
68 private static final byte NULL = 2;
69
70 private List events = new ArrayList();
71
72 private Map prefixMappings = new HashMap();
73
74 private static final String XMLNS = "xmlns";
75
76 private static final String EMPTY_STRING = "";
77
78 public SAXEventRecorder() {
79 }
80
81 public void replay(ContentHandler handler) throws SAXException {
82 SAXEvent saxEvent;
83 Iterator itr = events.iterator();
84
85 while (itr.hasNext()) {
86 saxEvent = (SAXEvent) itr.next();
87
88 switch (saxEvent.event) {
89 // replay to ContentHandler
90 case SAXEvent.PROCESSING_INSTRUCTION:
91 handler.processingInstruction((String) saxEvent.getParm(0),
92 (String) saxEvent.getParm(1));
93
94 break;
95
96 case SAXEvent.START_PREFIX_MAPPING:
97 handler.startPrefixMapping((String) saxEvent.getParm(0),
98 (String) saxEvent.getParm(1));
99
100 break;
101
102 case SAXEvent.END_PREFIX_MAPPING:
103 handler.endPrefixMapping((String) saxEvent.getParm(0));
104
105 break;
106
107 case SAXEvent.START_DOCUMENT:
108 handler.startDocument();
109
110 break;
111
112 case SAXEvent.END_DOCUMENT:
113 handler.endDocument();
114
115 break;
116
117 case SAXEvent.START_ELEMENT:
118
119 AttributesImpl attributes = new AttributesImpl();
120 List attParmList = (List) saxEvent.getParm(3);
121
122 if (attParmList != null) {
123 Iterator attsItr = attParmList.iterator();
124
125 while (attsItr.hasNext()) {
126 String[] attParms = (String[]) attsItr.next();
127 attributes.addAttribute(attParms[0], attParms[1],
128 attParms[2], attParms[3], attParms[4]);
129 }
130 }
131
132 handler.startElement((String) saxEvent.getParm(0),
133 (String) saxEvent.getParm(1), (String) saxEvent
134 .getParm(2), attributes);
135
136 break;
137
138 case SAXEvent.END_ELEMENT:
139 handler.endElement((String) saxEvent.getParm(0),
140 (String) saxEvent.getParm(1), (String) saxEvent
141 .getParm(2));
142
143 break;
144
145 case SAXEvent.CHARACTERS:
146
147 char[] chars = (char[]) saxEvent.getParm(0);
148 int start = ((Integer) saxEvent.getParm(1)).intValue();
149 int end = ((Integer) saxEvent.getParm(2)).intValue();
150 handler.characters(chars, start, end);
151
152 break;
153
154 // replay to LexicalHandler
155 case SAXEvent.START_DTD:
156 ((LexicalHandler) handler).startDTD((String) saxEvent
157 .getParm(0), (String) saxEvent.getParm(1),
158 (String) saxEvent.getParm(2));
159
160 break;
161
162 case SAXEvent.END_DTD:
163 ((LexicalHandler) handler).endDTD();
164
165 break;
166
167 case SAXEvent.START_ENTITY:
168 ((LexicalHandler) handler).startEntity((String) saxEvent
169 .getParm(0));
170
171 break;
172
173 case SAXEvent.END_ENTITY:
174 ((LexicalHandler) handler).endEntity((String) saxEvent
175 .getParm(0));
176
177 break;
178
179 case SAXEvent.START_CDATA:
180 ((LexicalHandler) handler).startCDATA();
181
182 break;
183
184 case SAXEvent.END_CDATA:
185 ((LexicalHandler) handler).endCDATA();
186
187 break;
188
189 case SAXEvent.COMMENT:
190
191 char[] cchars = (char[]) saxEvent.getParm(0);
192 int cstart = ((Integer) saxEvent.getParm(1)).intValue();
193 int cend = ((Integer) saxEvent.getParm(2)).intValue();
194 ((LexicalHandler) handler).comment(cchars, cstart, cend);
195
196 break;
197
198 // replay to DeclHandler
199 case SAXEvent.ELEMENT_DECL:
200 ((DeclHandler) handler).elementDecl((String) saxEvent
201 .getParm(0), (String) saxEvent.getParm(1));
202
203 break;
204
205 case SAXEvent.ATTRIBUTE_DECL:
206 ((DeclHandler) handler).attributeDecl((String) saxEvent
207 .getParm(0), (String) saxEvent.getParm(1),
208 (String) saxEvent.getParm(2), (String) saxEvent
209 .getParm(3), (String) saxEvent.getParm(4));
210
211 break;
212
213 case SAXEvent.INTERNAL_ENTITY_DECL:
214 ((DeclHandler) handler).internalEntityDecl(
215 (String) saxEvent.getParm(0), (String) saxEvent
216 .getParm(1));
217
218 break;
219
220 case SAXEvent.EXTERNAL_ENTITY_DECL:
221 ((DeclHandler) handler).externalEntityDecl(
222 (String) saxEvent.getParm(0), (String) saxEvent
223 .getParm(1), (String) saxEvent.getParm(2));
224
225 break;
226
227 default:
228 throw new SAXException("Unrecognized event: "
229 + saxEvent.event);
230 }
231 }
232 }
233
234 // ContentHandler interface
235 // -------------------------------------------------------------------------
236 public void processingInstruction(String target, String data)
237 throws SAXException {
238 SAXEvent saxEvent = new SAXEvent(SAXEvent.PROCESSING_INSTRUCTION);
239 saxEvent.addParm(target);
240 saxEvent.addParm(data);
241 events.add(saxEvent);
242 }
243
244 public void startPrefixMapping(String prefix, String uri)
245 throws SAXException {
246 SAXEvent saxEvent = new SAXEvent(SAXEvent.START_PREFIX_MAPPING);
247 saxEvent.addParm(prefix);
248 saxEvent.addParm(uri);
249 events.add(saxEvent);
250 }
251
252 public void endPrefixMapping(String prefix) throws SAXException {
253 SAXEvent saxEvent = new SAXEvent(SAXEvent.END_PREFIX_MAPPING);
254 saxEvent.addParm(prefix);
255 events.add(saxEvent);
256 }
257
258 public void startDocument() throws SAXException {
259 SAXEvent saxEvent = new SAXEvent(SAXEvent.START_DOCUMENT);
260 events.add(saxEvent);
261 }
262
263 public void endDocument() throws SAXException {
264 SAXEvent saxEvent = new SAXEvent(SAXEvent.END_DOCUMENT);
265 events.add(saxEvent);
266 }
267
268 public void startElement(String namespaceURI, String localName,
269 String qualifiedName, Attributes attributes) throws SAXException {
270 SAXEvent saxEvent = new SAXEvent(SAXEvent.START_ELEMENT);
271 saxEvent.addParm(namespaceURI);
272 saxEvent.addParm(localName);
273 saxEvent.addParm(qualifiedName);
274
275 QName qName = null;
276 if (namespaceURI != null) {
277 qName = new QName(localName, Namespace.get(namespaceURI));
278 } else {
279 qName = new QName(localName);
280 }
281
282 if ((attributes != null) && (attributes.getLength() > 0)) {
283 List attParmList = new ArrayList(attributes.getLength());
284 String[] attParms = null;
285
286 for (int i = 0; i < attributes.getLength(); i++) {
287
288 String attLocalName = attributes.getLocalName(i);
289
290 if (attLocalName.startsWith(XMLNS)) {
291
292 // if SAXWriter is writing a DOMDocument, namespace
293 // decls are treated as attributes. record a start
294 // prefix mapping event
295 String prefix = null;
296 if (attLocalName.length() > 5) {
297 prefix = attLocalName.substring(6);
298 } else {
299 prefix = EMPTY_STRING;
300 }
301
302 SAXEvent prefixEvent = new SAXEvent(
303 SAXEvent.START_PREFIX_MAPPING);
304 prefixEvent.addParm(prefix);
305 prefixEvent.addParm(attributes.getValue(i));
306 events.add(prefixEvent);
307
308 // 'register' the prefix so that we can generate
309 // an end prefix mapping event within endElement
310 List prefixes = (List) prefixMappings.get(qName);
311 if (prefixes == null) {
312 prefixes = new ArrayList();
313 prefixMappings.put(qName, prefixes);
314 }
315 prefixes.add(prefix);
316
317 } else {
318
319 attParms = new String[5];
320 attParms[0] = attributes.getURI(i);
321 attParms[1] = attLocalName;
322 attParms[2] = attributes.getQName(i);
323 attParms[3] = attributes.getType(i);
324 attParms[4] = attributes.getValue(i);
325 attParmList.add(attParms);
326
327 }
328
329 }
330
331 saxEvent.addParm(attParmList);
332 }
333
334 events.add(saxEvent);
335 }
336
337 public void endElement(String namespaceURI, String localName, String qName)
338 throws SAXException {
339
340 SAXEvent saxEvent = new SAXEvent(SAXEvent.END_ELEMENT);
341 saxEvent.addParm(namespaceURI);
342 saxEvent.addParm(localName);
343 saxEvent.addParm(qName);
344 events.add(saxEvent);
345
346 // check to see if a we issued a start prefix mapping event
347 // for DOMDocument namespace decls
348
349 QName elementName = null;
350 if (namespaceURI != null) {
351 elementName = new QName(localName, Namespace.get(namespaceURI));
352 } else {
353 elementName = new QName(localName);
354 }
355
356 List prefixes = (List) prefixMappings.get(elementName);
357 if (prefixes != null) {
358 Iterator itr = prefixes.iterator();
359 while (itr.hasNext()) {
360 SAXEvent prefixEvent =
361 new SAXEvent(SAXEvent.END_PREFIX_MAPPING);
362 prefixEvent.addParm(itr.next());
363 events.add(prefixEvent);
364 }
365 }
366
367 }
368
369 public void characters(char[] ch, int start, int end) throws SAXException {
370 SAXEvent saxEvent = new SAXEvent(SAXEvent.CHARACTERS);
371 saxEvent.addParm(ch);
372 saxEvent.addParm(new Integer(start));
373 saxEvent.addParm(new Integer(end));
374 events.add(saxEvent);
375 }
376
377 // LexicalHandler interface
378 // -------------------------------------------------------------------------
379 public void startDTD(String name, String publicId, String systemId)
380 throws SAXException {
381 SAXEvent saxEvent = new SAXEvent(SAXEvent.START_DTD);
382 saxEvent.addParm(name);
383 saxEvent.addParm(publicId);
384 saxEvent.addParm(systemId);
385 events.add(saxEvent);
386 }
387
388 public void endDTD() throws SAXException {
389 SAXEvent saxEvent = new SAXEvent(SAXEvent.END_DTD);
390 events.add(saxEvent);
391 }
392
393 public void startEntity(String name) throws SAXException {
394 SAXEvent saxEvent = new SAXEvent(SAXEvent.START_ENTITY);
395 saxEvent.addParm(name);
396 events.add(saxEvent);
397 }
398
399 public void endEntity(String name) throws SAXException {
400 SAXEvent saxEvent = new SAXEvent(SAXEvent.END_ENTITY);
401 saxEvent.addParm(name);
402 events.add(saxEvent);
403 }
404
405 public void startCDATA() throws SAXException {
406 SAXEvent saxEvent = new SAXEvent(SAXEvent.START_CDATA);
407 events.add(saxEvent);
408 }
409
410 public void endCDATA() throws SAXException {
411 SAXEvent saxEvent = new SAXEvent(SAXEvent.END_CDATA);
412 events.add(saxEvent);
413 }
414
415 public void comment(char[] ch, int start, int end) throws SAXException {
416 SAXEvent saxEvent = new SAXEvent(SAXEvent.COMMENT);
417 saxEvent.addParm(ch);
418 saxEvent.addParm(new Integer(start));
419 saxEvent.addParm(new Integer(end));
420 events.add(saxEvent);
421 }
422
423 // DeclHandler interface
424 // -------------------------------------------------------------------------
425 public void elementDecl(String name, String model) throws SAXException {
426 SAXEvent saxEvent = new SAXEvent(SAXEvent.ELEMENT_DECL);
427 saxEvent.addParm(name);
428 saxEvent.addParm(model);
429 events.add(saxEvent);
430 }
431
432 public void attributeDecl(String eName, String aName, String type,
433 String valueDefault, String value) throws SAXException {
434 SAXEvent saxEvent = new SAXEvent(SAXEvent.ATTRIBUTE_DECL);
435 saxEvent.addParm(eName);
436 saxEvent.addParm(aName);
437 saxEvent.addParm(type);
438 saxEvent.addParm(valueDefault);
439 saxEvent.addParm(value);
440 events.add(saxEvent);
441 }
442
443 public void internalEntityDecl(String name, String value)
444 throws SAXException {
445 SAXEvent saxEvent = new SAXEvent(SAXEvent.INTERNAL_ENTITY_DECL);
446 saxEvent.addParm(name);
447 saxEvent.addParm(value);
448 events.add(saxEvent);
449 }
450
451 public void externalEntityDecl(String name, String publicId, String sysId)
452 throws SAXException {
453 SAXEvent saxEvent = new SAXEvent(SAXEvent.EXTERNAL_ENTITY_DECL);
454 saxEvent.addParm(name);
455 saxEvent.addParm(publicId);
456 saxEvent.addParm(sysId);
457 events.add(saxEvent);
458 }
459
460 public void writeExternal(ObjectOutput out) throws IOException {
461 if (events == null) {
462 out.writeByte(NULL);
463 } else {
464 out.writeByte(OBJECT);
465 out.writeObject(events);
466 }
467 }
468
469 public void readExternal(ObjectInput in) throws ClassNotFoundException,
470 IOException {
471 if (in.readByte() != NULL) {
472 events = (List) in.readObject();
473 }
474 }
475
476 // SAXEvent inner class
477 // -------------------------------------------------------------------------
478 static class SAXEvent implements Externalizable {
479 public static final long serialVersionUID = 1;
480
481 static final byte PROCESSING_INSTRUCTION = 1;
482
483 static final byte START_PREFIX_MAPPING = 2;
484
485 static final byte END_PREFIX_MAPPING = 3;
486
487 static final byte START_DOCUMENT = 4;
488
489 static final byte END_DOCUMENT = 5;
490
491 static final byte START_ELEMENT = 6;
492
493 static final byte END_ELEMENT = 7;
494
495 static final byte CHARACTERS = 8;
496
497 static final byte START_DTD = 9;
498
499 static final byte END_DTD = 10;
500
501 static final byte START_ENTITY = 11;
502
503 static final byte END_ENTITY = 12;
504
505 static final byte START_CDATA = 13;
506
507 static final byte END_CDATA = 14;
508
509 static final byte COMMENT = 15;
510
511 static final byte ELEMENT_DECL = 16;
512
513 static final byte ATTRIBUTE_DECL = 17;
514
515 static final byte INTERNAL_ENTITY_DECL = 18;
516
517 static final byte EXTERNAL_ENTITY_DECL = 19;
518
519 protected byte event;
520
521 protected List parms;
522
523 public SAXEvent() {
524 }
525
526 SAXEvent(byte event) {
527 this.event = event;
528 }
529
530 void addParm(Object parm) {
531 if (parms == null) {
532 parms = new ArrayList(3);
533 }
534
535 parms.add(parm);
536 }
537
538 Object getParm(int index) {
539 if ((parms != null) && (index < parms.size())) {
540 return parms.get(index);
541 } else {
542 return null;
543 }
544 }
545
546 public void writeExternal(ObjectOutput out) throws IOException {
547 out.writeByte(event);
548
549 if (parms == null) {
550 out.writeByte(NULL);
551 } else {
552 out.writeByte(OBJECT);
553 out.writeObject(parms);
554 }
555 }
556
557 public void readExternal(ObjectInput in) throws ClassNotFoundException,
558 IOException {
559 event = in.readByte();
560
561 if (in.readByte() != NULL) {
562 parms = (List) in.readObject();
563 }
564 }
565 }
566 }
567
568 /*
569 * Redistribution and use of this software and associated documentation
570 * ("Software"), with or without modification, are permitted provided that the
571 * following conditions are met:
572 *
573 * 1. Redistributions of source code must retain copyright statements and
574 * notices. Redistributions must also contain a copy of this document.
575 *
576 * 2. Redistributions in binary form must reproduce the above copyright notice,
577 * this list of conditions and the following disclaimer in the documentation
578 * and/or other materials provided with the distribution.
579 *
580 * 3. The name "DOM4J" must not be used to endorse or promote products derived
581 * from this Software without prior written permission of MetaStuff, Ltd. For
582 * written permission, please contact dom4j-info@metastuff.com.
583 *
584 * 4. Products derived from this Software may not be called "DOM4J" nor may
585 * "DOM4J" appear in their names without prior written permission of MetaStuff,
586 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
587 *
588 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
589 *
590 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
591 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
592 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
593 * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
594 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
595 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
596 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
597 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
598 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
599 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
600 * POSSIBILITY OF SUCH DAMAGE.
601 *
602 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
603 */