如何通过流处理单个 xml 的两个部分?

How to process two parts of a single xml by streaming?

我有一个非常大的 XML,它有两组不同的数据。我需要流过一个并在另一个上进行查找。我的数据如下所示

    <?xml version="1.0" encoding="utf-8"?>
    <Root>
       <EmployeePersonal>
          <Employee>
             <ID>21234</ID>
             <Name>Jim Carrey</Name>
             <Age>43</Age>
             <City>Chicago</City>
             <State>IL</State>
          </Employee>
          <Employee>
             <ID>41876</ID>
             <Name>Edward Norton</Name>
             <Age>33</Age>
             <City>New York</City>
             <State>NY</State>
          </Employee>
          <Employee>
             <ID>51239</ID>
             <Name>Eli Roth</Name>
             <Age>46</Age>
             <City>Los Angeles</City>
             <State>CA</State>
          </Employee>
       </EmployeePersonal>
       <EmployeeEmployment>
          <Empl>
             <Emplid>21234</Emplid>
             <Title>HR Partner</Title>
             <HireDate>2008-12-29</HireDate>
          </Empl>
          <Empl>
             <Emplid>41876</Emplid>
             <Title>Comp Partner</Title>
             <HireDate>1999-07-09</HireDate>
          </Empl>
          <Empl>
             <Emplid>51239</Emplid>
             <Title>Programmer</Title>
             <HireDate>2004-12-06</HireDate>
          </Empl>
       </EmployeeEmployment>
    </Root>

我想遍历 /Root/EmployeePersonal 数据并通过匹配员工 ID 查找 /Root/EmployeeEmployment。

我尝试循环一个,然后加载到地图中,然后循环另一个,但一直出现错误。最后,我尝试将一组加载到一个变量中,然后尝试流式传输另一组,但没有成功。这是我到目前为止尝试过的。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="3.0">

    <xsl:mode streamable="yes" on-no-match="shallow-skip"/>
    <xsl:output indent="no"/>

    <xsl:variable name="vEmploymentData" select="Root/copy-of(EmployeeEmployment)"/>

    <xsl:template match="/Root/EmployeePersonal">
      <AllEmployeeData>
          <xsl:for-each select="Employee/copy-of()">
              <Employee>
                  <xsl:copy-of select="./*"/>
                  <xsl:copy-of select="$vEmploymentData/Empl[Emplid=current()/ID]/*[local-name() ne 'Emplid']"/>
              </Employee>
          </xsl:for-each>
      </AllEmployeeData>
    </xsl:template>

</xsl:stylesheet>

我需要将这两组数据合并在一起。由于数据量很大,有没有办法把两组数据都流进去?

谢谢

鉴于您需要同时对不同的数据集进行比较和合并,我不确定是否存在一种简单的单次传递、可流式传输且因此仅转发的方法,因此一些简单的方法可以解决这个问题将同一个文档设置为 xsl:merge-source 两次 xsl:merge:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all">

    <xsl:param name="input-uri" as="xs:string" select="'input1.xml'"/>

    <xsl:output indent="yes"/>

    <xsl:template name="xsl:initial-template">
        <AllEmployeeData>
            <xsl:merge>
                <xsl:merge-source name="emp-details1" for-each-source="$input-uri" streamable="yes" select="Root/EmployeePersonal/Employee">
                    <xsl:merge-key select="ID"/>
                </xsl:merge-source>
                <xsl:merge-source name="emp-details2" for-each-source="$input-uri" streamable="yes" select="Root/EmployeeEmployment/Empl">
                    <xsl:merge-key select="Emplid"/>
                </xsl:merge-source>
                <xsl:merge-action>
                    <xsl:copy>
                        <xsl:copy-of select="current-merge-group('emp-details1')/*, current-merge-group('emp-details2')/(* except Emplid)"/>
                    </xsl:copy>
                </xsl:merge-action>
            </xsl:merge>
        </AllEmployeeData>
    </xsl:template>

</xsl:stylesheet>

由于 xsl:merge 使用快照,因此访问 IDEmplid 子节点不是问题。您也不必采取任何特定操作来缓冲映射或累加器中的数据。主要缺点是 xsl:merge 期望源按合并键排序,您的示例数据似乎就是这种情况,但我不确定您的完整数据是否如此。

你会 运行 使用命令行选项 -it 的 Saxon 9 EE 这样的样式表开始时没有主要输入,而是使用名为 xsl:initial-template 的初始模板。

作为仅读取主输入一次但使用 Saxon 9.9 EE 特定 saxon:capture 属性在累加器中缓冲 EmployeePersonal/Employee 的替代方法,您可以使用:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:saxon="http://saxon.sf.net/"
    exclude-result-prefixes="#all">

    <xsl:output indent="yes"/>

    <xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="emp-details1"/>

    <xsl:accumulator name="emp-details1" streamable="yes" as="element(Employee)*" initial-value="()">
        <xsl:accumulator-rule match="EmployeePersonal/Employee" phase="end" saxon:capture="yes" select="$value, ."/>
    </xsl:accumulator>

    <xsl:template match="/*">
        <AllEmployeeData>
            <xsl:apply-templates/>
        </AllEmployeeData>
    </xsl:template>

    <xsl:template match="EmployeeEmployment/Empl">
        <Employee>
            <xsl:variable name="this" select="copy-of()"/>
            <xsl:copy-of select="accumulator-before('emp-details1')[ID = $this/Emplid]/*, $this/(* except Emplid)"/>
        </Employee>
    </xsl:template>

