2015-04-05 43 views
2

我想要一個XML文件,結構嚴密,大小約爲一半,並從中創建另一個XML文件,其中只包含原始文件的選定元素。基於Java中的另一種XML創建XML

1)我該怎麼做?

2)可以用DOM Parser完成嗎?什麼是DOM解析器的大小限制?

謝謝!

+0

考慮使用XSLT,它允許您編寫一個模板(以XML形式),作爲提取所需元素和/或屬性的配方,然後將其作爲新文檔(如果需要,可以使用XML)寫出。過去我使用[Saxon](http://saxon.sourceforge.net/)來執行此操作(使用命令行腳本而非Java應用程序)。 – Bobulous 2015-04-05 19:26:18

+0

您可能更喜歡按順序閱讀文件,只保存實際需要的元素。通過這種策略,您將不需要分配內存來存儲和操作0.5GB文件。你可以用SAX解析器來做到這一點。您也可以在Java中使用Stax。 – helderdarocha 2015-04-05 19:39:44

回答

2

如果您有一個非常大的源XML(例如您的0.5 GB文件),並希望從中提取信息,可能創建一個新的XML,您可以考慮使用基於事件的解析器,它不需要加載整個內存中的XML。這些實現中最簡單的是SAX解析器,它需要你編寫一個事件監聽器來捕獲你正在閱讀的數據(名稱爲document-start,element-start,element-end等等)元素,屬性等),並決定是否要忽略它或者對數據做些什麼。

使用JAXP搜索SAX教程,您應該找到幾個示例。您可能要考慮的另一種策略,取決於您想要做的是StAX。

下面是一個簡單的示例,它使用SAX從XML文件讀取數據並根據搜索條件提取一些信息。這是我用來教SAX處理的一個非常簡單的例子。我認爲這可能有助於你理解它是如何工作的。搜索標準是硬連線的,由電影導演的名字組成,用巨大的XML搜索從IMDB數據生成的電影選擇。

XML源例如( 「source.xml」 〜300MB文件)

<Movies> 
    ... 
    <Movie> 
     <Imdb>tt1527186</Imdb> 
     <Title>Melancholia</Title> 
     <Director>Lars von Trier</Director> 
     <Year>2011</Year> 
     <Duration>136</Duration> 
    </Movie> 
    <Movie> 
     <Imdb>tt0060390</Imdb> 
     <Title>Fahrenheit 451</Title> 
     <Director>François Truffaut</Director> 
     <Year>1966</Year> 
     <Duration>112</Duration> 
    </Movie> 
    <Movie> 
     <Imdb>tt0062622</Imdb> 
     <Title>2001: A Space Odyssey</Title> 
     <Director>Stanley Kubrick</Director> 
     <Year>1968</Year> 
     <Duration>160</Duration> 
    </Movie> 
    ... 
</Movies> 

這裏是事件處理程序的一個例子。它通過匹配字符串來選擇Movie元素。我擴展了DefaultHandler並實現了startElement()(在找到開始標記時調用),characters()(在讀取一個字符塊時調用),endElement()(在發現結束標記時調用)和endDocument()(在文檔完成時調用一次)。由於讀取的數據不會保留在內存中,因此您必須保存您自己感興趣的數據。我使用了一些布爾標誌和實例變量保存當前標籤,當前數據等

class ExtractMovieSaxHandler extends DefaultHandler { 

    // These are some parameters for the search which will select 
    // the subtrees (they will receive data when we set up the parser) 
    private String tagToMatch; 
    private String tagContents; // OR match 
    private boolean strict = false; // if strict matches will be exact 

    /** 
    * Sets criteria to select and copy Movie elements from source XML. 
    * 
    * @param tagToMatch Must contain text only 
    * @param tagContents Text contents of the tag 
    * @param strict If true, match must be exact 
    */ 
    public void setSearchCriteria(String tagToMatch, String tagContents, boolean strict) { 
     this.tagToMatch = tagToMatch; 
     this.tagContents = tagContents; 
     this.strict = strict; 
    } 

    // These are the temporary values we store as we parse the file 
    private String currentElement; 
    private StringBuilder contents = null; // if not null we are in Movie tag 
    private String currentData; 
    List<String> result = new ArrayList<String>(); // store resulting nodes here 
    private boolean skip = false; 

... 

這些方法都是ContentHandler的實施。第一個檢測到一個元素被找到(開始標記)。我們在一個變量保存標記(Movie子)的名字,因爲它可能是一個我們在搜索中使用:

... 

    @Override 
    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { 

     // Store the current element that started now 
     currentElement = qName; 

     // If this is a Movie tag, save the contents because we might need it 
     if (qName.equals("Movie")) { 
      contents = new StringBuilder(); 
     } 

    } 
...  

這一個被稱爲每個字符塊被稱爲時間。我們檢查這些字符是否發生在我們感興趣的元素內。如果是,我們匹配內容並保存,如果匹配。

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

