使用XSLT进行XML提取而不必将整个DOM树读入内存?

时间:2009-12-17 13:42:12

标签: java xml xslt streaming stx

我有一种情况,我想从一些非常大但常规的XML文件中提取一些信息(只需要使用500 Mb文件),以及XSLT将是完美的。

不幸的是,我所知道的那些XSLT实现(除了最昂贵的Saxon版本之外)不支持只读取DOM的必要部分,而是读取整个树。这导致计算机交换死亡。

有问题的XPath是

//m/e[contains(.,'foobar')

所以它基本上只是一个grep。

是否有可以执行此操作的XSLT实现?或者给出合适的“建议”的XSLT实现可以修复内存中不再需要的部分吗?

我更喜欢Java实现,但Windows和Linux都是可行的原生平台。


编辑:输入XML如下所示:

<log>
<!-- Fri Jun 26 12:09:27 CEST 2009 -->
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>Registering Catalina:type=Manager,path=/axsWHSweb-20090626,host=localhost</m></e>
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>Force random number initialization starting</m></e>
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>Getting message digest component for algorithm MD5</m></e>
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>Completed getting message digest component</m></e>
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>getDigest() 0</m></e>
......
</log>

Essentialy我想选择一些m节点(我知道XPath是错误的,它只是一个快速的黑客),但保持XML布局。


编辑:似乎STX可能是我正在寻找的东西(我可以使用另一种转换语言),而Joost是其实现。任何经历?


编辑:我发现带有-Xmx1500m的Saxon 6.5.4可以加载我的XML,所以这让我现在可以使用我的XPath。这只是一个幸运的因素,所以我仍然想要解决这个问题 - 这意味着可编写脚本,这反过来意味着没有手工制作的Java过滤。


编辑:哦,顺便说一下。这是一个非常类似于log4j XMLLayout生成的日志文件。 XML的原因是能够做到这一点,即对日志进行查询。这是最初的尝试,因此是一个简单的问题。后来我希望能够提出更复杂的问题 - 因此我希望查询语言能够处理输入文件。

10 个答案:

答案 0 :(得分:6)

考虑VTD-XML更多更多内存效率。您可以找到API here和基准here

alt text

请注意,最后一个图表说DOM使用的内存至少是XML文件的5倍。毕竟这真是令人惊讶,不是吗?

作为奖励,它在解析和Xpath方面也比DOM和JDK更快:

alt text

