Home > Back-end >  Sort XML with XSL matching specific selectors and keep XML the same
Sort XML with XSL matching specific selectors and keep XML the same

Time:10-14

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:

  1. The root element will always be mpcconfiguration.
  2. I need to sort the <lineitem> elements relative to each other, underneath mpcconfiguration.
  3. 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> whose id is Mstr_ModelSortOrder, and whose parent is an <option> with id Mstr_Information, whose parent is a <category> with id Mstr_Information whose parent is a <lineitem>")
  4. 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.
  5. 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>
  • Related