You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
595 lines
20 KiB
595 lines
20 KiB
/* |
|
* Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved. |
|
* |
|
* This software is open source. |
|
* See the bottom of this file for the licence. |
|
*/ |
|
|
|
package org.dom4j.io; |
|
|
|
import java.io.Externalizable; |
|
import java.io.IOException; |
|
import java.io.ObjectInput; |
|
import java.io.ObjectOutput; |
|
import java.util.ArrayList; |
|
import java.util.HashMap; |
|
import java.util.Iterator; |
|
import java.util.List; |
|
import java.util.Map; |
|
|
|
import org.dom4j.Namespace; |
|
import org.dom4j.QName; |
|
import org.xml.sax.Attributes; |
|
import org.xml.sax.ContentHandler; |
|
import org.xml.sax.DTDHandler; |
|
import org.xml.sax.SAXException; |
|
import org.xml.sax.ext.DeclHandler; |
|
import org.xml.sax.ext.LexicalHandler; |
|
import org.xml.sax.helpers.AttributesImpl; |
|
import org.xml.sax.helpers.DefaultHandler; |
|
|
|
/** |
|
* <p> |
|
* Records SAX events such that they may be "replayed" at a later time. Provides |
|
* an alternative serialization approach when externalizing a DOM4J document. |
|
* Rather than serializing a document as text and re-parsing, the sax events may |
|
* be serialized instead. |
|
* </p> |
|
* Example usage: |
|
* |
|
* <pre> |
|
* |
|
* |
|
* |
|
* SAXEventRecorder recorder = new SAXEventRecorder(); |
|
* SAXWriter saxWriter = new SAXWriter(recorder, recorder); |
|
* saxWriter.write(document); |
|
* out.writeObject(recorder); |
|
* ... |
|
* SAXEventRecorder recorder = (SAXEventRecorder)in.readObject(); |
|
* SAXContentHandler saxContentHandler = new SAXContentHandler(); |
|
* recorder.replay(saxContentHandler); |
|
* Document document = saxContentHandler.getDocument(); |
|
* |
|
* |
|
* |
|
* </pre> |
|
* |
|
* @author Todd Wolff (Bluestem Software) |
|
*/ |
|
public class SAXEventRecorder extends DefaultHandler implements LexicalHandler, |
|
DeclHandler, DTDHandler, Externalizable { |
|
public static final long serialVersionUID = 1; |
|
|
|
private static final byte STRING = 0; |
|
|
|
private static final byte OBJECT = 1; |
|
|
|
private static final byte NULL = 2; |
|
|
|
private List<SAXEvent> events = new ArrayList<SAXEvent>(); |
|
|
|
private Map<QName, List<String>> prefixMappings = new HashMap<QName, List<String>>(); |
|
|
|
private static final String XMLNS = "xmlns"; |
|
|
|
private static final String EMPTY_STRING = ""; |
|
|
|
public SAXEventRecorder() { |
|
} |
|
|
|
public void replay(ContentHandler handler) throws SAXException { |
|
for (SAXEvent saxEvent : events) { |
|
|
|
switch (saxEvent.event) { |
|
// replay to ContentHandler |
|
case SAXEvent.PROCESSING_INSTRUCTION: |
|
handler.processingInstruction((String) saxEvent.getParm(0), |
|
(String) saxEvent.getParm(1)); |
|
|
|
break; |
|
|
|
case SAXEvent.START_PREFIX_MAPPING: |
|
handler.startPrefixMapping((String) saxEvent.getParm(0), |
|
(String) saxEvent.getParm(1)); |
|
|
|
break; |
|
|
|
case SAXEvent.END_PREFIX_MAPPING: |
|
handler.endPrefixMapping((String) saxEvent.getParm(0)); |
|
|
|
break; |
|
|
|
case SAXEvent.START_DOCUMENT: |
|
handler.startDocument(); |
|
|
|
break; |
|
|
|
case SAXEvent.END_DOCUMENT: |
|
handler.endDocument(); |
|
|
|
break; |
|
|
|
case SAXEvent.START_ELEMENT: |
|
|
|
AttributesImpl attributes = new AttributesImpl(); |
|
List<String[]> attParmList = (List<String[]>) saxEvent.getParm(3); |
|
|
|
if (attParmList != null) { |
|
for (String[] attParms : attParmList) { |
|
attributes.addAttribute(attParms[0], attParms[1], |
|
attParms[2], attParms[3], attParms[4]); |
|
} |
|
} |
|
|
|
handler.startElement((String) saxEvent.getParm(0), |
|
(String) saxEvent.getParm(1), (String) saxEvent |
|
.getParm(2), attributes); |
|
|
|
break; |
|
|
|
case SAXEvent.END_ELEMENT: |
|
handler.endElement((String) saxEvent.getParm(0), |
|
(String) saxEvent.getParm(1), (String) saxEvent |
|
.getParm(2)); |
|
|
|
break; |
|
|
|
case SAXEvent.CHARACTERS: |
|
|
|
char[] chars = (char[]) saxEvent.getParm(0); |
|
int start = (Integer) saxEvent.getParm(1); |
|
int end = (Integer) saxEvent.getParm(2); |
|
handler.characters(chars, start, end); |
|
|
|
break; |
|
|
|
// replay to LexicalHandler |
|
case SAXEvent.START_DTD: |
|
((LexicalHandler) handler).startDTD((String) saxEvent |
|
.getParm(0), (String) saxEvent.getParm(1), |
|
(String) saxEvent.getParm(2)); |
|
|
|
break; |
|
|
|
case SAXEvent.END_DTD: |
|
((LexicalHandler) handler).endDTD(); |
|
|
|
break; |
|
|
|
case SAXEvent.START_ENTITY: |
|
((LexicalHandler) handler).startEntity((String) saxEvent |
|
.getParm(0)); |
|
|
|
break; |
|
|
|
case SAXEvent.END_ENTITY: |
|
((LexicalHandler) handler).endEntity((String) saxEvent |
|
.getParm(0)); |
|
|
|
break; |
|
|
|
case SAXEvent.START_CDATA: |
|
((LexicalHandler) handler).startCDATA(); |
|
|
|
break; |
|
|
|
case SAXEvent.END_CDATA: |
|
((LexicalHandler) handler).endCDATA(); |
|
|
|
break; |
|
|
|
case SAXEvent.COMMENT: |
|
|
|
char[] cchars = (char[]) saxEvent.getParm(0); |
|
int cstart = (Integer) saxEvent.getParm(1); |
|
int cend = (Integer) saxEvent.getParm(2); |
|
((LexicalHandler) handler).comment(cchars, cstart, cend); |
|
|
|
break; |
|
|
|
// replay to DeclHandler |
|
case SAXEvent.ELEMENT_DECL: |
|
((DeclHandler) handler).elementDecl((String) saxEvent |
|
.getParm(0), (String) saxEvent.getParm(1)); |
|
|
|
break; |
|
|
|
case SAXEvent.ATTRIBUTE_DECL: |
|
((DeclHandler) handler).attributeDecl((String) saxEvent |
|
.getParm(0), (String) saxEvent.getParm(1), |
|
(String) saxEvent.getParm(2), (String) saxEvent |
|
.getParm(3), (String) saxEvent.getParm(4)); |
|
|
|
break; |
|
|
|
case SAXEvent.INTERNAL_ENTITY_DECL: |
|
((DeclHandler) handler).internalEntityDecl( |
|
(String) saxEvent.getParm(0), (String) saxEvent |
|
.getParm(1)); |
|
|
|
break; |
|
|
|
case SAXEvent.EXTERNAL_ENTITY_DECL: |
|
((DeclHandler) handler).externalEntityDecl( |
|
(String) saxEvent.getParm(0), (String) saxEvent |
|
.getParm(1), (String) saxEvent.getParm(2)); |
|
|
|
break; |
|
|
|
default: |
|
throw new SAXException("Unrecognized event: " |
|
+ saxEvent.event); |
|
} |
|
} |
|
} |
|
|
|
// ContentHandler interface |
|
// ------------------------------------------------------------------------- |
|
public void processingInstruction(String target, String data) |
|
throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.PROCESSING_INSTRUCTION); |
|
saxEvent.addParm(target); |
|
saxEvent.addParm(data); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void startPrefixMapping(String prefix, String uri) |
|
throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.START_PREFIX_MAPPING); |
|
saxEvent.addParm(prefix); |
|
saxEvent.addParm(uri); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void endPrefixMapping(String prefix) throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.END_PREFIX_MAPPING); |
|
saxEvent.addParm(prefix); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void startDocument() throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.START_DOCUMENT); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void endDocument() throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.END_DOCUMENT); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void startElement(String namespaceURI, String localName, |
|
String qualifiedName, Attributes attributes) throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.START_ELEMENT); |
|
saxEvent.addParm(namespaceURI); |
|
saxEvent.addParm(localName); |
|
saxEvent.addParm(qualifiedName); |
|
|
|
QName qName; |
|
if (namespaceURI != null) { |
|
qName = new QName(localName, Namespace.get(namespaceURI)); |
|
} else { |
|
qName = new QName(localName); |
|
} |
|
|
|
if ((attributes != null) && (attributes.getLength() > 0)) { |
|
List<String[]> attParmList = new ArrayList<String[]>(attributes.getLength()); |
|
String[] attParms; |
|
|
|
for (int i = 0; i < attributes.getLength(); i++) { |
|
|
|
String attLocalName = attributes.getLocalName(i); |
|
|
|
if (attLocalName.startsWith(XMLNS)) { |
|
|
|
// if SAXWriter is writing a DOMDocument, namespace |
|
// decls are treated as attributes. record a start |
|
// prefix mapping event |
|
String prefix; |
|
if (attLocalName.length() > 5) { |
|
prefix = attLocalName.substring(6); |
|
} else { |
|
prefix = EMPTY_STRING; |
|
} |
|
|
|
SAXEvent prefixEvent = new SAXEvent( |
|
SAXEvent.START_PREFIX_MAPPING); |
|
prefixEvent.addParm(prefix); |
|
prefixEvent.addParm(attributes.getValue(i)); |
|
events.add(prefixEvent); |
|
|
|
// 'register' the prefix so that we can generate |
|
// an end prefix mapping event within endElement |
|
List<String> prefixes = prefixMappings.get(qName); |
|
if (prefixes == null) { |
|
prefixes = new ArrayList<String>(); |
|
prefixMappings.put(qName, prefixes); |
|
} |
|
prefixes.add(prefix); |
|
|
|
} else { |
|
|
|
attParms = new String[5]; |
|
attParms[0] = attributes.getURI(i); |
|
attParms[1] = attLocalName; |
|
attParms[2] = attributes.getQName(i); |
|
attParms[3] = attributes.getType(i); |
|
attParms[4] = attributes.getValue(i); |
|
attParmList.add(attParms); |
|
|
|
} |
|
|
|
} |
|
|
|
saxEvent.addParm(attParmList); |
|
} |
|
|
|
events.add(saxEvent); |
|
} |
|
|
|
public void endElement(String namespaceURI, String localName, String qName) |
|
throws SAXException { |
|
|
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.END_ELEMENT); |
|
saxEvent.addParm(namespaceURI); |
|
saxEvent.addParm(localName); |
|
saxEvent.addParm(qName); |
|
events.add(saxEvent); |
|
|
|
// check to see if a we issued a start prefix mapping event |
|
// for DOMDocument namespace decls |
|
|
|
QName elementName; |
|
if (namespaceURI != null) { |
|
elementName = new QName(localName, Namespace.get(namespaceURI)); |
|
} else { |
|
elementName = new QName(localName); |
|
} |
|
|
|
List<String> prefixes = prefixMappings.get(elementName); |
|
if (prefixes != null) { |
|
for (String prefixe : prefixes) { |
|
SAXEvent prefixEvent = |
|
new SAXEvent(SAXEvent.END_PREFIX_MAPPING); |
|
prefixEvent.addParm(prefixe); |
|
events.add(prefixEvent); |
|
} |
|
} |
|
|
|
} |
|
|
|
public void characters(char[] ch, int start, int end) throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.CHARACTERS); |
|
saxEvent.addParm(ch); |
|
saxEvent.addParm(start); |
|
saxEvent.addParm(end); |
|
events.add(saxEvent); |
|
} |
|
|
|
// LexicalHandler interface |
|
// ------------------------------------------------------------------------- |
|
public void startDTD(String name, String publicId, String systemId) |
|
throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.START_DTD); |
|
saxEvent.addParm(name); |
|
saxEvent.addParm(publicId); |
|
saxEvent.addParm(systemId); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void endDTD() throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.END_DTD); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void startEntity(String name) throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.START_ENTITY); |
|
saxEvent.addParm(name); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void endEntity(String name) throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.END_ENTITY); |
|
saxEvent.addParm(name); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void startCDATA() throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.START_CDATA); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void endCDATA() throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.END_CDATA); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void comment(char[] ch, int start, int end) throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.COMMENT); |
|
saxEvent.addParm(ch); |
|
saxEvent.addParm(start); |
|
saxEvent.addParm(end); |
|
events.add(saxEvent); |
|
} |
|
|
|
// DeclHandler interface |
|
// ------------------------------------------------------------------------- |
|
public void elementDecl(String name, String model) throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.ELEMENT_DECL); |
|
saxEvent.addParm(name); |
|
saxEvent.addParm(model); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void attributeDecl(String eName, String aName, String type, |
|
String valueDefault, String value) throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.ATTRIBUTE_DECL); |
|
saxEvent.addParm(eName); |
|
saxEvent.addParm(aName); |
|
saxEvent.addParm(type); |
|
saxEvent.addParm(valueDefault); |
|
saxEvent.addParm(value); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void internalEntityDecl(String name, String value) |
|
throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.INTERNAL_ENTITY_DECL); |
|
saxEvent.addParm(name); |
|
saxEvent.addParm(value); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void externalEntityDecl(String name, String publicId, String sysId) |
|
throws SAXException { |
|
SAXEvent saxEvent = new SAXEvent(SAXEvent.EXTERNAL_ENTITY_DECL); |
|
saxEvent.addParm(name); |
|
saxEvent.addParm(publicId); |
|
saxEvent.addParm(sysId); |
|
events.add(saxEvent); |
|
} |
|
|
|
public void writeExternal(ObjectOutput out) throws IOException { |
|
if (events == null) { |
|
out.writeByte(NULL); |
|
} else { |
|
out.writeByte(OBJECT); |
|
out.writeObject(events); |
|
} |
|
} |
|
|
|
public void readExternal(ObjectInput in) throws ClassNotFoundException, |
|
IOException { |
|
if (in.readByte() != NULL) { |
|
events = (List<SAXEvent>) in.readObject(); |
|
} |
|
} |
|
|
|
// SAXEvent inner class |
|
// ------------------------------------------------------------------------- |
|
static class SAXEvent implements Externalizable { |
|
public static final long serialVersionUID = 1; |
|
|
|
static final byte PROCESSING_INSTRUCTION = 1; |
|
|
|
static final byte START_PREFIX_MAPPING = 2; |
|
|
|
static final byte END_PREFIX_MAPPING = 3; |
|
|
|
static final byte START_DOCUMENT = 4; |
|
|
|
static final byte END_DOCUMENT = 5; |
|
|
|
static final byte START_ELEMENT = 6; |
|
|
|
static final byte END_ELEMENT = 7; |
|
|
|
static final byte CHARACTERS = 8; |
|
|
|
static final byte START_DTD = 9; |
|
|
|
static final byte END_DTD = 10; |
|
|
|
static final byte START_ENTITY = 11; |
|
|
|
static final byte END_ENTITY = 12; |
|
|
|
static final byte START_CDATA = 13; |
|
|
|
static final byte END_CDATA = 14; |
|
|
|
static final byte COMMENT = 15; |
|
|
|
static final byte ELEMENT_DECL = 16; |
|
|
|
static final byte ATTRIBUTE_DECL = 17; |
|
|
|
static final byte INTERNAL_ENTITY_DECL = 18; |
|
|
|
static final byte EXTERNAL_ENTITY_DECL = 19; |
|
|
|
protected byte event; |
|
|
|
protected List<Object> parms; |
|
|
|
public SAXEvent() { |
|
} |
|
|
|
SAXEvent(byte event) { |
|
this.event = event; |
|
} |
|
|
|
void addParm(Object parm) { |
|
if (parms == null) { |
|
parms = new ArrayList<Object>(3); |
|
} |
|
|
|
parms.add(parm); |
|
} |
|
|
|
Object getParm(int index) { |
|
if ((parms != null) && (index < parms.size())) { |
|
return parms.get(index); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
public void writeExternal(ObjectOutput out) throws IOException { |
|
out.writeByte(event); |
|
|
|
if (parms == null) { |
|
out.writeByte(NULL); |
|
} else { |
|
out.writeByte(OBJECT); |
|
out.writeObject(parms); |
|
} |
|
} |
|
|
|
public void readExternal(ObjectInput in) throws ClassNotFoundException, |
|
IOException { |
|
event = in.readByte(); |
|
|
|
if (in.readByte() != NULL) { |
|
parms = (List<Object>) in.readObject(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* Redistribution and use of this software and associated documentation |
|
* ("Software"), with or without modification, are permitted provided that the |
|
* following conditions are met: |
|
* |
|
* 1. Redistributions of source code must retain copyright statements and |
|
* notices. Redistributions must also contain a copy of this document. |
|
* |
|
* 2. Redistributions in binary form must reproduce the above copyright notice, |
|
* this list of conditions and the following disclaimer in the documentation |
|
* and/or other materials provided with the distribution. |
|
* |
|
* 3. The name "DOM4J" must not be used to endorse or promote products derived |
|
* from this Software without prior written permission of MetaStuff, Ltd. For |
|
* written permission, please contact dom4j-info@metastuff.com. |
|
* |
|
* 4. Products derived from this Software may not be called "DOM4J" nor may |
|
* "DOM4J" appear in their names without prior written permission of MetaStuff, |
|
* Ltd. DOM4J is a registered trademark of MetaStuff, Ltd. |
|
* |
|
* 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND |
|
* ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE |
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGE. |
|
* |
|
* Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved. |
|
*/
|
|
|