4

我有一种情况,我想从一些非常大但常规的 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>

本质上,我想选择一些 m 节点(我知道 XPath 是错误的,这只是一个快速破解),但要保持 XML 布局。


编辑:看来 STX 可能是我正在寻找的(我可以使用另一种转换语言),并且 Joost 是其中的一种实现。有什么经验吗?


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


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

4

10 回答 10

6

考虑VTD-XML。它的内存效率更高您可以在此处找到 API并在此处找到基准。

替代文字

请注意,最后一张图表明 DOM 使用的内存至少是 XML 文件大的 5 倍。毕竟真的很惊人,不是吗?

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

替代文字

替代文字
(来源:sourceforge.net

于 2009-12-17T13:56:41.677 回答
2

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

根据您的 XML 示例,您正在寻找匹配 /log/e/m[ ... predicate ...]。一些 XSLT 处理器应该能够对其进行优化,使其不扫描 // 不会扫描的完整文档。

由于您的 XML 文档非常简单,因此根本不使用 XSLT 可能更容易。STaX是用于处理大型 XML 文档的出色流式 API。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
...
于 2009-12-17T13:46:34.797 回答
1

我有同样的问题,不想写任何 Java 代码。我设法通过 Joost 用 STX 解决了这个问题。

根据规格

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 规则!

于 2013-01-14T12:14:27.233 回答
1

Saxon XSLT 处理器企业版支持大型文档的流式传输,正是这种类型的问题。

于 2009-12-17T14:27:23.060 回答
0

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

于 2009-12-17T14:13:27.003 回答
0

正如已经建议的那样,您可以通过 STX/Joost 执行此操作,但请注意,许多 XSLT 实现具有 SAX 流模式并且不需要将所有内容都保存在内存中。您只需要确保您的 XSLT 文件没有查看任何错误的轴。

但是,如果我是你并且真的想要性能,我会在 STaX 中做到这一点。它简单、标准且快速。它在 java 6 中开箱即用,尽管您也可以使用 Woodstox 来获得更好的实现。

对于您列出的 xpath,实现是微不足道的。缺点是您需要维护更多代码,而且它不像在 Joost 或 XSLT 中那样具有 XPath 的表现力和高级别的。

于 2010-06-15T09:26:35.657 回答
0

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

没有什么能阻止您将 SAX 源连接到 XSLT 的输入;并且至少在理论上很容易从 SAX 流中执行 grep 而无需 DOM。所以...想试试吗?

于 2009-12-17T13:50:46.010 回答
0

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

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

于 2009-12-18T13:29:05.693 回答
0

我不是 Java 人,我不知道我在 .NET 中用来执行此操作的工具在 Java 世界中是否有类似物。

为了在 .NET 中解决这个问题,我会从 中派生一个类XmlReader,并让它只返回我感兴趣的元素。然后我可以XmlReader将 子类基本上对输入流进行了预处理,使其看起来像一个非常小得多的 XML 文档,无论是使用它从中读取的任何类。XmlDocumentXslCompiledTransformXmlReader

看起来这里描述的技术是类似的。但正如我所说,我不是 Java 人。

于 2009-12-18T07:19:50.667 回答
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();
于 2019-03-08T19:23:56.710 回答