2009-12-29 38 views
2

我有一個類似於an earlier question about emitting XML的情況。我正在分析SAX ContentHandler中的數據,並將其序列化爲流。我懷疑鏈接問題中的解決方案 - 儘管正是我在尋找API的方法 - 並不是內存高效的,因爲它涉及到XSLT處理器的身份轉換。我希望程序的內存消耗是有限的,而不是隨着輸入大小的增長而增長。什麼是從JAXP SAX ContentHandler發出XML的最節省內存的方式?

如何輕鬆地將參數傳遞給我的ContentHandler方法到序列化程序,而不用雜技來適應例如StAX到SAX,或者更糟的是,將SAX事件內容複製到輸出流?

編輯:這是我之後的一個簡單例子。 thingIWant應該只寫入給它的OutputStream。就像我說的,早期的問題有一個TransformerHandler,它給了我正確的API,但它使用XSLT處理器而不是簡單的序列化。

public class MyHandler implements ContentHandler { 

    ContentHandler thingIWant; 

    MyHandler(OutputStream outputStream) { 
     thingIWant = setup(outputStream); 
    } 

    public void startDocument() throws SAXException { 
     // parsing logic 
     thingIWant.startDocument(); 
    } 

    public void startElement(String uri, String localName, String qName, 
          Attributes atts) throws SAXException { 
     // parsing logic 
     thingIWant.startElement(uri, localName, qName, atts); 
    } 

    public void characters(char[] ch, int start, int length) throws SAXException { 
     // parsing logic 
     thingIWant.characters(ch, start, length); 
    } 

    // etc... 
} 
+0

你的問題不是很清楚。你能舉個例子嗎? – 2009-12-29 23:44:23

+0

如果您擔心的是默認轉換是否構建輸出的內存中表示,請不要。只要您使用SAX源代碼,它將通過事件傳遞。然而,你似乎想要更多的東西,或許是某種「T恤」操作。如果是這種情況,請描述您的確切流程。 – kdgregory 2009-12-30 16:44:18

+0

@kdgregory:謝謝你的回答。這是一種T型操作。 SAXParser將使用MyHandler()。 MyHandler的一部分操作是將其輸入回顯到給定的OutputStream中。另一部分(在我的例子中解析邏輯)不會影響這個輸出。 – 2009-12-30 16:59:39

回答

2

第一:不要擔心身份轉換;它不會構建數據的內存中表示。

要實現「tee」功能,必須創建一個內容處理程序,用於偵聽由解析器生成的事件流,並將它們傳遞給由變換器爲您提供的處理程序。不幸的是,這聽起來並不容易:解析器想要將事件發送到DefaultHandler,而變壓器想要從XMLReader讀取事件。前者是一個抽象類,後者是一個接口。 JDK還提供了類XMLFilterImpl,該類實現了DefaultHandler的所有接口,但並未從中擴展...這就是您將兩個不同項目合併爲「參考實現」所得到的結果。

所以,你需要寫兩個之間的橋樑類:

import java.io.IOException; 
import java.io.StringReader; 
import java.io.StringWriter; 

import javax.xml.parsers.SAXParser; 
import javax.xml.parsers.SAXParserFactory; 
import javax.xml.transform.Transformer; 
import javax.xml.transform.TransformerFactory; 
import javax.xml.transform.sax.SAXSource; 
import javax.xml.transform.stream.StreamResult; 

import org.xml.sax.Attributes; 
import org.xml.sax.InputSource; 
import org.xml.sax.Locator; 
import org.xml.sax.SAXException; 
import org.xml.sax.SAXParseException; 
import org.xml.sax.XMLReader; 
import org.xml.sax.helpers.DefaultHandler; 
import org.xml.sax.helpers.XMLFilterImpl; 

/** 
* Uses a decorator ContentHandler to insert a "tee" into a SAX parse/serialize 
* stream. 
*/ 
public class SaxTeeExample 
{ 
    public static void main(String[] argv) 
    throws Exception 
    { 
     StringReader src = new StringReader("<root><child>text</child></root>"); 
     StringWriter dst = new StringWriter(); 

     Transformer xform = TransformerFactory.newInstance().newTransformer(); 
     XMLReader reader = new MyReader(SAXParserFactory.newInstance().newSAXParser()); 
     xform.transform(new SAXSource(reader, new InputSource(src)), 
         new StreamResult(dst)); 

     System.out.println(dst.toString()); 
    } 


    private static class MyReader 
    extends XMLFilterImpl 
    { 
     private SAXParser _parser; 

     public MyReader(SAXParser parser) 
     { 
      _parser = parser; 
     } 

     @Override 
     public void parse(InputSource input) 
     throws SAXException, IOException 
     { 
      _parser.parse(input, new XMLFilterBridge(this)); 
     } 

     // this is an example of a "tee" function 
     @Override 
     public void startElement(String uri, String localName, String name, Attributes atts) throws SAXException 
     { 
      System.out.println("startElement: " + name); 
      super.startElement(uri, localName, name, atts); 
     } 
    } 


