将巨型XML文件拆分为n个子版本

时间:2018-07-11 21:16:53

标签: xml xslt sql-server-openxml

例如,巨大的文件有5000万行,例如:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<root>
  <activity>
    <deliv>
      <subitem1>text</subitem1>
      <subitem2>text</subitem2>
    </deliv>
    <deliv>
      <subitem1>text</subitem1>
      <subitem2>text</subitem2>
    </deliv>
    <deliv>
      <subitem1>text</subitem1>
      <subitem2>text</subitem2>
    </deliv>
  </activity>
</root>

每个“子”文件将具有相同的结构,但大约为500万行,或原始文件的1/10。

这样做的原因是使这样的导入到数据库中更易于管理,而不会耗尽内存(SQL Server的OPENXML)。

这里XSLT是最好的选择吗?

3 个答案:

答案 0 :(得分:2)

XSLT-2.0及更高版本非常适合此任务。
XSLT-3.0甚至支持流式传输。

以下样式表使用xsl:result-document将XML文件拆分为可配置的文件数量。

它带有两个参数:

  • split-每个分组中的项目数
  • doc-源文档的名称

这是为您的示例(split.xslt)自定义的XSLT-2.0模板:

<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xsl:param name="split" select="2" />        <!-- number of entries in each split -->
  <xsl:param name="doc" select="'src.xml'" />  <!-- name of source XML --> 

  <xsl:template match="/">  
    <xsl:variable name="cnt" select="xs:integer(count(document($doc)/root/activity/deliv) div xs:integer($split))" />    
    <xsl:value-of select="concat('#',$cnt,'#')" />
    <xsl:for-each select="0 to $cnt">
        <xsl:variable name="cur" select="xs:integer(.)" /> 
        <xsl:result-document method="xml" href="output_no_{$cur}.xml" exclude-result-prefixes="xs">
            <root>
                <activity>
                    <xsl:for-each select="document($doc)/root/activity/deliv[position() gt (xs:integer($split) * $cur) and position() le (xs:integer($split) * ($cur + 1))]">
                        <xsl:copy-of select="."/>
                    </xsl:for-each>
                </activity>
            </root>
        </xsl:result-document>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet> 

在当前版本的 Saxon 中,您可以这样称呼它:

java -jar saxon9he.jar -xsl:split.xslt src.xml doc=src.xml split=2

答案 1 :(得分:2)

Saxon 9.8(Saxon 9.8 EE)企业版支持the streaming feature of the one year old XSLT 3.0 specification,它允许您使用XSLT的子集以仅向前方式读取XML文档,而仅使用存储当前文件所需的内存。访问过的节点及其祖先。

使用这种方法,您可以编写类似for-each-group select="activity/deliv" group-adjacent="(position() - 1) idiv $size"的代码来进行位置分组,从而按deliv元素读取文件deliv并将其收集到$size组中:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    exclude-result-prefixes="xs math"
    version="3.0">

    <xsl:param name="size" as="xs:integer" select="1000"/>

    <xsl:mode on-no-match="shallow-copy" streamable="yes"/>

    <xsl:template match="root">
        <xsl:for-each-group select="activity/deliv" group-adjacent="(position() - 1) idiv $size">
            <xsl:result-document href="split-{format-number(current-grouping-key() + 1, '00000')}.xml" indent="yes">
                <root>
                    <activity>
                        <xsl:copy-of select="current-group()"/>
                    </activity>
                </root>
            </xsl:result-document>
        </xsl:for-each-group>
    </xsl:template>

</xsl:stylesheet>

这会将您的输入分为多个文件,每个文件包含$size deliv个元素(如果少于{{1},则分别是其余deliv个元素的最后一个) })。

使用Saxon EE需要获得商业许可证,但存在试用许可证。

答案 2 :(得分:1)

XSLT可以完成此工作。我建议您动手使用XSLT v2.0处理器,以便可以使用xsl:result-document。然后,您需要一点逻辑来决定何时在文件之间分割。您可以根据deliv元素的position()进行设置,也可以尝试使用xsl:for-each-group来创建发送到每个文件的组。