HerrimanCoder
HerrimanCoder

Reputation: 7218

How to sort multiple nested element groups of xml with XSLT 1.0?

I found an SO post titled XSLT sort by attribute value whose objective is similar to mine. I am trying to sort xml nodes by attribute value using XSLT 1.0. But when I tried to apply the accepted solution, I got unexpected results.

Here is some very stripped-down xml from my system upon which I'm trying to achieve XSLT-based sorting.

<?xml version="1.0" encoding="UTF-8"?>
<mpcbom xmlns:bv="urn:BomViewXmlUtil" xmlns:msxsl="urn:schemas-microsoft-com:xslt" seriesid="Flatbed_A" seriesdesc="Flatbed A" modelid="FB2" modeldesc="Flatbed Aluminum 123" pricelistid="0">
   <propDefn>
      <prop seriesid="Flatbed_A" id="AddCharges" text="AddCharges" />
   </propDefn>
   <assembly seriesid="Flatbed_A" id="102B-1" name="Flatbed_B" qty="1" price="123" catid="Finish_unit">
      <prop id="PriceOverride" val="False" />
      <prop id="SuggestedPrice" val="54005" />
      <prop id="TabSeqNum" val="0" src="Assembly" />
      <operation seriesid="Flatbed_A" id="ALC-FOB" name="Client delivery" qty="1" price="0" catid="O_ABC123">
         <prop id="HostOptionID" val="ALC-FOB" />
         <prop id="ProcessTimeStd" val="0" />
         <prop id="ProcessTimeXtra" val="0" />
      </operation>
      <assembly seriesid="Flatbed_A" id="102B-2" name="Unit_Offline" qty="1" price="0" catid="Unit_Offline">
         <prop id="HostOptionID" val="" />
         <prop id="ERP_RouteId" val="" />
         <assembly seriesid="Flatbed_A" id="Step70" name="Step70" qty="1" price="0" catid="Step70">
            <prop id="HostOptionID" val="Step70" />
            <prop id="BOMUOM_From_BYOD" val="UN / EA" />
            <operation seriesid="Flatbed_A" id="523-110" name="Washing" qty="1" price="0" catid="O_523_110">
               <prop id="HostOptionID" val="523-110" />
               <prop id="ProcessTimeStd" val="3.5" />
            </operation>
            <operation seriesid="Flatbed_A" id="523-120" name="Finish1" qty="1" price="0" catid="O_523_120">
               <prop id="HostOptionID" val="523-120" />
               <prop id="ProcessTimeStd" val="0.5" />
            </operation>
            <material seriesid="Flatbed_A" id="3UN5020000" name="Rubber1" qty="1" price="0" catid="Rear_Bumper_Option_Assy">
               <prop id="Cost" val="124.9899768" />
               <prop id="ExtCost" val="124.9899768" />
            </material>
            <material seriesid="Flatbed_A" id="3UN5990000" name="Round1" qty="1" price="0" catid="Doc_Holder_Assy">
               <prop id="Cost" val="5.11" />
               <prop id="ExtCost" val="5.11" />
            </material>
         </assembly>
         <assembly seriesid="Flatbed_A" id="102B-3" name="ABC1" qty="1" price="0" catid="Assembled_Unit">
            <prop id="CatID" val="Assembled_Unit" />
            <prop id="HostOptionID" val="102B-3" />
            <assembly seriesid="Flatbed_A" id="Step10" name="Step10" qty="1" price="0" catid="Step10">
               <prop id="HostOptionID" val="Step10" />
               <prop id="BOMUOM_From_BYOD" val="UN / EA" />
               <operation seriesid="Flatbed_A" id="510-110" name="Assemblage 1" qty="1" price="0" catid="O_510_110">
                  <prop id="HostOptionID" val="510-110" />
                  <prop id="ProcessTimeStd" val="13.5" />
               </operation>
               <material seriesid="Flatbed_A" id="XYZ123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
                  <prop id="Cost" val="80.679768" />
                  <prop id="ExtCost" val="80.679768" />
               </material>
               <material seriesid="Flatbed_A" id="FB2C53XXXA" name="53ft 2 axles Flatbed" qty="1" price="0" catid="Structure_Assy">
                  <prop id="Cost" val="12901.27129" />
                  <prop id="ExtCost" val="12901.27129" />
               </material>
               <material seriesid="Flatbed_A" id="ABC123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
                  <prop id="Cost" val="80.679768" />
                  <prop id="ExtCost" val="80.679768" />
               </material>
            </assembly>
            <assembly seriesid="Flatbed_A" id="Step20" name="Step20" qty="1" price="0" catid="Step20">
               <prop id="HostOptionID" val="Step20" />
               <prop id="BOMUOM_From_BYOD" val="UN / EA" />
               <operation seriesid="Flatbed_A" id="510-120" name="Assemblage 2" qty="1" price="0" catid="O_510_120">
                  <prop id="HostOptionID" val="510-120" />
                  <prop id="ProcessTimeStd" val="14" />
               </operation>
               <material seriesid="Flatbed_A" id="DEF123" name="PLANCHER 53" qty="1" price="0" catid="FloorAlu_Assy">
                  <prop id="Cost" val="3629.53568" />
                  <prop id="ExtCost" val="3629.53568" />
               </material>
               <material seriesid="Flatbed_A" id="STU123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
                  <prop id="Cost" val="773.383414" />
                  <prop id="ExtCost" val="773.383414" />
               </material>
               <material seriesid="Flatbed_A" id="ABC123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
                  <prop id="Cost" val="773.383414" />
                  <prop id="ExtCost" val="773.383414" />
               </material>
            </assembly>
         </assembly>
      </assembly>
   </assembly>