    private static class XMLFilterBridge 
    extends DefaultHandler 
    { 
     private XMLFilterImpl _filter; 

     public XMLFilterBridge(XMLFilterImpl myFilter) 
     { 
      _filter = myFilter; 
     } 

     @Override 
     public void characters(char[] ch, int start, int length) 
     throws SAXException 
     { 
      _filter.characters(ch, start, length); 
     } 

     // override all other methods of DefaultHandler 
     // ... 
    } 
} 

main方法設置變壓器。有趣的部分是SAXSource圍繞MyReader構建。當變壓器準備好發生事件時,它將調用該對象的方法parse(),並將其傳遞給指定的InputSource

下一部分並不明顯:XMLFilterImpl遵循裝飾模式。在開始變換之前,變換器會在這個對象上調用各種setter方法,並傳遞它自己的處理程序。任何我不會覆蓋的方法(例如,startDocument())都會簡單地調用委託。作爲重寫的一個例子,我在startElement()中進行「分析」(只是一個println)。您可能會覆蓋其他ContentHandler方法。

最後,XMLFilterBridgeDefaultHandlerXmlReader之間的橋;它也是一個裝飾器,每個方法都只是調用委託。我展示了一個覆蓋,但你必須全部完成。

+0

順便提一句,我將在實用Xml庫(http://sourceforge.net/projects/practicalxml/develop)中添加橋接類...我將這些舊XML代碼拉到一起以獲取此答案,並且我認爲它可能通常很有用。 – kdgregory 2009-12-30 19:08:37

+0

正如我在回顧一下,我認爲'parse()'是由'XMLFilterImpl'實現的,我不需要這個橋。不幸的是,實現只是委託給一個父XMLReader。 – kdgregory 2009-12-30 19:53:15

+0

這是很棒的信息 - 但有一個問題。在規範或JDK實現本身中,SAXSource上的標識轉換是內存安全的,還是包含作爲默認JDK處理器的轉換處理器的工件?即如果有人通過JAXP配置使用特別虛假的XSL系統,這是否仍然有效? – 2009-12-31 03:11:19

1

編輯:包括默認的JDK版本

最有效的將是一個XMLWriter它實現ContentHandler。簡而言之,您正在讀取和寫入IO緩衝區。 DOM4J中有一個XMLWriter,正在使用下面。您可以子類XMLWriter或使用XMLFilter進行分析。在這個例子中我使用XMLFilter。請注意,XMLFilter也是ContentHandler。這是完整的代碼。

import org.dom4j.io.XMLWriter; 
import org.xml.sax.Attributes; 
import org.xml.sax.SAXException; 
import org.xml.sax.XMLReader; 
import org.xml.sax.helpers.XMLFilterImpl; 

import javax.xml.parsers.ParserConfigurationException; 
import javax.xml.parsers.SAXParserFactory; 
import java.io.IOException; 
import java.io.PrintStream; 

public class XMLPipeline { 

    public static void main(String[] args) throws Exception { 
     String inputFile = "build.xml"; 
     PrintStream outputStream = System.out; 
     new XMLPipeline().pipe(inputFile, outputStream); 
    } 

//dom4j 
public void pipe(String inputFile, OutputStream outputStream) throws 
     SAXException, ParserConfigurationException, IOException { 
    XMLWriter xwriter = new XMLWriter(outputStream); 
    XMLReader xreader = XMLReaderFactory.createXMLReader(); 
    XMLAnalyzer analyzer = new XMLAnalyzer(xreader); 
    analyzer.setContentHandler(xwriter); 
    analyzer.parse(inputFile); 

    //do what you want with analyzer 
    System.err.println(analyzer.elementCount); 
} 


//default JDK 
public void pipeTrax(String inputFile, OutputStream outputStream) throws 
     SAXException, ParserConfigurationException, 
     IOException, TransformerException { 
    StreamResult xwriter = new StreamResult(outputStream); 
    XMLReader xreader = XMLReaderFactory.createXMLReader(); 
    XMLAnalyzer analyzer = new XMLAnalyzer(xreader); 
    TransformerFactory stf = SAXTransformerFactory.newInstance(); 
    SAXSource ss = new SAXSource(analyzer, new InputSource(inputFile)); 
    stf.newTransformer().transform(ss, xwriter); 
    System.out.println(analyzer.elementCount); 
} 

//This method simply reads from a file, runs it through SAX parser and dumps it 
//to dom4j writer 
public void dom4jNoop(String inputFile, OutputStream outputStream) throws 
     IOException, SAXException { 
    XMLWriter xwriter = new XMLWriter(outputStream); 
    XMLReader xreader = XMLReaderFactory.createXMLReader(); 
    xreader.setContentHandler(xwriter); 
    xreader.parse(inputFile); 

} 

//Simplest way to read a file and write it back to an output stream 
public void traxNoop(String inputFile, OutputStream outputStream) 
    throws TransformerException { 
    TransformerFactory stf = SAXTransformerFactory.newInstance(); 
    stf.newTransformer().transform(new StreamSource(inputFile), 
    new StreamResult(outputStream)); 
}  
    //this analyzer counts the number of elements in sax stream 
    public static class XMLAnalyzer extends XMLFilterImpl { 
     int elementCount = 0; 

     public XMLAnalyzer(XMLReader xmlReader) { 
      super(xmlReader); 
     } 

     @Override 
     public void startElement(String uri, String localName, String qName, 
      Attributes atts) throws SAXException { 
      super.startElement(uri, localName, qName, atts); 
      elementCount++; 
     } 
    } 
} 
+0

我其實不想做任何轉換。我想解析文檔並根據其內容更新某個狀態,同時將文檔按原樣序列化到OutputStream。 – 2009-12-30 02:30:59

+0

你的編輯稍微好一些,但是我現在沒有依賴於DOM4J,並且如果我可以幫助它的話不需要。我知道特定的XML解析器和API具有XMLWriter類型的序列化器,但是我想將其限制爲javax.xml。* world。 – 2009-12-30 17:01:42

+0

我已經添加了JDK版本。 AFAIK,在JDK中打印一個XML,你需要使用變形金剛。我的微型基準測試中的JDK Transformer代碼比使用dom4j編寫器慢了2倍。如果你想要性能和不依賴,你可以很容易地編寫你的XML編寫器,這很簡單。請參閱DOM4J XMLWriter源代碼。 – 2009-12-30 18:54:59

4

我最近有一個類似的問題。這是我寫的,以獲得類,你thingIWant:

import java.io.OutputStream; 
import javax.xml.transform.Transformer; 
import javax.xml.transform.TransformerFactory; 
import javax.xml.transform.TransformerException; 
import javax.xml.transform.sax.SAXSource; 
import javax.xml.transform.stream.StreamResult; 
import org.xml.sax.*; 

public class XMLSerializer implements ContentHandler { 
    static final private TransformerFactory tf = TransformerFactory.newInstance(); 
    private ContentHandler ch; 

    public XMLSerializer(OutputStream os) throws SAXException { 
     try { 
      final Transformer t = tf.newTransformer(); 

      t.transform(new SAXSource(    
       new XMLReader() {  
        public ContentHandler getContentHandler() { return ch; } 
        public DTDHandler getDTDHandler() { return null; }  
        public EntityResolver getEntityResolver() { return null; } 
        public ErrorHandler getErrorHandler() { return null; }  
        public boolean getFeature(String name) { return false; } 
        public Object getProperty(String name) { return null; } 
        public void parse(InputSource input) { }    
        public void parse(String systemId) { } 
        public void setContentHandler(ContentHandler handler) { ch = handler; }     
        public void setDTDHandler(DTDHandler handler) { } 
        public void setEntityResolver(EntityResolver resolver) { } 
        public void setErrorHandler(ErrorHandler handler) { } 
        public void setFeature(String name, boolean value) { } 
        public void setProperty(String name, Object value) { } 
       }, new InputSource()),          
       new StreamResult(os)); 
     } 
     catch (TransformerException e) { 
      throw new SAXException(e); 
     } 

     if (ch == null) 
      throw new SAXException("Transformer didn't set ContentHandler"); 
    } 

    public void setDocumentLocator(Locator locator) { 
     ch.setDocumentLocator(locator); 
    } 

    public void startDocument() throws SAXException { 
     ch.startDocument(); 
    } 

    public void endDocument() throws SAXException { 
     ch.endDocument(); 
    } 

    public void startPrefixMapping(String prefix, String uri) throws SAXException { 
     ch.startPrefixMapping(prefix, uri); 
    } 

    public void endPrefixMapping(String prefix) throws SAXException { 
     ch.endPrefixMapping(prefix); 
    } 

    public void startElement(String uri, String localName, String qName, Attributes atts) 
     throws SAXException { 
     ch.startElement(uri, localName, qName, atts); 
    } 

    public void endElement(String uri, String localName, String qName) 
     throws SAXException { 
     ch.endElement(uri, localName, qName); 
    } 

    public void characters(char[] ch, int start, int length) 
     throws SAXException { 
     this.ch.characters(ch, start, length); 
    } 

    public void ignorableWhitespace(char[] ch, int start, int length) 
     throws SAXException { 
     this.ch.ignorableWhitespace(ch, start, length); 
    } 

    public void processingInstruction(String target, String data) 
     throws SAXException { 
     ch.processingInstruction(target, data); 
    } 

    public void skippedEntity(String name) throws SAXException { 
     ch.skippedEntity(name); 
    } 
} 

基本上,它攔截了變壓器的調用解析(),並抓住其內部的ContentHandler的參考。在那之後,這個課程就像一個受限制的ContentHandler代理。

不是很乾淨,但它的工作原理。

相關問題