XSLT:将累积值存储在类似结构的数组中

XSLT : storing accumulated values in array like structures

PFB 输入 xml 和所需的输出。

输入XML

 <Shipment xmlns="http://www.example.org">
  <Container>
    <ContainerID>C1</ContainerID>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>4</Quantity>
      <Total>0</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>4</Quantity>
      <Total>0</Total>
    </PackedItem>
  </Container>
  <Container>
    <ContainerID>C2</ContainerID>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>4</Quantity>
      <Total>0</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>8</Quantity>
      <Total>0</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>2</Quantity>
      <Total>0</Total>
    </PackedItem>
  </Container>
  <Container>
    <ContainerID>C3</ContainerID>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>3</Quantity>
      <Total>0</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>3</Quantity>
      <Total>0</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>2</Quantity>
      <Total>0</Total>
    </PackedItem>
  </Container>
</Shipment>

期望输出

<Shipment xmlns="http://www.example.org"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.example.org>
  <Container>
    <ContainerID>C1</ContainerID>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>4</Quantity>
      <Total>8</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>4</Quantity>
      <Total>8</Total>
    </PackedItem>
  </Container>
  <Container>
    <ContainerID>C2</ContainerID>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>4</Quantity>
      <Total>6</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>8</Quantity>
      <Total>8</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>2</Quantity>
      <Total>6</Total>
    </PackedItem>
  </Container>
  <Container>
    <ContainerID>C3</ContainerID>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>3</Quantity>
      <Total>8</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>3</Quantity>
      <Total>8</Total>
    </PackedItem>
    <PackedItem>
      <ItemID>A123</ItemID>
      <Quantity>2</Quantity>
      <Total>8</Total>
    </PackedItem>
  </Container>
</Shipment>

对于每个容器,检查每个 PackedItem 的数量(在当前容器 ->PackedItem 中)是否等于任何前面兄弟姐妹中的 "Total" 元素

if yes; Total= Quantity     
else; Total=Total+quantity //total is initially 0 

应用于样本XML,表示:

  1. 我们必须将每个 PackedItem 中的数量与前面的每个兄弟 "Total" 值进行比较。如果数量不等于任何预先计算的总数,那么我们会将其视为构成该特定容器总数的参数。

    例如:如果您看到第一个容器,则第一个容器的总计值为 8(简单地将两个数量相加,因为前面没有兄弟姐妹)。

  2. 对于第二个容器,我们选择第一个数量为 4 并检查它是否等于前面兄弟姐妹的总和(为 8)。由于它不相等,因此将考虑形成总和。在第二个容器中进一步移动,我们将不考虑 8(第二个项目的数量),因为它与之前的容器总和相等。

    第三个数量 2 是唯一的(不等于前面兄弟姐妹的总和)因此将被视为形成总和。因此,第二个容器的最终总数为 4+2=6。

因此 "Total" 元素中的项目(具有唯一数量)填充了值 6。因此,第二个容器内的第 1 和第 3 个包装物品中总共填充了 6 个。

运行 容器中其余 PackedItem 的循环,将容器中的每个数量与先前容器中的总量进行比较。如果我们有一个新的 Quantity 值,它没有出现在任何前面的兄弟 "Total" 元素中,将该值添加到总和中,并在当前容器 "Total" 元素中填充最终值

如果前面没有兄弟项,则只需添加 PackedItems 中的所有数量并更新 "Total" 元素中的最终值。请参考下面随附的 DesiredOutput 示例。

我的问题是,如果我必须为第四个容器填充 "Total",我必须计算并比较前三个容器的总数。我不必每次都计算总值。我正在寻找一种方法来计算这些值并将这些值存储在类似结构的数组中,并能够引用它们以将 Packeditem 中的每个数量与前一个兄弟中的 Total 的每个值进行比较。

或者我正在寻找的另一个选项是 - 如果有一种方法可以从目标树结构中读取累积值。因此,我可以读取先前容器的 "Total" 元素中累积的内容。

我正在处理的 XSLT(1.0 版)的 PFB 部分

<xsl:template match="/">
        <xsl:copy>
            <xsl:apply-templates></xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[starts-with(name(),'Total')]">
        <xsl:variable name="ContainerID" select="../../ns0:ContainerID"/>
        <xsl:variable name="ItemID" select="../ns0:ItemID"/>
        <xsl:variable name="Quantity" select="../ns0:Quantity"/>
        <xsl:element name="Total" namespace="http://www.example.org">
            <xsl:call-template name="Add">
                <xsl:with-param name="ContainerID" select="$ContainerID"/>
                <xsl:with-param name="ItemID" select="$ItemID"/>
                <xsl:with-param name="Quantity" select="$Quantity"/>
            </xsl:call-template>
        </xsl:element>
    </xsl:template>
    <xsl:template name="Add">
        <xsl:param name="ContainerID" select="$ContainerID"/>
        <xsl:param name="ItemID"/>
        <xsl:param name="Quantity"/>
        <xsl:choose>
            <!-- when there are no preceding siblings. this is the first container-->
            <xsl:when test="not(/ns0:Shipment/ns0:Container[./ns0:ContainerID=$ContainerID]/preceding-sibling::ns0:Container/ns0:PackedItem[./ns0:ItemID=$ItemID])">
                <xsl:value-of select="sum(/ns0:Shipment/ns0:Container[./ns0:ContainerID=$ContainerID]/ns0:PackedItem[./ns0:ItemID=$ItemID]/ns0:Quantity)"/>
            </xsl:when>
            <xsl:otherwise>
            <!-- check the total value in preceding siblings and compare them to each quantity in this container -->
            <!-- problem: reading the accumulated value in Total for preceding containers. -->
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

