XSLT:将累积值存储在类似于结构

时间:2015-05-18 19:20:24

标签: xml xslt xslt-1.0

PFB输入xml和所需的输出。

InputXML

 <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>

DesiredOutput

<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)。由于它不相等,因此可以考虑形成总和。在第二个conatiner中进一步移动我们将不会考虑8(第二项的数量),因为它与之前的conatiners总和相同。

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

  3. 因此,对于“Total”元素中的项目(具有唯一数量),将填充值6。因此,总计6填充在第二个容器中的第一个和第三个包装物品中。

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

    如果没有前面的兄弟姐妹,那么只需在PackedItems中添加所有数量,并更新“Total”元素中的最终值。请参阅下面附带的DesiredOutput示例。

    我的问题是,如果我必须填充第四个容器的“总计”,我必须计算然后比较所有三个容器中的总计。我不应该每次都计算总值。我正在寻找一种方法来计算和存储这些值在数组结构中,并能够引用它们将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>
    

    任何指针/建议都会非常有用。

1 个答案:

答案 0 :(得分:0)

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

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

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

与数量进行比较的前者总数是&#34;存储&#34;在computed.totals参数中,每次处理Container时都会填充。

<?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:小心命名空间......