     // if we discovered that we don't need this data, we skip it 
     if (skip || currentElement == null) { 
      return; 
     } 

     // If we are inside the tag we want to search, save the contents 
     currentData = new String(ch, start, length); 

     if (currentElement.equals(tagToMatch)) { 
      boolean discard = true; 

      if (strict) { 
       if (currentData.equals(tagContents)) { // exact match 
        discard = false; 
       } 

      } else { 
       if (currentData.toLowerCase().indexOf(tagContents.toLowerCase()) >= 0) { // matches occurrence of substring 
        discard = false; 
       } 
      } 

      if (discard) { 
       skip = true; 
      } 
     } 

    } 
...  

當找到結束標籤時調用它。如果我們願意,我們現在可以將它附加到我們正在建立的文檔中。

... 
    @Override 
    public void endElement(String uri, String localName, String qName) throws SAXException { 

     // Rebuild the XML if it's a node we didn't skip 
     if (qName.equals("Movie")) { 
      if (!skip) { 
       result.add(contents.insert(0, "<Movie>").append("</Movie>").toString()); 
      } 

      // reset the variables so we can check the next node 
      contents = null; 
      skip = false; 
     } else if (contents != null && !skip) { 
      contents.append("<").append(qName).append(">") 
        .append(currentData) 
        .append("</").append(qName).append(">"); 
     } 

     currentElement = null; 
    } 
...  

最後,在文檔結束時調用這個函數。我也用它在最後打印結果。

... 
    @Override 
    public void endDocument() throws SAXException { 
     StringBuilder resultFile = new StringBuilder(); 
     resultFile.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 
     resultFile.append("<Movies>"); 
     for (String childNode : result) { 
      resultFile.append(childNode.toString()); 
     } 
     resultFile.append("</Movies>"); 

     System.out.println("=== Resulting XML containing Movies where " + tagToMatch + " is one of " + tagContents + " ==="); 
     System.out.println(resultFile.toString()); 
    } 

} 

這是一個加載該文件並使用事件處理程序提取數據的小型Java應用程序。

public class SAXReaderExample { 

    public static final String PATH = "src/main/resources"; // this is where I put the XML file 

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException { 

     // Obtain XML Reader 
     SAXParserFactory spf = SAXParserFactory.newInstance(); 
     SAXParser sp = spf.newSAXParser(); 
     XMLReader reader = sp.getXMLReader(); 

     // Instantiate SAX handler 
     ExtractMovieSaxHandler handler = new ExtractMovieSaxHandler(); 

     // set search criteria 
     handler.setSearchCriteria("Director", "Kubrick", false); 

     // Register handler with XML reader 
     reader.setContentHandler(handler); 

     // Parse the XML 
     reader.parse(new InputSource(new FileInputStream(new File(PATH, "source.xml")))); 
    } 
} 

這裏是生成的文件,處理後:

<?xml version="1.0" encoding="UTF-8"?> 
<Movies> 
    <Movie> 
     <Imdb>tt0062622</Imdb> 
     <Title>2001: A Space Odyssey</Title> 
     <Director>Stanley Kubrick</Director> 
     <Year>1968</Year> 
     <Duration>160</Duration> 
    </Movie> 
    <Movie> 
     <Imdb>tt0066921</Imdb> 
     <Title>A Clockwork Orange</Title> 
     <Director>Stanley Kubrick</Director> 
     <Year>1972</Year> 
     <Duration>136</Duration> 
    </Movie> 
    <Movie> 
     <Imdb>tt0081505</Imdb> 
     <Title>The Shining</Title> 
     <Director>Stanley Kubrick</Director> 
     <Year>1980</Year> 
     <Duration>144</Duration> 
    </Movie> 
    ... 
</Movies> 

你的情況可能會有所不同,但這個例子顯示了你也許可以適應您的問題的通用解決方案。您可以在關於SAX和JAXP的教程中找到更多信息。

1

500Mb在使用XSLT可以實現的範圍內。這取決於您想花費多少努力來開發最佳解決方案:即哪個更貴,您的時間或機器的時間?

+0

好吧,很明顯,機器的時間更加廣泛,因爲在我完成開發之後,它會按照我的解決方案進行工作:) 雖然,我的問題不是關於XSLT的限制,而是關於上下文中DOM的限制大小... – theexplorer 2015-04-06 07:53:21

+0

我看不出爲什麼你想要使用DOM。如果您使用XSLT處理器,它將構建內存樹,但大多數XSLT處理器具有比DOM更經濟的內部樹表示形式。 – 2015-04-06 18:31:18

+0

我只想知道DOM的限制,我沒有說我想要使用它...我之前並不瞭解XSLT,但我現在正在調查它。我的問題依然存在 - 任何人都可以提供關於DOM解析器文件大小限制的信息嗎? (用於教育目的)謝謝! – theexplorer 2015-04-06 19:53:16