I am attempting a sort-only transform of my xml using XSLT 1.0
. I don't need any changes to the transformed xml other than order/sequence.
I created a stripped-down version of my xml that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<mpcconfiguration>
<lineitem id="0">
<seriesdesc>series1</seriesdesc>
<modeldesc>model1</modeldesc>
<labels>
<label id="ExtPrice">Extended Price</label>
</labels>
<category id="Mstr_Information">
<description>Model Information</description>
<option id="Mstr_Information">
<description>descr1</description>
<unitprice>0</unitprice>
<property id="ExtPrice">0</property>
<property id="Mstr_ModelSortOrder">3</property>
</option>
</category>
<category id="Category1">
<description>a cool category</description>
<option id="option123">
<description>a cool option</description>
<unitprice>0</unitprice>
<property id="Mstr_ModelSortOrder">777</property>
</option>
</category>
</lineitem>
<lineitem id="1">
<seriesdesc>series2</seriesdesc>
<modeldesc>model2</modeldesc>
<labels>
<label id="ExtPrice">Extended Price</label>
</labels>
<category id="Mstr_Information">
<description>Model Information</description>
<option id="Mstr_Information">
<description>descr1</description>
<unitprice>0</unitprice>
<property id="ExtPrice">0</property>
<property id="Mstr_ModelSortOrder">1</property>
</option>
</category>
<category id="Category2">
<description>a cool category</description>
<option id="option123">
<description>a cool option</description>
<unitprice>0</unitprice>
<property id="Mstr_ModelSortOrder">999</property>
</option>
</category>
</lineitem>
<lineitem id="2">
<seriesdesc>series3</seriesdesc>
<modeldesc>model3</modeldesc>
<labels>
<label id="ExtPrice">Extended Price</label>
</labels>
<category id="Mstr_Information">
<description>Model Information</description>
<option id="Mstr_Information">
<description>descr1</description>
<unitprice>0</unitprice>
<property id="ExtPrice">0</property>
<property id="Mstr_ModelSortOrder">2</property>
</option>
</category>
<category id="Category3">
<description>a cool category</description>
<option id="option123">
<description>a cool option</description>
<unitprice>0</unitprice>
<property id="Mstr_ModelSortOrder">555</property>
</option>
</category>
</lineitem>
</mpcconfiguration>
Here are the important aspects to focus on:
- The root element will always be
mpcconfiguration
. - I need to sort the
<lineitem>
elements relative to each other, underneathmpcconfiguration
. - The sort sequence should be driven by the value of
/mpcconfiguration/lineitem/category@id=Mstr_Information/option@id=Mstr_Information/property@id=Mstr_ModelSortOrder
(that pseudocode means in plain English: "sort by the value of the<property>
whoseid
isMstr_ModelSortOrder
, and whose parent is an<option>
with idMstr_Information
, whose parent is a<category>
with idMstr_Information
whose parent is a<lineitem>
") - Notice the
<property
elements with values such as 555, 777, and 999. Those can be ignored for sort purposes because their ancestors don't match the pattern I described in #3. All that data still has to be in the transformed xml, but those have no bearing on sort. - There will be only one
<property id="Mstr_ModelSortOrder">XXX</property>
per<lineitem>
whose ancestry matches the pattern described in #3 above.
Here is the desired/transformed XML if the XSL I'm trying to work out behaves correctly:
<?xml version="1.0" encoding="UTF-8"?>
<mpcconfiguration>
<lineitem id="1">
<seriesdesc>series2</seriesdesc>
<modeldesc>model2</modeldesc>
<labels>
<label id="ExtPrice">Extended Price</label>
</labels>
<category id="Mstr_Information">
<description>Model Information</description>
<option id="Mstr_Information">
<description>descr1</description>
<unitprice>0</unitprice>
<property id="ExtPrice">0</property>
<property id="Mstr_ModelSortOrder">1</property>
</option>
</category>
<category id="Category2">
<description>a cool category</description>
<option id="option123">
<description>a cool option</description>
<unitprice>0</unitprice>
<property id="Mstr_ModelSortOrder">999</property>
</option>
</category>
</lineitem>
<lineitem id="2">
<seriesdesc>series3</seriesdesc>
<modeldesc>model3</modeldesc>
<labels>
<label id="ExtPrice">Extended Price</label>
</labels>
<category id="Mstr_Information">
<description>Model Information</description>
<option id="Mstr_Information">
<description>descr1</description>
<unitprice>0</unitprice>
<property id="ExtPrice">0</property>
<property id="Mstr_ModelSortOrder">2</property>
</option>
</category>
<category id="Category3">
<description>a cool category</description>
<option id="option123">
<description>a cool option</description>
<unitprice>0</unitprice>
<property id="Mstr_ModelSortOrder">555</property>
</option>
</category>
</lineitem>
<lineitem id="0">
<seriesdesc>series1</seriesdesc>
<modeldesc>model1</modeldesc>
<labels>
<label id="ExtPrice">Extended Price</label>
</labels>
<category id="Mstr_Information">
<description>Model Information</description>
<option id="Mstr_Information">
<description>descr1</description>
<unitprice>0</unitprice>
<property id="ExtPrice">0</property>
<property id="Mstr_ModelSortOrder">3</property>
</option>
</category>
<category id="Category1">
<description>a cool category</description>
<option id="option123">
<description>a cool option</description>
<unitprice>0</unitprice>
<property id="Mstr_ModelSortOrder">777</property>
</option>
</category>
</lineitem>
</mpcconfiguration>
Notice that the 2 xml examples are identical except that the <lineitem>
nodes are in a different sequence, sorted by:
<property id="Mstr_ModelSortOrder">1</property>
<property id="Mstr_ModelSortOrder">2</property>
<property id="Mstr_ModelSortOrder">3</property>
Here is my feeble attempt at the xsl, though it's not correct:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="utf-8" indent="no" />
<xsl:template match="/">
<xsl:copy-of select="*" />
</xsl:template>
<xsl:template match="mpcconfiguration">
<xsl:copy>
<xsl:apply-templates select="//mpcconfiguration/category/option/property">
<xsl:sort select="@id"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I know there's a fair amount of xml and xsl above, but the summary is quite straightforward: sort all <lineitem>
nodes by the Mstr_ModelSortOrder
xml <property>
, as long as that property has the correct ancestors up the xml tree.
Thanks in advance for any assistance.
CodePudding user response:
This XSLT 1.0 transformation does what you describe
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="utf-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="mpcconfiguration">
<xsl:copy>
<xsl:apply-templates select="@*" />
<xsl:apply-templates select="lineitem">
<xsl:sort select="category[@id='Mstr_Information']/option[@id='Mstr_Information']/property[@id='Mstr_ModelSortOrder']" data-type="number" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>
<mpcconfiguration>
<lineitem id="1">
<seriesdesc>series2</seriesdesc>
<modeldesc>model2</modeldesc>
<labels>
<label id="ExtPrice">Extended Price</label>
</labels>
<category id="Mstr_Information">
<description>Model Information</description>
<option id="Mstr_Information">
<description>descr1</description>
<unitprice>0</unitprice>
<property id="ExtPrice">0</property>
<property id="Mstr_ModelSortOrder">1</property>
</option>
</category>
<category id="Category2">
<description>a cool category</description>
<option id="option123">
<description>a cool option</description>
<unitprice>0</unitprice>
<property id="Mstr_ModelSortOrder">999</property>
</option>
</category>
</lineitem>
<lineitem id="2">
<seriesdesc>series3</seriesdesc>
<modeldesc>model3</modeldesc>
<labels>
<label id="ExtPrice">Extended Price</label>
</labels>
<category id="Mstr_Information">
<description>Model Information</description>
<option id="Mstr_Information">
<description>descr1</description>
<unitprice>0</unitprice>
<property id="ExtPrice">0</property>
<property id="Mstr_ModelSortOrder">2</property>
</option>
</category>
<category id="Category3">
<description>a cool category</description>
<option id="option123">
<description>a cool option</description>
<unitprice>0</unitprice>
<property id="Mstr_ModelSortOrder">555</property>
</option>
</category>
</lineitem>
<lineitem id="0">
<seriesdesc>series1</seriesdesc>
<modeldesc>model1</modeldesc>
<labels>
<label id="ExtPrice">Extended Price</label>
</labels>
<category id="Mstr_Information">
<description>Model Information</description>
<option id="Mstr_Information">
<description>descr1</description>
<unitprice>0</unitprice>
<property id="ExtPrice">0</property>
<property id="Mstr_ModelSortOrder">3</property>
</option>
</category>
<category id="Category1">
<description>a cool category</description>
<option id="option123">
<description>a cool option</description>
<unitprice>0</unitprice>
<property id="Mstr_ModelSortOrder">777</property>
</option>
</category>
</lineitem>
</mpcconfiguration>
Template #1 is the identity template. It matches any node that does not match a more specific template, and copies it verbatim to the output.
Template #2 is the only template that is more specific - it only matches <mpcconfiguration>
, copies it, and invokes matching templates for any attribute nodes @*
(there happen to be none in your input sample) and for any <lineitem>
children, sorted by their respective <property id="Mstr_ModelSortOrder">
. The only matching template for those nodes is the identity template, which does its job and copies them as they are.
<xsl:strip-space elements="*" />
is for convenience, it achieves pretty output with <xsl:output indent="yes" />
.
A shorter version assumes that <mpcconfiguration>
is the top-level element, and copies the <lineitem>
children using <xsl:for-each>
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="utf-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/mpcconfiguration">
<xsl:copy>
<xsl:for-each select="lineitem">
<xsl:sort select="category[@id='Mstr_Information']/option[@id='Mstr_Information']/property[@id='Mstr_ModelSortOrder']" data-type="number" />
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>