</mpcbom>

And here is my base XSLT that does exactly what I need done, minus the sorting:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                exclude-result-prefixes="msxsl">
   <xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes" indent="no" version="1.0" standalone="yes" />
   <xsl:variable name="lowercase">abcdefghijklmnopqrstuvwxyz</xsl:variable>
   <xsl:variable name="uppercase">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>

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

   <xsl:template match="propDefn" />

   <xsl:template match="material|operation">
      <xsl:copy>
         <xsl:apply-templates select="@*" />
         <xsl:apply-templates select="prop">
            <xsl:sort select="translate(@id, $uppercase, $lowercase)" />
         </xsl:apply-templates>
         <xsl:apply-templates select="*[not(self::prop)]" />
      </xsl:copy>
   </xsl:template>

   <xsl:template match="@rid" />
   <xsl:template match="material/@name|material/@name|operation/@name" />
   <xsl:template match="@id|@catid">
      <xsl:attribute name="{local-name()}">
         <xsl:value-of select="translate(., $uppercase, $lowercase)" />
      </xsl:attribute>
   </xsl:template>
</xsl:stylesheet>

Here is the xml result of the transformation above:

<?xml version="1.0" encoding="UTF-8"?>
<mpcbom xmlns:bv="urn:BomViewXmlUtil" xmlns:msxsl="urn:schemas-microsoft-com:xslt" seriesid="Flatbed_A" seriesdesc="Flatbed A" modelid="FB2" modeldesc="Flatbed Aluminum 123" pricelistid="0">
   <assembly seriesid="Flatbed_A" id="102b-1" name="Flatbed_B" qty="1" price="123" catid="finish_unit">
      <prop id="priceoverride" val="False" />
      <prop id="suggestedprice" val="54005" />
      <prop id="tabseqnum" val="0" src="Assembly" />
      <operation seriesid="Flatbed_A" id="alc-fob" qty="1" price="0" catid="o_abc123">
         <prop id="hostoptionid" val="ALC-FOB" />
         <prop id="processtimestd" val="0" />
         <prop id="processtimextra" val="0" />
      </operation>
      <assembly seriesid="Flatbed_A" id="102b-2" name="Unit_Offline" qty="1" price="0" catid="unit_offline">
         <prop id="hostoptionid" val="" />
         <prop id="erp_routeid" val="" />
         <assembly seriesid="Flatbed_A" id="step70" name="Step70" qty="1" price="0" catid="step70">
            <prop id="hostoptionid" val="Step70" />
            <prop id="bomuom_from_byod" val="UN / EA" />
            <operation seriesid="Flatbed_A" id="523-110" qty="1" price="0" catid="o_523_110">
               <prop id="hostoptionid" val="523-110" />
               <prop id="processtimestd" val="3.5" />
            </operation>
            <operation seriesid="Flatbed_A" id="523-120" qty="1" price="0" catid="o_523_120">
               <prop id="hostoptionid" val="523-120" />
               <prop id="processtimestd" val="0.5" />
            </operation>
            <material seriesid="Flatbed_A" id="3un5020000" qty="1" price="0" catid="rear_bumper_option_assy">
               <prop id="cost" val="124.9899768" />
               <prop id="extcost" val="124.9899768" />
            </material>
            <material seriesid="Flatbed_A" id="3un5990000" qty="1" price="0" catid="doc_holder_assy">
               <prop id="cost" val="5.11" />
               <prop id="extcost" val="5.11" />
            </material>
         </assembly>
         <assembly seriesid="Flatbed_A" id="102b-3" name="ABC1" qty="1" price="0" catid="assembled_unit">
            <prop id="catid" val="Assembled_Unit" />
            <prop id="hostoptionid" val="102B-3" />
            <assembly seriesid="Flatbed_A" id="step10" name="Step10" qty="1" price="0" catid="step10">
               <prop id="hostoptionid" val="Step10" />
               <prop id="bomuom_from_byod" val="UN / EA" />
               <operation seriesid="Flatbed_A" id="510-110" qty="1" price="0" catid="o_510_110">
                  <prop id="hostoptionid" val="510-110" />
                  <prop id="processtimestd" val="13.5" />
               </operation>
               <material seriesid="Flatbed_A" id="xyz123" qty="1" price="0" catid="front_rail_assy">
                  <prop id="cost" val="80.679768" />
                  <prop id="extcost" val="80.679768" />
               </material>
               <material seriesid="Flatbed_A" id="fb2c53xxxa" qty="1" price="0" catid="structure_assy">
                  <prop id="cost" val="12901.27129" />
                  <prop id="extcost" val="12901.27129" />
               </material>
               <material seriesid="Flatbed_A" id="abc123" qty="1" price="0" catid="front_rail_assy">
                  <prop id="cost" val="80.679768" />
                  <prop id="extcost" val="80.679768" />
               </material>
            </assembly>
            <assembly seriesid="Flatbed_A" id="step20" name="Step20" qty="1" price="0" catid="step20">
               <prop id="hostoptionid" val="Step20" />
               <prop id="bomuom_from_byod" val="UN / EA" />
               <operation seriesid="Flatbed_A" id="510-120" qty="1" price="0" catid="o_510_120">
                  <prop id="hostoptionid" val="510-120" />
                  <prop id="processtimestd" val="14" />
               </operation>
               <material seriesid="Flatbed_A" id="def123" qty="1" price="0" catid="flooralu_assy">
                  <prop id="cost" val="3629.53568" />
                  <prop id="extcost" val="3629.53568" />
               </material>
               <material seriesid="Flatbed_A" id="stu123" qty="1" price="0" catid="rear_bumper_assy">
                  <prop id="cost" val="773.383414" />
                  <prop id="extcost" val="773.383414" />
               </material>
               <material seriesid="Flatbed_A" id="abc123" qty="1" price="0" catid="rear_bumper_assy">
                  <prop id="cost" val="773.383414" />
                  <prop id="extcost" val="773.383414" />
               </material>
            </assembly>
         </assembly>
      </assembly>
   </assembly>
