除非xml节点按特定顺序排列,否则XSL Transform不起作用

时间:2014-09-15 00:07:26

标签: xml xslt

我在下面粘贴了我的XML和XSLT的精简版本。

我差不多得到了我需要的东西,但我似乎无法获得“2级'如果xml的排序如下。

前2个订单元素的Class属性为“No Class'”。如果我把这些元素放在最后那么转换就可以了。但是,xml是按以下顺序接收的,因此需要像这样工作。

xml的顺序似乎是问题,所以我想也许可以对xml进行排序并放入变量然后我可以转换变量的内容。是否可以在xslt

中执行此操作

有人可以帮忙吗?

XML:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE EmailOrder 
  SYSTEM "http://orders.bbb.co.uk/xml/Xorder.DTD">
<?xml-stylesheet type='text/xsl' href='BBSORG6.XSL'?>
<EmailOrders>
  <Order Key="COLGRE1-20140914-2345">
    <Customer_Msg>Class sort</Customer_Msg>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1001</ProductKey>
      <Qty>1</Qty>
      <Free>0</Free>
      <Cost>12.99</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>Friends Character Encyclopedia   </Details>
      <Class>No Class</Class>
      <Bags>0</Bags>
      <Section>Funfare</Section>
    </OrderLine>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1002</ProductKey>
      <Qty>2</Qty>
      <Free>0</Free>
      <Cost>19.98</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>Winnie’s Big Bad Robot</Details>
      <Class>No Class</Class>
      <Bags>0</Bags>
      <Section>Funfare</Section>
    </OrderLine>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1012</ProductKey>
      <Qty>1</Qty>
      <Free>0</Free>
      <Cost>6.50</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>Snow</Details>
      <Class>Class 1</Class>
      <Bags>5</Bags>
      <Section>Funfare</Section>
    </OrderLine>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1088</ProductKey>
      <Qty>2</Qty>
      <Free>0</Free>
      <Cost>17.98</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>Great Fairy Bake Off</Details>
      <Class>Class 1</Class>
      <Bags>5</Bags>
      <Section>Funfare</Section>
    </OrderLine>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1123</ProductKey>
      <Qty>1</Qty>
      <Free>0</Free>
      <Cost>3.99</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>Space</Details>
      <Class>Class 1</Class>
      <Bags>5</Bags>
      <Section>Funfare</Section>
    </OrderLine>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1001</ProductKey>
      <Qty>2</Qty>
      <Free>0</Free>
      <Cost>25.98</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>Friends Character Encyclopedia   </Details>
      <Class>Class 2</Class>
      <Bags>4</Bags>
      <Section>Funfare</Section>
    </OrderLine>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1002</ProductKey>
      <Qty>1</Qty>
      <Free>0</Free>
      <Cost>9.99</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>Winnie’s Big Bad Robot</Details>
      <Class>Class 2</Class>
      <Bags>4</Bags>
      <Section>Funfare</Section>
    </OrderLine>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1012</ProductKey>
      <Qty>1</Qty>
      <Free>0</Free>
      <Cost>6.50</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>Snow</Details>
      <Class>Class 2</Class>
      <Bags>4</Bags>
      <Section>Funfare</Section>
    </OrderLine>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1023</ProductKey>
      <Qty>10</Qty>
      <Free>0</Free>
      <Cost>69.90</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>The Witch with an Itch</Details>
      <Class>Class 2</Class>
      <Bags>4</Bags>
      <Section>Funfare</Section>
    </OrderLine>
    <OrderLine>
      <OrderKey>COLGRE1-20140914-2345</OrderKey>
      <ProductKey>1333</ProductKey>
      <Qty>2</Qty>
      <Free>0</Free>
      <Cost>19.98</Cost>
      <VAT_rate>0</VAT_rate>
      <Details>Scientriffic: Planet Earth</Details>
      <Class>Class 2</Class>
      <Bags>4</Bags>
      <Section>Book Zone</Section>
    </OrderLine>
  </Order>
</EmailOrders>

XSLT:

