無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

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[]{ "&", "&amp;"} );
        add( new String[]{ "<", "&lt;"} );
        add( new String[]{ ">", "&gt;"} );
    }};
    @SuppressWarnings("serial")
    static final List<String[]> ATRIBUTES_ENTITIES = new LinkedList<String[]>() {{
        addAll( ENTITIES );
        add( new String[]{ "'",  "&apos;"} );
        add( new String[]{ "\"", "&quot;"} );
        add( new String[]{ "\r", "&#x0D;"} );
        add( new String[]{ "\n", "&#x0A;"} );
        add( new String[]{ "\t", "&#x09;"} );
    }};

    public void setDocumentLocator(Locator locator) {
    }
}