</mpcbom>

So far so good, but again, no sorting has been attempted yet. Now here is the same XSLT as above, but with my attempt at sorting added:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                exclude-result-prefixes="msxsl">
   <xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes" indent="no" version="1.0" standalone="yes" />
   <xsl:variable name="lowercase">abcdefghijklmnopqrstuvwxyz</xsl:variable>
   <xsl:variable name="uppercase">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>

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

   <xsl:template match="propDefn" />

   <xsl:template match="material|operation">
      <xsl:copy>
         <xsl:apply-templates select="@*" />
         <xsl:apply-templates select="prop">
            <xsl:sort select="translate(@id, $uppercase, $lowercase)" />
         </xsl:apply-templates>
         <xsl:apply-templates select="*[not(self::prop)]" />
      </xsl:copy>
   </xsl:template>

   <xsl:template match="assembly">
    <xsl:copy>
      <xsl:apply-templates select="assembly">
        <xsl:sort select="material/@id"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
  
   <xsl:template match="@rid" />
   <xsl:template match="material/@name|material/@name|operation/@name" />
   <xsl:template match="@id|@catid">
      <xsl:attribute name="{local-name()}">
         <xsl:value-of select="translate(., $uppercase, $lowercase)" />
      </xsl:attribute>
   </xsl:template>
</xsl:stylesheet>

For clarity, here is the section I added (trying to sort):

<xsl:template match="assembly">
    <xsl:copy>
      <xsl:apply-templates select="assembly">
        <xsl:sort select="material/@id"/>
      </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

And here is the disastrous result of the transformation (same xml source as above):

