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

时间:2013-09-24 13:52:49

标签: 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> with group-by显然 streamable
  • <xsl:stream>只能包含一个 <xsl:iterate>块,这没关系。但是:在迭代块中,您只能访问当前节点的属性和一个子节点(甚至<xsl:for-each>在该节点上工作)。如果您尝试访问第二个节点的值,则会出现“SXST0060:多个子表达式消耗输入流”错误。
  • {li> <xsl:apply-templates><xsl:stream>内(而不是迭代)需要模式可流化(如下所示)。但是,流只能像迭代一样使用一次 - 否则你也会得到错误“SXST0060:多个子表达式消耗输入流”。

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

<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>

在某种程度上,使用解释的和更复杂的命令行脚本从大型XML文件中提取不同的实体的速度更快 - 因此使XSLT变得缓慢而无用:(

我希望有一个基于XSLT 3.0的解决方案可以按预期工作而不会多次扫描输入文件?我没有看到XSLT的基本技术限制阻止了这种用例。

2 个答案:

答案 0 :(得分:2)

问题实际上很容易解决:使用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>

注意:直接在<xsl:iterate>中放置copy-of()不起作用,您将获得大文档的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()手动组织一些数据缓冲。我   希望找到其他方式可以放松撒克逊人的限制   以便让事情变得更容易。

答案 1 :(得分:0)

“使用简单的命令行脚本更快”

我同意。您可以从脚本调用OS和商业命令行xml分割器。 他们使用快速前向读取xml解析器,消耗很少的内存,并且可以一次完成拆分。 Google用于“拆分大型xml文件”