将巨型 XML 文件拆分为 n 个子版本
Split giant XML file into n-child versions
例如巨型文件有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>
并且每个 'child' 文件将具有相同的结构,但大约有 500 万行,或原始文件的 1/10。
这样做的原因是为了使将此类导入数据库更易于管理,而不会耗尽内存(SQL 服务器的 OPENXML)。
XSLT 是这里的最佳选择吗?
XSLT 可以完成这项工作。我建议您使用 XSLT v2.0 处理器,以便您可以使用 xsl:result-document。然后您需要一些逻辑来决定何时在文件之间拆分。您可以基于 deliv 元素的 position(),或尝试使用 xsl:for-each-group 来创建发送到每个文件的组。
XSLT-2.0 及更高版本非常适合这项任务。
XSLT-3.0 甚至支持流式传输。
以下样式表使用 xsl:result-document
.
将一个 XML 文件拆分为可配置数量的文件
它有两个参数:
split
- 每个拆分中的项目数
doc
- 源文档名称
这是为您的示例定制的 XSLT-2.0 模板 (split.xslt
):
<?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
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
并将它们收集到 [=14= 的组中]:
<?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
个元素(如果少于 $size
左)。
使用 Saxon EE 需要获得商业许可证,但存在试用许可证。
例如巨型文件有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>
并且每个 'child' 文件将具有相同的结构,但大约有 500 万行,或原始文件的 1/10。
这样做的原因是为了使将此类导入数据库更易于管理,而不会耗尽内存(SQL 服务器的 OPENXML)。
XSLT 是这里的最佳选择吗?
XSLT 可以完成这项工作。我建议您使用 XSLT v2.0 处理器,以便您可以使用 xsl:result-document。然后您需要一些逻辑来决定何时在文件之间拆分。您可以基于 deliv 元素的 position(),或尝试使用 xsl:for-each-group 来创建发送到每个文件的组。
XSLT-2.0 及更高版本非常适合这项任务。
XSLT-3.0 甚至支持流式传输。
以下样式表使用 xsl:result-document
.
它有两个参数:
split
- 每个拆分中的项目数doc
- 源文档名称
这是为您的示例定制的 XSLT-2.0 模板 (split.xslt
):
<?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
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
并将它们收集到 [=14= 的组中]:
<?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
个元素(如果少于 $size
左)。
使用 Saxon EE 需要获得商业许可证,但存在试用许可证。