<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:msxsl="urn:schemas-microsoft-com:xslt">
  <xsl:template match="/">
    <xsl:apply-templates select="EmailOrders/Order"/>
  </xsl:template>
  <xsl:template match="Order">
    <TABLE WIDTH="65%" BORDER="1" CELLPADDING="4" 
           bgcolor="yellow" align="center">
      <TR ALIGN="left">
        <TD>Customer message</TD>
        <TD>
          <xsl:value-of select="Customer_Msg"/>
        </TD>
      </TR>
    </TABLE>
    <BR/>
    <BR/>
    <TABLE WIDTH="100%" BORDER="1" CELLPADDING="4"
           bgcolor="lightyellow" rules="cols" align="center">
      <xsl:choose>
        <xsl:when test="Customer_Msg = 'Class sort' ">
          <THEAD>
            <TH>Class</TH>
            <TH>List</TH>
            <TH>Paid</TH>
            <TH>Free</TH>
            <TH>No.</TH>
            <TH>Title</TH>
            <TH>Price</TH>
          </THEAD>
          <xsl:for-each select="OrderLine">
            <xsl:sort select="Class"/>
            <xsl:sort select="ProductKey"/>
            <TR ALIGN="left">
              <TD>
                <xsl:value-of select="Class"/>
              </TD>
              <TD>
                <xsl:value-of select="Section"/>
              </TD>
              <TD>
                <xsl:value-of select="Qty"/>
              </TD>
              <TD>
                <xsl:value-of select="Free"/>
              </TD>
              <TD>
                <xsl:value-of select="ProductKey"/>
              </TD>
              <TD>
                <xsl:value-of select="Details"/>
              </TD>
              <TD>
                <xsl:value-of select="Cost"/>
              </TD>
            </TR>
            <xsl:if test="Class != following::Class[1] or position() = last()">
              <xsl:variable name="LastClass">
                <xsl:value-of select="Class"/>
              </xsl:variable>
              <TR ALIGN="left" bgcolor="lightblue">
                <TD>
                <xsl:value-of select="$LastClass"/> Totals</TD>
                <TD><xsl:value-of select="Bags"/> Bags</TD>
                <TD>
                  <xsl:value-of select="sum(/EmailOrders
                                        /Order/OrderLine
                                        [Class=$LastClass]/Qty)"/>
                </TD>
                <TD>
                  <xsl:value-of select="sum(/EmailOrders
                                        /Order/OrderLine
                                        [Class=$LastClass]/Free)"/>
                </TD>
                <TD/>
                <TD/>
                <TD>
                  <xsl:value-of select="format-number(
                                        sum(
                                        /EmailOrders
                                        /Order/OrderLine
                                        [Class=$LastClass]/Cost),
                                        '#####.##')"/>
                </TD>
              </TR>
            </xsl:if>
          </xsl:for-each>
          <TR ALIGN="left" bgcolor="lightseagreen">
            <TD>Order totals</TD>
            <TD/>
            <TD>
              <xsl:value-of select="sum(/EmailOrders
                                    /Order/OrderLine/Qty)"/>
            </TD>
            <TD>
              <xsl:value-of select="sum(/EmailOrders
                                    /Order/OrderLine/Free)"/>
            </TD>
            <TD/>
            <TD/>
            <TD>
              <xsl:value-of select="format-number(sum(/EmailOrders
                                    /Order/OrderLine/Cost),
                                    '#####.##')"/>
            </TD>
          </TR>
        </xsl:when>
        <xsl:otherwise>
          <THEAD>
            <TH>List</TH>
            <TH>Paid</TH>
            <TH>Free</TH>
            <TH>Ref</TH>
            <TH>Title</TH>
            <TH>Total</TH>
          </THEAD>
          <xsl:for-each select="OrderLine">
            <xsl:sort select="ProductKey"/>
            <TR ALIGN="left">
              <TD>
                <xsl:value-of select="Section"/>
              </TD>
              <TD>
                <xsl:value-of select="Qty"/>
              </TD>
              <TD>
                <xsl:value-of select="Free"/>
              </TD>
              <TD>
                <xsl:value-of select="ProductKey"/>
              </TD>
              <TD>
                <xsl:value-of select="Details"/>
              </TD>
              <TD>
                <xsl:value-of select="Cost"/>
              </TD>
            </TR>
            <xsl:if test="Section != following::Section[1] 
                          or position() = last()">
              <xsl:variable name="LastSection">
                <xsl:value-of select="Section"/>
              </xsl:variable>
              <TR ALIGN="left" bgcolor="lightblue">
                <TD>
                <xsl:value-of select="Section"/> totals</TD>
                <TD>
                  <xsl:value-of select="sum(/EmailOrders
                                        /Order/OrderLine
                                        [Section=$LastSection]/Qty)"/>
                </TD>
                <TD>
                  <xsl:value-of select="sum(/EmailOrders
                                        /Order/OrderLine
                                        [Section=$LastSection]/Free)"/>
                </TD>
                <TD/>
                <TD/>
                <TD>
                  <xsl:value-of select="format-number(
                                        sum(/EmailOrders
                                        /Order/OrderLine
                                        [Section=$LastSection]
                                        /Cost),
                                        '#####.##')"/>
                </TD>
              </TR>
            </xsl:if>
          </xsl:for-each>
          <TR ALIGN="left" bgcolor="lightseagreen">
            <TD>Order totals</TD>
            <TD>
              <xsl:value-of select="sum(/EmailOrders
                                    /Order/OrderLine/Qty)"/>
            </TD>
            <TD>
              <xsl:value-of select="sum(/EmailOrders
                                    /Order/OrderLine/Free)"/>
            </TD>
            <TD/>
            <TD/>
            <TD>
              <xsl:value-of select="format-number(
                                    sum(/EmailOrders
                                    /Order/OrderLine
                                    /Cost),
                                    '#####.##')"/>
            </TD>
          </TR>
        </xsl:otherwise>
      </xsl:choose>
    </TABLE>
    <BR/>
    <BR/>
  </xsl:template>
