【问题标题】:Split giant XML file into n-child versions将巨型 XML 文件拆分为 n 个子版本
【发布时间】:2018-07-11 21:16:52
【问题描述】:

例如巨型文件有 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 是这里的最佳选择吗?

【问题讨论】:

    标签: xml xslt sql-server-openxml


    【解决方案1】:

    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 元素(如果剩下的少于$size,则分别是最后一个和剩余的deliv 元素)。

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

    【讨论】:

      【解决方案2】:

      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
      

      【讨论】:

      • 感谢 @zx485,我是 XSLT 的新手,所以这很有帮助。
      • 在我的 3.6Gb 文件上试过这个,但不幸的是内存不足:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
      • 这很不幸。我从未使用过 XSLT-3.0 流,这将是您在这种情况下的第一选择。也许其他人可以帮忙。
      • Martin Honnen 展示了如何使解决方案可流式传输。
      【解决方案3】:

      XSLT 可以完成这项工作。我建议您使用 XSLT v2.0 处理器,以便您可以使用 xsl:result-document。然后,您需要一些逻辑来决定何时在文件之间拆分。您可以将其基于 deliv 元素的 position(),或者尝试使用 xsl:for-each-group 来创建发送到每个文件的组。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-09-20
        • 1970-01-01
        • 1970-01-01
        • 2011-03-12
        • 2017-08-16
        • 1970-01-01
        • 2019-10-21
        相关资源
        最近更新 更多