在流式处理时使用公共字段从循环外部的元素中提取数据

Pull data from an element outside of a loop using a common field while streaming

提问人:user2337472 提问时间:10/23/2023 最后编辑:user2337472 更新时间:10/25/2023 访问量:88

问:

我之前提出过类似的问题,但我的 XML 结构发生了变化/我需要利用流式处理,并且无法将其与我当前的解决方案一起使用。

我正在遍历 XML,但我需要引用的一些字段位于我的循环之外,并且依赖于当前循环的 ID,我可以使用一个通用的 ID 字段来匹配元素,但我需要它们在外部循环的上下文中匹配,而不是整个文档。我一直在尝试使用密钥,但无法让它在流式传输时工作/作为测试,我删除了流式引用,我在某种程度上得到了我需要的东西,但它与整个文档匹配,而不是我的循环。

<Date>13/10/2023 15/10/2023 20/10/2023</Date>

XML 结构

<Company><Employee_Stack_Grouped>
<Employee_Stack>
  <Employee>
    <Details>
      <EmployeeID>ABC11111</EmployeeID>
    </Details>
    <Changes sequence="0">
      <ChangeDetails>
        <entryDate>20/10/2023</entryDate>
      </ChangeDetails>
      <Plans>
        <Plan delete="Y">
          <Name>Plan 1</Name>
          <Start>01/01/2023</Start>
          <ID>12345</ID>
        </Plan>
        <Plan updated="Y">
          <Name>Plan 1</Name>
          <Start>13/10/2023</Start>
          <ID>12345</ID>
        </Plan>
        <Plan updated="Y">
          <Name>Plan 2</Name>
          <Start>13/10/2023</Start>
          <ID>67890</ID>
        </Plan>
        <Plan>
          <Name>Plan 3</Name>
          <Start>01/01/2023</Start>
          <ID>11111</ID>
        </Plan>
        <Interest_Plan updated="Y">
          <Name>Plan 4</Name>
          <Start>13/10/2023</Start>
          <ID>22222</ID>
        </Interest_Plan>
      </Plans>
      <Amounts>
        <Earning updated="Y">
          <Amount>1000</Amount>
          <Plan>
            <ID>12345</ID>
          </Plan>
        </Earning>
        <Earning updated="Y">
          <Amount>1000</Amount>
          <Plan>
            <ID>67890</ID>
          </Plan>
        </Earning>
        <Earning>
          <Amount>100</Amount>
          <Plan>
            <ID>11111</ID>
          </Plan>
        </Earning>
        <Earning updated="Y">
          <Amount>5000</Amount>
          <Plan>
            <ID>22222</ID>
          </Plan>
        </Earning>
      </Amounts>
    </Changes>
    <Changes sequence="1">
      <ChangeDetails>
        <entryDate>23/10/2023</entryDate>
      </ChangeDetails>
      <Plans>
        <Plan>
          <Name>Plan 1</Name>
          <Start>13/10/2023</Start>
          <ID>12345</ID>
        </Plan>
        <Plan updated="Y">
          <Name>Plan 2</Name>
          <Start>15/10/2023</Start>
          <ID>67890</ID>
        </Plan>
        <Plan>
          <Name>Plan 3</Name>
          <Start>01/01/2023</Start>
          <ID>11111</ID>
        </Plan>
        <Interest_Plan>
          <Name>Plan 4</Name>
          <Start>13/10/2023</Start>
          <ID>22222</ID>
        </Interest_Plan>
      </Plans>
      <Amounts>
        <Earning>
          <Amount>1000</Amount>
          <Plan>
            <ID>12345</ID>
          </Plan>
        </Earning>
        <Earning updated="Y">
          <Amount>2500</Amount>
          <Plan>
            <ID>67890</ID>
          </Plan>
        </Earning>
        <Earning>
          <Amount>100</Amount>
          <Plan>
            <ID>11111</ID>
          </Plan>
        </Earning>
        <Earning>
          <Amount>5000</Amount>
          <Plan>
            <ID>22222</ID>
          </Plan>
        </Earning>
      </Amounts>
    </Changes>
  </Employee>
  <Employee>
    <Details>
      <EmployeeID>ABC222222</EmployeeID>
    </Details>
    <Changes sequence="0">
      <ChangeDetails>
        <entryDate>23/10/2023</entryDate>
      </ChangeDetails>
      <Plans>
        <Plan updated="Y">
          <Name>Plan 1</Name>
          <Start>20/10/2023</Start>
          <ID>12345</ID>
        </Plan>
        <Plan updated="Y">
          <Name>Plan 2</Name>
          <Start>20/10/2023</Start>
          <ID>67890</ID>
        </Plan>
        <Plan>
          <Name>Plan 3</Name>
          <Start>01/01/2023</Start>
          <ID>11111</ID>
        </Plan>
        <Interest_Plan updated="Y">
          <Name>Plan 4</Name>
          <Start>13/10/2023</Start>
          <ID>22222</ID>
        </Interest_Plan>
      </Plans>
      <Amounts>
        <Earning updated="Y">
          <Amount>1000</Amount>
          <Plan>
            <ID>12345</ID>
          </Plan>
        </Earning>
        <Earning updated="Y">
          <Amount>1000</Amount>
          <Plan>
            <ID>67890</ID>
          </Plan>
        </Earning>
        <Earning>
          <Amount>100</Amount>
          <Plan>
            <ID>11111</ID>
          </Plan>
        </Earning>
        <Earning updated="Y">
          <Amount>5000</Amount>
          <Plan>
            <ID>22222</ID>
          </Plan>
        </Earning>
      </Amounts>
    </Changes>
  </Employee>