</xsl:stylesheet>

1 个答案:

答案 0 :(得分:1)

问题归结为此行(或Section元素的类似行。

<xsl:if test="Class != following::Class[1] or position() = last()">

此处following轴的使用不依赖于Class元素的排序顺序,而是原始文档中Class元素的顺序。这就是您在重新订购文档时获得不同结果的原因。

如何解决?那么,你需要采取另一种方法。这实际上是分组问题的一个例子。有了这些问题,您可以使用的XSLT版本非常重要,因为在XSLT 2.0中,分组的处理方式与在XSLT 1.0中的处理方式不同。

您的样式表已指定version="2.0",但您也使用urn:schemas-microsoft-com:xslt,Microsoft不喜欢XSLT 1.0。 (您可以使用XSLT 1.0处理器运行XSLT 2.0样式表,但它只会忽略它无法识别的命令)

假设使用XSLT 1.0,您将使用名为Muenchian Grouping

的技术

为了这个答案的目的,专注于Class元素,你可以像这样定义一个键:

<xsl:key name="OrderLine" match="OrderLine" use="Class" />

然后,您将获得不同的Class元素,这些元素构成每个组的开头,如下所示:

<xsl:for-each select="OrderLine[generate-id() = generate-id(key('OrderLine', Class)[1])]">
   <xsl:sort select="Class"/>

要获得组成该组的OrderLine元素(即具有相同类的所有OrderLine元素,您可以这样做:

<xsl:apply-templates select="key('OrderLine', Class)">
    <xsl:sort select="ProductKey"/>
</xsl:apply-templates>

key函数也可用于对类的总和求和。

试试这个(非常简化的)XSLT

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes" />

    <xsl:key name="OrderLine" match="OrderLine" use="Class" />

    <xsl:template match="/">
        <xsl:apply-templates select="EmailOrders/Order"/>
    </xsl:template>

    <xsl:template match="Order">
        <TABLE>
        <THEAD>
            <TH>Class</TH>
            <TH>No.</TH>
            <TH>Title</TH>
            <TH>Price</TH>
        </THEAD>
        <xsl:for-each select="OrderLine[generate-id() = generate-id(key('OrderLine', Class)[1])]">
            <xsl:sort select="Class"/>
            <xsl:apply-templates select="key('OrderLine', Class)">
                <xsl:sort select="ProductKey"/>
            </xsl:apply-templates>
            <TR>
                <TD><xsl:value-of select="Class"/> Totals</TD>
                <TD/>
                <TD/>
                <TD>
                    <xsl:value-of select="format-number(sum(key('OrderLine', Class)/Cost), '#####.##')"/>
                </TD>
            </TR>
        </xsl:for-each>
        <TR>
            <TD>Totals</TD>
            <TD/>
            <TD/>
            <TD>
                <xsl:value-of select="format-number(sum(OrderLine/Cost), '#####.##')"/>
            </TD>
        </TR>
        </TABLE>
    </xsl:template>

    <xsl:template match="OrderLine">
        <TR ALIGN="left">
          <TD><xsl:value-of select="Class"/></TD>
          <TD><xsl:value-of select="ProductKey"/></TD>
          <TD><xsl:value-of select="Details"/></TD>
          <TD><xsl:value-of select="Cost"/></TD>
        </TR>
    </xsl:template>
</xsl:stylesheet>

现在,如果你可以使用XSLT 2.0,你可以使用xsl:for-each-group命令,就像这样

 <xsl:for-each-group select="OrderLine" group-by="Class">

然后,您将使用current-group函数,而不是使用键来获取组中的项目。例如

<xsl:apply-templates select="current-group()">
    <xsl:sort select="ProductKey"/>
</xsl:apply-templates>

同样在总和中

<xsl:value-of select="format-number(sum(current-group()/Cost), '#####.##')"/>