alt text
(来源:sourceforge.net

答案 1 :(得分:2)

您应该能够在没有全表扫描的情况下实现此功能。 '//'运算符意味着在任何级别的树中查找元素。特别是在你的尺寸的文件上运行是非常昂贵的。如果优化XPath查询或考虑设置匹配模板,则XSLT转换器可能不需要将整个文档加载到内存中。

根据您的XML示例,您希望匹配/ log / e / m [...谓词...]。这应该能够被某些XSLT处理器优化,以便不扫描//不会在哪里的完整文档。

由于您的XML文档非常简单,因此根本不使用XSLT可能更容易。 STaX是一个很棒的流API,用于处理大型XML文档。 Dom4j也很好地支持对大型文档进行查询的XPath。有关在大型文档中使用dom4j的信息,请访问:http://dom4j.sourceforge.net/dom4j-1.6.1/faq.html#large-doc

以上来源的示例:

SAXReader reader = new SAXReader();
reader.addHandler( "/ROWSET/ROW", 
    new ElementHandler() {
        public void onStart(ElementPath path) {
            // do nothing here...    
        }
        public void onEnd(ElementPath path) {
            // process a ROW element
            Element row = path.getCurrent();
            Element rowSet = row.getParent();
            Document document = row.getDocument();
            ...
            // prune the tree
            row.detach();
        }
    }
);

Document document = reader.read(url);

// The document will now be complete but all the ROW elements
// will have been pruned.
// We may want to do some final processing now
...

答案 2 :(得分:1)

答案 3 :(得分:1)

我遇到了同样的问题,并且不想编写任何Java代码。我设法通过Joost解决了这个问题。

根据spec

  

STX进程可能会将大型XML文档拆分为较小的片段,   将这些片段中的每一个传递给外部过滤器(例如   XSLT处理器),并将结果组合成一个大的XML结果   文档。

这正是我所需要的。我拥有的最大XML文件示例是1.5 GB,我有一个XSLT模板来处理它。使用Saxon免费版时,处理时消耗了 3GB 内存。使用Joost时,小于90MB

我的XML文件包含大量产品列表,每个产品都有一个复杂的XML结构。所以我不想在STX中重新实现我的XSLT,但只想分割每个产品的处理,同时为每个产品使用相同的XSLT。

以下是代码详细信息,希望对某些人有所帮助。

原始XSLT文件(这是我实现的第一个XSLT,很抱歉for-each语句使用不当):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
  <xsl:template match="/">
    <xsl:for-each select="Products/Product">
      <!-- Some XSL statements relative to "Product" element -->
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

我将其转换为以下STX:

<?xml version="1.0" encoding="UTF-8"?>

<stx:transform version="1.0"
    output-method="text"
    output-encoding="UTF-8"
    xmlns:stx="http://stx.sourceforge.net/2002/ns">

  <stx:buffer name="xslt-product">

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
      <xsl:template match="Product">
        <!-- The same XSL statements relative to "Product" element -->
      </xsl:template>
    </xsl:stylesheet>

  </stx:buffer>

  <stx:template match="/">
    <stx:process-children />
  </stx:template>

  <stx:template match="Product">
    <stx:process-self filter-method="http://www.w3.org/1999/XSL/Transform"
                      filter-src="buffer(xslt-product)" />
  </stx:template>

</stx:transform>

当运行Joost时,我仍然需要添加Saxon库,因为我在XSLT中使用了函数,所以我需要XSLT 2.0支持。最后,运行转换的命令是这样的:

java -Djavax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl -cp joost.jar:commons-discovery-0.5.jar:commons-logging-1.1.1.jar:saxon9he.jar net.sf.joost.Main my-source.xml my-convert.stx

最重要的是,现在我可以在低内存服务器上运行转换,而无需实现任何Java代码或重新实现原始XSLT规则!

答案 4 :(得分:0)

这是在黑暗中刺伤,也许你会笑我出门。

没有什么可以阻止您将SAX源连接到XSLT的输入;至少在理论上它很容易从SAX流中执行grep而不需要DOM。那么......想尝试一下吗?

答案 5 :(得分:0)

从xponentsoftware尝试CAX解析器。它是一个基于Microsoft的xmlreader构建的快速xml解析器。它在解析每个元素时给出完整路径,因此您可以检查路径是否为“m / e”,然后检查文本节点是否包含“foo”

答案 6 :(得分:0)

我不是Java人,我不知道我在.NET中使用的工具是否在Java世界中具有类似功能。

要在.NET中解决这个问题,我会从XmlReader派生一个类,让它只返回我感兴趣的元素。然后我可以使用XmlReader作为输入任何XML对象,例如XmlDocumentXslCompiledTransformXmlReader子类基本上预处理输入流,使其看起来像一个更小,更小的XML文档,无论什么类使用它来读取。

似乎所描述的技术here是类似的。但正如我所说,我不是一个Java人。

答案 7 :(得分:0)

STX包含一个可流式的XPath子集,我相信称为STXPath;我应该记住,因为我共同编写了规范: - )

您绝对可以选择Joost并提取相关位,但请注意,STX并未获得业界的广泛认可,因此您需要对该工具的当前稳定性和支持做一些尽职调查。

答案 8 :(得分:0)

您可以通过已建议的STX / Joost来完成,但请注意,许多XSLT实现都具有SAX流模式,并且不需要将所有内容保留在内存中。您只需要确保您的XSLT文件没有查找任何错误的轴。

但是如果我是你并且真的想要表现我会在STaX中做到。它简单,标准,快速。它在Java 6中开箱即用,尽管您也可以使用Woodstox实现更好的实现。

对于您列出的xpath,实现很简单。缺点是你需要维护更多的代码,而且它不像XPath那样富有表现力和高水平,就像你在Joost或XSLT中那样。

答案 9 :(得分:0)

编写xslt以返回首选xml布局中的值,该布局仅包含来自largeXmls所需的值。

但是,如果要进一步使用Java处理值,则:

  1. 将该简单xml转换为POJO并读取值(首选)
  2. 使用正则表达式提取值

使用StreamSource通过xslt解析xml的示例:

使用的包裹:

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;

代码:

        String xmlStr = "<A><b>value</b><c>value</c></A>";
        File xslt = new ClassPathResource("xslt/Transformer.xslt").getFile();
        Source xsltSource = new StreamSource(xslt);
        Source xmlSource = new StreamSource(new StringReader(xmlStr));
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer(xsltSource);
        StringWriter stringWriter = new StringWriter();
        transformer.transform(xmlSource, new StreamResult(stringWriter));
        String response = stringWriter.toString();