任何 pointers/suggestions 都会很有帮助。

这非常具有挑战性....这是我的 XSLT;请注意,您的 XSL 处理器必须支持 XSLT 扩展 (exslt)——如果您使用的是 Xalan 或 Saxon,就可以了。

基本上,我们必须一个接一个地处理 Container,并添加有关已计算的 Totals 的信息。这是通过递归完成的,这是在 XSLT 1.0 中修改值的唯一方法。

这就是为什么您会在最开始找到 <xsl:apply-templates select="ex:Container[1]" /><xsl:apply-templates select="following-sibling::ex:Container[1]"> 来处理下一个 Container,等等。

之前要与数量进行比较的总计是computed.totals参数中的"stored",每次处理一个容器时都会填充。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    extension-element-prefixes="exsl"
    exclude-result-prefixes="exsl"
    xmlns:ex="http://www.example.org"
    xmlns:ns0="http://www.example.org"
    xmlns:ns1="http://www.example.org"
version="1.0">

    <xsl:template match="/*">
        <xsl:copy>
            <xsl:apply-templates select="ex:Container[1]" />
        </xsl:copy>
    </xsl:template>

    <xsl:template match="node()|@*">
        <xsl:param name="computed.totals"></xsl:param>
        <xsl:param name="curr.total"></xsl:param>
        <xsl:copy>
            <xsl:apply-templates>
                <xsl:with-param name="computed.totals" select="$computed.totals"></xsl:with-param>
                <xsl:with-param name="curr.total" select="$curr.total"></xsl:with-param>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="ex:Container">
        <!-- A trick to initialize to an empty node-set, see http://lists.w3.org/Archives/Public/xsl-editors/2000JulSep/0068.html -->
        <xsl:param name="computed.totals" select="/@empty-node-set" />

        <!-- Process the current total, taking into accound the totals of preceding siblings -->
        <xsl:variable name="curr.total" select="sum(ex:PackedItem/ex:Quantity[(exsl:node-set($computed.totals)/MyTotal/@value != .) or not(exsl:node-set($computed.totals)/*)])"></xsl:variable>

        <!-- Process current Container elements -->
        <xsl:copy>
            <xsl:apply-templates>
                <xsl:with-param name="computed.totals" select="$computed.totals"></xsl:with-param>
                <xsl:with-param name="curr.total" select="$curr.total"></xsl:with-param>
            </xsl:apply-templates>
        </xsl:copy>

        <!-- Process next container, with the updated list of Total already computed -->
        <xsl:apply-templates select="following-sibling::ex:Container[1]">
            <xsl:with-param name="computed.totals">
                <xsl:copy-of select="$computed.totals" />
                <MyTotal>
                    <xsl:attribute name="ContainerID"><xsl:value-of select="ex:ContainerID"/></xsl:attribute>
                    <xsl:attribute name="value"><xsl:value-of select="$curr.total"/></xsl:attribute>
                </MyTotal>
            </xsl:with-param>
        </xsl:apply-templates>

    </xsl:template>

    <xsl:template match="*[starts-with(name(),'Total')]">
        <xsl:param name="computed.totals"></xsl:param>
        <!-- store the current total of the Container, will be used only when processing the Total -->
        <xsl:param name="curr.total"></xsl:param>

        <xsl:variable name="ContainerID" select="../../ns0:ContainerID"/>
        <xsl:variable name="ItemID" select="../ns0:ItemID"/>
        <xsl:variable name="Quantity" select="../ns0:Quantity"/>

        <xsl:choose>
            <xsl:when test="exsl:node-set($computed.totals)/MyTotal[@value = $Quantity]">
                <xsl:copy-of select="$Quantity" />
            </xsl:when>
            <xsl:otherwise>
                <xsl:element name="Total" namespace="http://www.example.org">
                    <xsl:value-of select="$curr.total"/>
                </xsl:element>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

样式表中也有评论:如果您还有其他问题,请告诉我。

您可以在这里检查它是否正常工作:http://xsltransform.net/bFDb2CH

PS : 小心命名空间...