J3FFK
J3FFK

Reputation: 684

XSLT sort parent element based on child element attribute

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>

Upvotes: 2

Views: 2237

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 117175

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.

Upvotes: 5

Related Questions