XMLをコピーするSAXハンドラ
今日の発掘品。XMLをコピーするSAXハンドラです。最新のXalanを使うとDTDのコピーもできる、というのは調べたけど影響範囲があれなので不採用→仕方なく自前で実装した記憶があったり。使い方は以下です。
/** * {@link XMLReader}を作成する * @param handler SAXハンドラ * @return {@link XMLReader} * @throws SAXException * @throws ParserConfigurationException */ static final <H extends DTDHandler & DeclHandler & LexicalHandler & ContentHandler> XMLReader createReader( H handler ) throws SAXException, ParserConfigurationException { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); XMLReader r = factory.newSAXParser().getXMLReader(); r.setContentHandler( handler ); r.setDTDHandler( handler ); r.setProperty( "http://xml.org/sax/properties/lexical-handler", handler ); r.setProperty( "http://xml.org/sax/properties/declaration-handler", handler ); return r; } ... // ストリームからXMLを読み込んで、出力にコピーする。 InputStream in = null; // 入力 OutputStream out = null; // 出力 try { XMLReader r = createReader( new CopyHandler( new StreamResult(out) )); r.parse( new InputSource(in) ); } finally { try { if ( in != null ) in.close(); } finally { if ( out != null ) out.close(); } }
実装は次のとおり。
import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.xml.transform.stream.StreamResult; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.DTDHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.ext.DeclHandler; import org.xml.sax.ext.LexicalHandler; /** * XMLをコピーするSAX ハンドラ。 */ class CopyHandler implements DTDHandler, DeclHandler, LexicalHandler, ContentHandler { StreamResult result; Writer w; final Map<String, String> prefixMap = new HashMap<String, String>(); boolean isCdata = false; boolean isOuterDtd = false; int outerDtdcount = 0; CopyHandler( ) {} CopyHandler( StreamResult result ) { if ( result != null ) { setResult(result); } } void setResult( StreamResult result ) { this.result = result; this.w = new OutputStreamWriter( result.getOutputStream(), Charset.forName("UTF-8") ); } public void startDTD(String name, String publicId, String systemId) throws SAXException { if ( isOuterDtd ) { outerDtdcount++; return; } try { w.write( "\n<!DOCTYPE " ); w.write( name ); w.write( " " ); if ( systemId != null ) { wExternalID( publicId, systemId ); isOuterDtd = true; } else { w.write( "[" ); } } catch ( IOException e ) { throw new SAXException( e ); } } public void endDTD() throws SAXException { if ( isOuterDtd && outerDtdcount-- > 0) { return; } if ( isOuterDtd ) { isOuterDtd = false; } else { w( "\n]" ); } w( ">\n" ); } public void attributeDecl ( String name, String name2, String type, String mode, String value ) throws SAXException { if ( isOuterDtd ) { return; } w("\n<!ATTLIST ", name, " ", name2, " ", type, " "); if ( mode != null ) { w( mode ); } if ( value != null ) { w( " \"", reverseEntityRefs( value, true), "\"" ); } w( " >" ); } public void elementDecl ( String name, String model ) throws SAXException { if ( isOuterDtd ) { return; } w("\n<!ELEMENT ", name, " ", model, " >") ; } public void externalEntityDecl ( String name, String publicId, String systemId ) throws SAXException { if ( isOuterDtd ) { return; } if ( name.startsWith("%") ) { name = "% " + name.substring(1); } w("\n<!ENTITY ", name, " "); wExternalID( publicId, systemId ); w(" >"); } public void internalEntityDecl ( String name, String value ) throws SAXException { if ( isOuterDtd ) { return; } if ( name.startsWith("%") ) { name = "% " + name.substring(1); } w("\n<!ENTITY ", name, " \"", value, "\" >" ); } public void notationDecl ( String name, String publicId, String systemId ) throws SAXException { if ( isOuterDtd ) { return; } w("\n<!NOTATION ", name, " "); if ( systemId != null ) { wExternalID( publicId, systemId ); } else { w( "PUBLIC \"", publicId, "\" " ); } w(">"); } public void unparsedEntityDecl ( String name, String publicId, String systemId, String notationName ) throws SAXException { if ( isOuterDtd ) { return; } if ( name.startsWith("%") ) { name = "% " + name.substring(1); } w("\n<!ENTITY ", name, " "); wExternalID( publicId, systemId ); w(" NDATA ", notationName, " >"); } public void characters(char[] ch, int start, int length) throws SAXException { if ( isCdata ) { w( new String( ch, start, length )); } else { w( reverseEntityRefs( new String( ch, start, length ), false)); } } public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { if ( isCdata ) { w( new String( ch, start, length )); } else { w( reverseEntityRefs( new String( ch, start, length ), false)); } } public void comment(char[] ch, int start, int length) throws SAXException { if ( isOuterDtd ) { return; } w("<!--"); w(new String( ch, start, length )); w("-->"); } public void startCDATA() throws SAXException { w("<![CDATA["); isCdata = true; } public void endCDATA() throws SAXException { w("]]>"); isCdata = false; } public void startDocument() throws SAXException { w("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); } public void endDocument() throws SAXException {} public void startEntity(String name) throws SAXException { } public void endEntity(String name) throws SAXException { } public void skippedEntity(String name) throws SAXException { } public void processingInstruction(String target, String data) throws SAXException { if ( isOuterDtd ) { return; } w("<?", target, " ", data, "?>"); } public void startElement(String uri, String localName, String name, Attributes attrs) throws SAXException { w("<", name ); for (int i = 0; i < attrs.getLength(); i++) { w( " ", attrs.getQName(i), "=\"", reverseEntityRefs(attrs.getValue(i), true), "\""); } for ( Entry<String,String> e : prefixMap.entrySet() ) { w ( " xmlns" ); if ( e.getKey().length() > 0 ) w( ":", e.getKey() ); w( "=\"", reverseEntityRefs(e.getValue(), true), "\""); } prefixMap.clear(); w(">"); } public void endElement(String uri, String localName, String name) throws SAXException { w("</", name, ">"); } public void startPrefixMapping(String prefix, String uri) throws SAXException { prefixMap.put(prefix, uri); } public void endPrefixMapping(String prefix) throws SAXException {} private void wExternalID( String publicId, String systemId ) throws SAXException { if ( publicId != null ) { w( "PUBLIC \"", publicId, "\"\n " ); } else { w( "SYSTEM "); } w( "\"", systemId, "\"" ); } private void w( String... str ) throws SAXException { for ( String s : str ) { try { w.write(s); } catch (IOException e) { throw new SAXException( e ); } } try { w.flush(); } catch (IOException e) { throw new SAXException( e ); } } String reverseEntityRefs( String str, boolean isAtttributes ) { for ( String[] r : isAtttributes ? ATRIBUTES_ENTITIES : ENTITIES ) { str = str.replace(r[0], r[1]); } return str; } @SuppressWarnings("serial") static final List<String[]> ENTITIES = new LinkedList<String[]>() {{ add( new String[]{ "&", "&"} ); add( new String[]{ "<", "<"} ); add( new String[]{ ">", ">"} ); }}; @SuppressWarnings("serial") static final List<String[]> ATRIBUTES_ENTITIES = new LinkedList<String[]>() {{ addAll( ENTITIES ); add( new String[]{ "'", "'"} ); add( new String[]{ "\"", """} ); add( new String[]{ "\r", "
"} ); add( new String[]{ "\n", "
"} ); add( new String[]{ "\t", "	"} ); }}; public void setDocumentLocator(Locator locator) { } }