<?xml version="1.0" encoding="UTF-8"?>
<mpcbom xmlns:bv="urn:BomViewXmlUtil" xmlns:msxsl="urn:schemas-microsoft-com:xslt" seriesid="Flatbed_A" seriesdesc="Flatbed A" modelid="FB2" modeldesc="Flatbed Aluminum 123" pricelistid="0">
   <assembly>
      <assembly>
         <assembly>
            <assembly />
            <assembly />
         </assembly>
         <assembly />
      </assembly>
   </assembly>
</mpcbom>

Clearly I'm a noob with XSLT...but here's what I'm trying to achieve: Whenever there are <material> nodes inside an <assembly> node, I want all the <material> nodes sorted by their id attributes in an alphanumeric fashion. For example, take this set of nodes from the source xml:

<material seriesid="Flatbed_A" id="XYZ123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
   <prop id="Cost" val="80.679768" />
   <prop id="ExtCost" val="80.679768" />
</material>
<material seriesid="Flatbed_A" id="FB2C53XXXA" name="53ft 2 axles Flatbed" qty="1" price="0" catid="Structure_Assy">
   <prop id="Cost" val="12901.27129" />
   <prop id="ExtCost" val="12901.27129" />
</material>
<material seriesid="Flatbed_A" id="ABC123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
   <prop id="Cost" val="80.679768" />
   <prop id="ExtCost" val="80.679768" />
</material>

Notice the ids of the 3 <material> nodes: XYZ123, FB2C53XXXA, ABC123. I need those sorted by their ids so that the transformed xml (just for that section) looks like this:

<material seriesid="Flatbed_A" id="ABC123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
   <prop id="Cost" val="80.679768" />
   <prop id="ExtCost" val="80.679768" />
</material>
<material seriesid="Flatbed_A" id="FB2C53XXXA" name="53ft 2 axles Flatbed" qty="1" price="0" catid="Structure_Assy">
   <prop id="Cost" val="12901.27129" />
   <prop id="ExtCost" val="12901.27129" />
</material>
<material seriesid="Flatbed_A" id="XYZ123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
   <prop id="Cost" val="80.679768" />
   <prop id="ExtCost" val="80.679768" />
</material>

Likewise for the 2nd set of <material> nodes. Original:

<material seriesid="Flatbed_A" id="DEF123" name="PLANCHER 53" qty="1" price="0" catid="FloorAlu_Assy">
   <prop id="Cost" val="3629.53568" />
   <prop id="ExtCost" val="3629.53568" />
</material>
<material seriesid="Flatbed_A" id="STU123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
   <prop id="Cost" val="773.383414" />
   <prop id="ExtCost" val="773.383414" />
</material>
<material seriesid="Flatbed_A" id="ABC123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
   <prop id="Cost" val="773.383414" />
   <prop id="ExtCost" val="773.383414" />
</material>

Desired sort, after transformation:

<material seriesid="Flatbed_A" id="ABC123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
   <prop id="Cost" val="773.383414" />
   <prop id="ExtCost" val="773.383414" />
</material>
<material seriesid="Flatbed_A" id="DEF123" name="PLANCHER 53" qty="1" price="0" catid="FloorAlu_Assy">
   <prop id="Cost" val="3629.53568" />
   <prop id="ExtCost" val="3629.53568" />
</material>
<material seriesid="Flatbed_A" id="STU123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
   <prop id="Cost" val="773.383414" />
   <prop id="ExtCost" val="773.383414" />
</material>

How is it done?

Upvotes: 0

Views: 495

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 116992

The problem with your added template is that it applies templates only to child assembly elements - thus removing all other child nodes (along with their descendants) that the parent assembly might have.

The other thing is that you are sorting the assembly elements - but then you explain you actually want to sort the material elements.

Now, since the material elements you want to sort also have other siblings, such as prop, operation and assembly, and these siblings also have an id attribute, you basically have two options:

  1. Apply templates to all, but sort by a node that only material has:

    <xsl:template match="assembly">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()">
                <xsl:sort select="@id[parent::material]"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    

    However, this will always place the material group at the end, even if it's not there in the original.

  2. Apply templates separately to each child node (or group of child nodes) and sort only the group of material elements - e.g.

    <xsl:template match="assembly">
        <xsl:copy>
            <xsl:apply-templates select="@* | prop | operation | assembly"/>
            <xsl:apply-templates select="material">
                <xsl:sort select="@id"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    

    Here you can control the order of the groups by using a separate xsl:apply-templates instruction for each group, in the order you want them.

Upvotes: 1

Related Questions