Reputation: 7218
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 id
s of the 3 <material>
nodes: XYZ123
, FB2C53XXXA
, ABC123
. I need those sorted by their id
s 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
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:
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.
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