</xsl:stylesheet>

这只读取一次输入,但将所有 EmployeePersonal/Employee 缓冲在内存中,然后在 EmployeeEmployment/Empl 匹配时使用后面的 buffered/accumulated 元素输出必要的相应数据。当然,这也是匹配 EmployeeEmployment/Empl 时创建的输出,需要该数据中存在所有 id,我不确定是这种情况还是 EmployeePersonal/Employee 是主要输入,你只寻找匹配 EmployeeEmployment/Empl 如果它存在。

最后,您还可以尝试使用映射将 EmployeePersonal/Employee 的详细信息存储在累加器中,然后从 EmployeeEmployment/Empl:

的模板访问它
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:saxon="http://saxon.sf.net/"
    expand-text="yes"
    exclude-result-prefixes="#all">

    <xsl:output indent="yes"/>

    <xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="#all"/>

    <xsl:accumulator name="current-emp-id" streamable="yes" as="xs:integer?" initial-value="()">
        <xsl:accumulator-rule match="EmployeePersonal/Employee/ID/text()" select="xs:integer(.)"/>
    </xsl:accumulator>

    <xsl:accumulator name="emp-details" streamable="yes" as="map(xs:integer, map(xs:string, xs:string))" initial-value="map{}">
        <xsl:accumulator-rule match="EmployeePersonal/Employee/ID/text()" select="map:put($value, xs:integer(.), map{})"/>
        <xsl:accumulator-rule match="EmployeePersonal/Employee/*[not(self::ID)]/text()" 
            select="map:put($value, accumulator-before('current-emp-id'), map:put(map:get($value, accumulator-before('current-emp-id')), local-name(..), string()))"/>
    </xsl:accumulator>

    <xsl:template match="/*">
        <AllEmployeeData>
            <xsl:apply-templates/>
        </AllEmployeeData>
    </xsl:template>

    <xsl:template match="EmployeeEmployment/Empl">
        <Employee>
            <xsl:variable name="this" select="copy-of()"/>
            <ID>{$this/Emplid}</ID>
            <xsl:apply-templates select="map:get(accumulator-before('emp-details'), xs:integer($this/Emplid))" mode="entry-to-element"/>
            <xsl:copy-of select="$this/(* except Emplid)"/>
        </Employee>
    </xsl:template>

    <xsl:template match=".[. instance of map(*)]" mode="entry-to-element">
        <xsl:variable name="map" select="."/>
        <xsl:for-each select="map:keys(.)">
            <xsl:element name="{.}">{map:get($map, .)}</xsl:element>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

当然它仍然会缓冲所有 EmployeePersonal/Employee 数据,只是这次使用轻量级映射而不是一系列 XML 快照元素。主要缺点是 XSLT 3/XPath 3.1 映射没有顺序,因此除非您稍后以想要的顺序拼出您要查找的每个条目,否则您将以随机顺序获得结果。在上面的示例中,我只是使用 for-each 将映射条目转换回 XML 元素,因此顺序是随机顺序而不是输入 XML 中的一个(xsl:merge 方法或 saxon:capture 方法将保留)。

正如您在关于使用 xsl:merge 但将输入作为主要输入源提供的评论中所问,我能想到的唯一方法是将其用作虚拟可流式输入,然后提供其 document-uri() 用于 xsl:merge/xsl:merge-source,即将上面的代码改编为

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all">

    <xsl:mode streamable="yes"/>

    <xsl:output indent="yes"/>

    <xsl:template match="/">
        <AllEmployeeData>
            <xsl:variable name="input-uri" as="xs:anyURI" select="document-uri()"/>
            <xsl:message select="$input-uri"/>
            <xsl:merge>
                <xsl:merge-source name="emp-details1" for-each-source="$input-uri" streamable="yes" select="Root/EmployeePersonal/Employee">
                    <xsl:merge-key select="ID"/>
                </xsl:merge-source>
                <xsl:merge-source name="emp-details2" for-each-source="$input-uri" streamable="yes" select="Root/EmployeeEmployment/Empl">
                    <xsl:merge-key select="Emplid"/>
                </xsl:merge-source>
                <xsl:merge-action>
                    <xsl:copy>
                        <xsl:copy-of select="current-merge-group('emp-details1')/*, current-merge-group('emp-details2')/(* except Emplid)"/>
                    </xsl:copy>
                </xsl:merge-action>
            </xsl:merge>
        </AllEmployeeData>
    </xsl:template>

</xsl:stylesheet>

有趣的是,它适用于 Saxon 9.7 EE(当然,打开相同的输入三次以进行流式传输)但失败并出现错误“XTSE3430:模板规则不可流式传输 * The merge-source/@select 在 Saxon 9.8 和 9.9 EE 中表达式不跨步”。

不确定这是否有帮助,在之前的问题中,您似乎指出您使用的工具嵌入了 Saxon 9.7 EE,因此它可能是一个选项。