使用单个 XSL 流将一个大型 XML 文件拆分为多个文件

las*_*ero 5 xml xslt saxon

我的目标是将包含各种内容(大约 2 到 15 GB)的大型单个 XML 文件拆分为多个 XML 文件,每个文件包含特定的实体类型,例如稍后可以通过 SQL 数据库导入该实体类型。我目前使用Saxon-EE 版本 9.5.1.2J,但任何其他 XSL 处理器只要它能够快速可靠地完成工作,就可以了。

这是我已经弄清楚的:

  • Saxon似乎是 XSLT 3.0 事实上的标准处理器,而 Raptor XML 服务器似乎是另一个(更昂贵)的选择。其他 XSL 处理器通常只支持 XSLT 1.0。
  • 可以使用XSLT 3.0 流处理大文件,因此不需要将整个文件放入内存中。注意:此功能仅在 Saxon Enterprise Edition 中可用。
  • 您可以使用<xsl:result-document>它将输出写入不同的文件,但不能同一样式表中多次使用它来写入同一文件(显然不是线程安全的)。
  • <xsl:for-each-group>使用 group-by 显然是不可流式传输的
  • <xsl:stream>只能包含一个 <xsl:iterate>块,这是可以的。但是:在该迭代块内,您只能访问当前节点和一个子节点的属性(甚至<xsl:for-each>适用于该一个节点)。如果您尝试访问第二个节点的值,则会收到“SXST0060:多个子表达式消耗输入流”错误。
  • <xsl:apply-templates>inside <xsl:stream>(而不是迭代)需要可流模式(如下所示)。但是,与迭代一样,该流只能使用一次 - 否则您还会收到错误“SXST0060:多个子表达式使用输入流”。

我的结论是,当前可用的 XSL 处理器需要使用多个<xsl:stream>标记来写入不同的文件,这实际上意味着为每个输出文件多次扫描大型输入文件。当将不同实体写入同一输出文件作为解决方法时,情况更是如此,因为不可能多次“使用”同一输入流:

<xsl:mode name="s" streamable="yes"/>

<xsl:template match="/">
    <xsl:stream href="input.xml">
        <xsl:apply-templates mode="s" select="content/articles"/>
    </xsl:stream>

    <xsl:stream href="input.xml">
        <xsl:apply-templates mode="s" select="content/articles/article/authors"/>
    </xsl:stream>
</xsl:template>
Run Code Online (Sandbox Code Playgroud)

使用解释的且更复杂的命令行脚本从大型 XML 文件中提取不同实体的速度更快,因此相比之下,XSLT 变得缓慢且无用:(

我希望有一个基于 XSLT 3.0 的解决方案能够按预期工作,而无需多次扫描输入文件?我不认为 XSLT 的基本技术限制会阻止这种用例。

las*_*ero 3

这个问题实际上很容易解决:使用copy-of()使您能够访问单个迭代块内的节点和所有子节点(例如name下面的示例):

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml"
                omit-xml-declaration="no"
                encoding="UTF-8"
                indent="yes"/>

    <xsl:template match="/">
        <xsl:stream href="input.xml">
            <resultset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <xsl:iterate select="content/articles/article">
                    <xsl:for-each select="copy-of()/.">
                        <xsl:apply-templates select="."/>
                        <xsl:apply-templates select="authors/author"/>
                    </xsl:for-each>
                </xsl:iterate>
            </resultset>
        </xsl:stream>
    </xsl:template>

    <xsl:template match="article">
        ...
    </xsl:template>

    <xsl:template match="author">
        ...
    </xsl:template>
</xsl:stylesheet>
Run Code Online (Sandbox Code Playgroud)

注意:直接放入 copy-of()<xsl:iterate>不起作用,对于大型文档,您将收到 OutOfMemoryError 错误。不需要流模式。

通过这种方式,Saxon 在我的 MacBook Air 上每分钟可以处理大约 1 GB 的 XML。现在,我仍然将所有实体写入同一个输出文件,但 MySQL 可以过滤哪些节点导入到每个表中(http://dev.mysql.com/doc/refman/5.5/en/load-xml. html),所以解决方法不是主要问题。如果您知道如何将输出写入交替输出文件,请告诉我。

我还直接从 Michael Kay (Saxonica) 那里得到了关于这个问题的反馈:

是的,一旦您进行了多次向下选择,您就需要使用 copy-of() 手动组织一些数据缓冲。我希望在撒克逊找到其他放宽限制的方法,让事情变得更容易一些。