【问题标题】:Creating an XML based on another XML in Java在 Java 中基于另一个 XML 创建 XML
【发布时间】:2015-04-05 19:20:59
【问题描述】:

我想要一个 XML 文件,结构很重,大小大约是一半,然后从它创建另一个 XML 文件,只包含原始文件的选定元素。

1) 我该怎么做?

2) 可以用 DOM Parser 完成吗? DOM 解析器的大小限制是多少?

谢谢!

【问题讨论】:

  • 考虑使用 XSLT,它允许您编写一个模板(在 XML 中),该模板充当提取所需元素和/或属性的配方,然后将它们写成一个新文档(如果所需)。我过去曾使用Saxon 来执行此操作(使用命令行脚本而不是 Java 应用程序)。
  • 您可能更喜欢按顺序读取文件,只保存您实际需要的元素。使用此策略,您将无需分配内存来存储和操作 0.5GB 文件。您可以使用 SAX 解析器执行此操作。您还可以在 Java 中使用 Stax。

标签: java xml xslt xml-parsing


【解决方案1】:

如果您有一个非常大的源 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 的教程中找到更多信息。

【讨论】:

    【解决方案2】:

    500Mb 完全在使用 XSLT 可以实现的范围内。这在一定程度上取决于您要花费多少精力来开发最佳解决方案:即,哪个更昂贵,您的时间还是机器的时间?

    【讨论】:

    • 好吧,显然机器的时间更广泛,因为它会在我完成开发很久之后根据我的解决方案工作:) 虽然,我的问题不是关于 XSLT 的限制,但是在大小的上下文中的 DOM...
    • 我不明白你为什么要使用 DOM。如果您使用 XSLT 处理器,它将构建内存中的树,但大多数 XSLT 处理器具有比 DOM 更经济的内部树表示。
    • 我只是想知道DOM的极限,我没有说我想用它......我之前不知道XSLT,但现在正在调查它。我的问题仍然存在 - 任何人都可以提供有关 DOM 解析器文件大小限制的信息吗? (出于教育目的)谢谢!
    • 这取决于 DOM 实现和 XML 的详细结构,但您可能需要 5*N 到 10*N 之间的内存,其中 N 是原始文档大小。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-23
    • 2012-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多