如何用DOM解析大型xml文档?

时间:2018-05-08 10:08:00

标签: java xml

我想解析一个包含以下事件的xml元素:

  • 且没有xml声明
  • 可以按照特定顺序提供元素
<employees>
   <employee>
      <details>
         <name>Joe</name>
         <age>34</age>
      </details>
      <address>
         <street>test</street>
         <nr>12</nr>
      </address>
   </employee>
   <employee>
      <address>....</address>
      <details>
         <!-- note the changed order of elements! -->
         <age>24</age>
         <name>Sam</name>
      </details>
   </employee>
</employees>

输出应该是csv:

name;age;street;nr
Joe,34,test,12
Sam,24,...

问题:当使用像stax/sax这样的事件驱动的解析器时,我必须创建一个临时的Employee bean,它的属性我在每个事件节点上设置,然后将bean转换为csv。

但是由于我的xml文件大小为几GB,我想防止为每个条目创建额外的bean对象。

因此我可能不得不使用普通的DOM解析?纠正我,如果我错了,我很高兴有任何建议。

我尝试如下。问题是doc.getElementsByTagName("employees")返回一个空节点列表,而我期望一个xml元素。为什么呢?

StringBuilder sb = new StringBuilder();

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xml)));
doc.getDocumentElement().normalize();

NodeList employees = doc.getElementsByTagName("employees");
for (int i = 0; i < employees.getLength(); i++) {
    Node employee = employees.item(i);
    if (employees.getNodeType() == Node.ELEMENT_NODE) {
        NodeList employee = ((Element) employees).getElementsByTagName("employee");
        for (int j = 0; j < employee.getLength(); j++) {
            NodeList details = ((Element) employee).getElementsByTagName("details");

            //the rest is pseudocode
            for (details)
                sb.append(getElements("name").item(0) + ",");
                sb.append(getElements("age").item(0) + ",");    

            for (address) 
                sb.append(getElements("street").item(0) + ",");
                sb.append(getElements("nr").item(0) + ",");
        }
    }
}

2 个答案:

答案 0 :(得分:3)

DOM解决方案将使用大量内存,SAX / Stax解决方案将涉及编写和调试大量代码。这项工作的理想工具是XSLT 3.0可流式转换:

<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:mode streamable="yes" on-no-match="shallow-skip"/>
  <xsl:template match="employee">
    <xsl:value-of select="copy-of(.)!(.//name, .//age, .//street, .//nr)" 
                  separator=","/>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>
</xsl:transform>

注意

我最初将选择表达式编写为copy-of(.)//(name, age, street, nr)。这是不正确的,因为//运算符会将结果排序为文档顺序,这是我们不想要的。使用!,会小心避免排序。

答案 1 :(得分:1)

不要使用StringBuilder,而是立即写入文件(Files.newBufferedWriter)。

手动解析XML并不是什么大问题,因为似乎没有高度复杂性,也不需要基于XML的验证。

  • DOM解析会构建一个文档对象模型,正是您不想要的。
  • 如果子元素无序,Stax需要构建一个完整的员工。
  • 因此,自己阅读一名员工并不会那么不同。
  • 此外,XML似乎不是源于XML编写,可能需要修补XML无效文本,例如&,它应该是XML中的&amp;

如果XML有效(您可以使用前面添加<?xml ...>的Reader),则扫描XML将是:

XMLInputFactory f = XMLInputFactory.newInstance();
XMLStreamReader r = f.createXMLStreamReader( ... );
while(r.hasNext()) {
    r.next();
}

这样可以轻松地为员工属性维护一个Map,从<employee>开始到结束,在</employee>进行验证和编写。