</Employee_Stack></Employee_Stack_Grouped></Company>

XSL 取消注释 Key 值会引发错误:提供的节点必须位于根为文档节点的树中。

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:mode streamable="true" on-no-match="shallow-skip"/>

<xsl:key name="plan" match="Plans/Plan[@updated = 'Y'] | Interest_Plan[@updated = 'Y']" use="ID"/>

<xsl:template match="/Company">
    <File>
        <xsl:for-each select="Employee_Stack_Grouped/Employee_Stack/Employee/copy-of()">
            <xsl:variable name="employeeID" select="Details/EmployeeID"/>
            <xsl:for-each select="Changes">
                <xsl:variable name="entryDate" select="ChangeDetails/entryDate"/>

                <xsl:for-each select="Amounts/Earning[@updated = 'Y']">
                    <row>
                        <Entry>
                            <xsl:value-of select="$entryDate"/>
                        </Entry>
                        <Employee_Number>
                            <xsl:value-of select="$employeeID"/>
                        </Employee_Number>
                        <Date>
                            <!-- <xsl:value-of select="key('plan', Plan/ID)/Start"/> -->
                        </Date>
                        <Amount>
                            <xsl:value-of select="Amount"/>
                        </Amount>
                        <ID>
                            <xsl:value-of select="Plan/ID"/>
                        </ID>
                    </row>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:for-each>
    </File>
</xsl:template></xsl:stylesheet>

预期输出

<File>
<row>
    <Entry>20/10/2023</Entry>
    <Employee_Number>ABC11111</Employee_Number>
    <Date>13/10/2023</Date>
    <Amount>1000</Amount>
    <ID>12345</ID>
</row>
<row>
    <Entry>20/10/2023</Entry>
    <Employee_Number>ABC11111</Employee_Number>
    <Date>13/10/2023</Date>
    <Amount>1000</Amount>
    <ID>67890</ID>
</row>
<row>
    <Entry>20/10/2023</Entry>
    <Employee_Number>ABC11111</Employee_Number>
    <Date>13/10/2023</Date>
    <Amount>5000</Amount>
    <ID>22222</ID>
