XSLT sort parent element based on child element at

2019-08-13 17:32发布

问题:

Although this question has been asked multiples times like here and here before and the answers all seem to work for that particular question, I can't get it to work in my situation.

This xml

 <root>
  <Orders>
    <Order type="S">
      <Ref>ABC123</Ref>
      <OrderedBy>
        <Debtor code="13456"></Debtor>
      </OrderedBy>
      <DeliveryMethod code="Truck"></DeliveryMethod>
      <OrderLine line="1">
        <Item code="ABC100400"></Item>
        <Quantity>1</Quantity>
      </OrderLine>
      <OrderLine line="2">
        <Item code="XYZ490204" type="S" searchcode="XYZ490204"></Item>
        <Quantity>2</Quantity>
      </OrderLine>
      <OrderLine line="3">
        <Item code="DEF1210847" type="S" searchcode="DEF1210847"></Item>
        <Quantity>4</Quantity>
      </OrderLine>
    </Order>
    <Order type="S">
      <Ref>ABC123</Ref>
      <OrderedBy>
        <Debtor code="BLABLA" number="802416" type="C"></Debtor>
      </OrderedBy>
      <DeliveryMethod code="Barefoot"></DeliveryMethod>
      <OrderLine line="1">
        <Item code="QQQ123456" type="S" searchcode="QQQ123456"></Item>
        <Quantity>1</Quantity>
      </OrderLine>
      <OrderLine line="2">
        <Item code="JJJ490204" type="S" searchcode="JJJ490204"></Item>
        <Quantity>3</Quantity>
      </OrderLine>
    </Order>
  </Orders>
</root>

needs to be transformed to this xml:

 <root>
      <Orders>
        <Order type="S">
          <Ref>ABC123</Ref>
          <OrderedBy>
            <Debtor code="13456"></Debtor>
          </OrderedBy>
          <DeliveryMethod code="Truck"></DeliveryMethod>
          <OrderLine line="1">
             <Item code="ABC100400"></Item>
            <Quantity>1</Quantity>
          </OrderLine>
          <OrderLine line="3">
            <Item code="DEF1210847"></Item>
            <Quantity>4</Quantity>
          </OrderLine>
          <OrderLine line="2">
            <Item code="XYZ490204"></Item>
            <Quantity>2</Quantity>
          </OrderLine>
        </Order>
        <Order type="S">
          <Ref>ABC123</Ref>
          <OrderedBy>
            <Debtor code="BLABLA"></Debtor>
          </OrderedBy>
          <DeliveryMethod code="Barefoot"></DeliveryMethod>
          <OrderLine line="2">
            <Item code="JJJ490204"></Item>
            <Quantity>3</Quantity>
          </OrderLine>
          <OrderLine line="1">
            <Item code="QQQ123456"></Item>
            <Quantity>1</Quantity>
          </OrderLine>
         </Order>
      </Orders>
    </root>

What I'm trying to do is for each <Order> sort the <OrderLine> elements based on the attributevalue of child <Item>/@code and also strip some attributes of that child. All the other element outside of OrderLine need to be left unchanged. Please don't mind the non-optimal structure of the xml, this can't be changed. It probably will take copy, for each combined with sort, like this, but much better:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

    <xsl:template match="Orders">
        <xsl:copy>
            <xsl:apply-templates select="Order/OrderLine/Item|@*">
                <xsl:sort select="@code" data-type="text"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

回答1:

What I'm trying to do is for each <Order> sort the <OrderLine> elements based on the attributevalue of child <Item>/@code...

If you want to sort the OrderLine elements, you must do so from the context of their parent Order:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="Order">
    <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:apply-templates select="*[not(self::OrderLine)]"/>
        <xsl:apply-templates select="OrderLine">
            <xsl:sort select="Item/@code" data-type="text" order="ascending"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Note: since empty strings sort first, you could shorten the template to:

<xsl:template match="Order">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()">
            <xsl:sort select="Item/@code" data-type="text" order="ascending"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

.. and also strip some attributes of that child.

I didn't see that in your stylesheet. In any case, it's just a matter of adding another template to match Item.



标签: xml sorting xslt