</row>
<row>
    <Entry>23/10/2023</Entry>
    <Employee_Number>ABC11111</Employee_Number>
    <Date>15/10/2023</Date>
    <Amount>2500</Amount>
    <ID>67890</ID>
</row>
<row>
    <Entry>23/10/2023</Entry>
    <Employee_Number>ABC222222</Employee_Number>
    <Date>20/10/2023</Date>
    <Amount>1000</Amount>
    <ID>12345</ID>
</row>
<row>
    <Entry>23/10/2023</Entry>
    <Employee_Number>ABC222222</Employee_Number>
    <Date>20/10/2023</Date>
    <Amount>1000</Amount>
    <ID>67890</ID>
</row>
<row>
    <Entry>23/10/2023</Entry>
    <Employee_Number>ABC222222</Employee_Number>
    <Date>13/10/2023</Date>
    <Amount>5000</Amount>
    <ID>22222</ID>
</row></File>

请注意,可以有多个Employee_Stack_Grouped,然后是每个Employee_Stack多个员工,最后是每个员工的多个更改。我基本上需要输出任何带有更新标志的收益,但引用更改/计划元素中的日期。

我不确定是否可以在流式传输时使用密钥来实现此目的?由于我无法让它工作,我正在考虑做的另一个选择是在循环遍历 Changes 元素时,使用另一个循环将任何更新的计划存储在地图中,然后在我循环浏览收益时引用它。关于我如何实现这样的事情,有什么建议吗?

XML XSLT XSLT-3.0

评论

0赞 Martin Honnen 10/23/2023
键不适用于流式处理,是的,您可以存储详细信息,因为 Plan 元素似乎位于 Employee 元素之前,最简单的方法是使用 Saxon EE 和捕获累加器或地图,然后访问累加器。请参阅 saxonica.com/html/documentation12/extensions/attributes/...

答:

1赞 Martin Honnen 10/23/2023 #1

使用捕获累积(目前是 Saxon 扩展,可能在 XSLT 4 中标准化),您可以使用例如

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:saxon="http://saxon.sf.net/"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  exclude-result-prefixes="#all">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:mode streamable="true" on-no-match="shallow-skip" use-accumulators="plans"/>

<xsl:accumulator name="plans" as="map(xs:integer, map(xs:string,xs:string))" initial-value="map{}" streamable="yes">
  <xsl:accumulator-rule phase="end" saxon:capture="yes" match="Plans/Plan[@updated = 'Y'] | Interest_Plan[@updated = 'Y']"
    select="map:put($value, xs:integer(ID), map:merge(*!map:entry(local-name(), string())))"/>
</xsl:accumulator>

<xsl:template match="/Company">
    <File>
        <xsl:for-each select="Employee_Stack_Grouped/Employee_Stack/Employee/copy-of()">
            <xsl:variable name="employeeID" select="Details/EmployeeID"/>
            <xsl:for-each select="Changes">
                <xsl:variable name="entryDate" select="ChangeDetails/entryDate"/>

                <xsl:for-each select="Amounts/Earning[@updated = 'Y']">
                    <row>
                        <Entry>
                            <xsl:value-of select="$entryDate"/>
                        </Entry>
                        <Employee_Number>
                            <xsl:value-of select="$employeeID"/>
                        </Employee_Number>
                        <Date>
                            <xsl:value-of select="accumulator-before('plans')(xs:integer(Plan/ID))?Start"/>
                        </Date>

如果您没有“捕获”累加器的 Saxon 扩展,您仍然可以尝试将数据存储在多个累加器中,然后,您处理一个 ,创建映射条目:ID

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:saxon="http://saxon.sf.net/"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  exclude-result-prefixes="#all">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:mode streamable="true" on-no-match="shallow-skip" use-accumulators="#all"/>

<xsl:accumulator name="name" as="xs:string?" initial-value="()" streamable="yes">
  <xsl:accumulator-rule match="Plans/Plan[@updated = 'Y']/Name/text() | Interest_Plan[@updated = 'Y']/Name/text()" select="string()"/>
</xsl:accumulator>

<xsl:accumulator name="start" as="xs:string?" initial-value="()" streamable="yes">
  <xsl:accumulator-rule match="Plans/Plan[@updated = 'Y']/Start/text() | Interest_Plan[@updated = 'Y']/Start/text()" select="string()"/>
</xsl:accumulator>


<xsl:accumulator name="plans" as="map(xs:integer, map(xs:string,xs:string))" initial-value="map{}" streamable="yes">
  <xsl:accumulator-rule match="Plans/Plan[@updated = 'Y']/ID/text() | Interest_Plan[@updated = 'Y']/ID/text()"
    select="map:put($value, xs:integer(.), map { 'Start' : accumulator-before('start'), 'Name' : accumulator-before('name') })"/>
</xsl:accumulator>

<xsl:template match="/Company">
    <File>
        <xsl:for-each select="Employee_Stack_Grouped/Employee_Stack/Employee/copy-of()">
            <xsl:variable name="employeeID" select="Details/EmployeeID"/>
            <xsl:for-each select="Changes">
                <xsl:variable name="entryDate" select="ChangeDetails/entryDate"/>

                <xsl:for-each select="Amounts/Earning[@updated = 'Y']">
                    <row>
                        <Entry>
                            <xsl:value-of select="$entryDate"/>
                        </Entry>
                        <Employee_Number>
                            <xsl:value-of select="$employeeID"/>
                        </Employee_Number>
                        <Date>
                            <xsl:value-of select="accumulator-before('plans')(xs:integer(Plan/ID))?Start"/>
                        </Date>

你需要测试一下这种方法是否适用于9.7 EE,正如我所说,最终的XSLT 3版本是在该版本之后产生的,只有9.8及更高版本应该实现它。

评论

0赞 user2337472 10/24/2023
感谢您提供此解决方案,这似乎可以满足我的需要。你能帮我理解这一点“(计划/ID))吗?Start“ - 如果在计划中找到 ID,这是否只是一个三元 TF,将返回 Start?此外,如果该字段具有命名空间,将如何处理?我认为test:Start不起作用,它需要以某种方式屏蔽?
0赞 Martin Honnen 10/24/2023
如果你想使用地图,我认为你会知道如何在 XPath 3.1 中做到这一点,如果你不知道查找运算符,请阅读 altova.com/training/xpath3/xpath-31#lookup-operator 了解详细信息。在我只是根据子元素的本地名称将每个子元素“映射”到映射条目,因此您可以获得例如名称的映射条目并选择该映射条目。?select="map:put($value, xs:integer(ID), map:merge(*!map:entry(local-name(), string())))"/>Start?Start
0赞 user2337472 10/24/2023
在 XMLSpy 中本地测试此功能有效,但在使用 SaxonEE 9.7 的应用程序中会抛出错误。“流式累加器的 xsl: accumulator-rule/@select 表达式必须是静止的。有多个使用操作数:{xs:integer(ID)} 和 {map:merge(...)}”任何想法是什么导致了这种情况?
0赞 Martin Honnen 10/24/2023
我假设您使用的是受支持的 Saxon 版本,并且已知可以处理 XSLT 3.0 规范(从 2017 年开始)(即 9.8 或更高版本是支持最终 XSLT 3.0 规范的版本,但当前支持的版本是 12、11、10)和我使用的 Saxon 特定扩展(saxonica.com/html/documentation11/extensions/attributes/...,在 9.9 中引入)。恐怕就流式处理和确切的规则而言,尝试 EE 9.7 没有多大意义,因为它是在 XSLT 3.0 最终确定之前发布的。我的使用需要 9.9 或更高版本saxon:capture
0赞 user2337472 10/24/2023
无论如何,感谢您对此的帮助。不幸的是,我被 9.7 的限制所困扰,我需要找出另一种方法,但这非常有帮助。