2

I am sorting the xml file using xslt and i am facing small problem here..

My XML file is given below:

<?xml version="1.0" encoding="ISO-8859-1"?>
    <bulkCmConfigDataFile xmlns="a.xsd" xmlns:xn="b.xsd" xmlns:subs="c.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="a.xsd a.xsd">
    <fileHeader fileFormatVersion="32.615 V5.0"/>
    <configData dnPrefix="" log="0" mediation="false">
    <subs:SuMSubscriberProfile id="378466">
    <subs:SuMSubscriptionProfile id="1">
    <subs:ImsServiceProfile id="1" modifier="create">
    <subs:attributes>
    <subs:chargingIdx>1</subs:chargingIdx>
    </subs:attributes>
    </subs:ImsServiceProfile>
    </subs:SuMSubscriptionProfile>
    </subs:SuMSubscriberProfile>
    <subs:SuMSubscriberProfile id="378460">
    <subs:SuMSubscriptionProfile id="1">
    <subs:ImsServiceProfile id="1" modifier="create">
    <subs:attributes>
    <subs:chargingIdx>2</subs:chargingIdx>
    </subs:attributes>
    </subs:ImsServiceProfile>
    </subs:SuMSubscriptionProfile>
    </subs:SuMSubscriberProfile>
    </configData>
    <fileFooter dateTime="2015-03-14T10:10:10"/>
    </bulkCmConfigDataFile>

and I am using following style sheet:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="a.xsd"
    xmlns:xn="b.xsd" xmlns:subs="c.xsd">
    <xsl:output method="xml" indent="yes" version="1.0"
        encoding="ISO-8859-1" />
    <xsl:strip-space elements="*" />

    <xsl:param name="outerMatchElement" />
    <xsl:param name="innerMatchElement" />
    <xsl:param name="sortBy" />


    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[name()=$outerMatchElement]">
        <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:choose>
                <xsl:when test="count(*[name()=$innerMatchElement]) = 0">
                    <xsl:apply-templates select="*[name()=$sortBy]">
                        <xsl:sort select="@id" data-type="number" />
                    </xsl:apply-templates>
                </xsl:when>
                <xsl:when test="count(*[name()=$innerMatchElement]) = 1">
                    <xsl:apply-templates select="*[name()=$innerMatchElement]/*[name()=$sortBy]">
                        <xsl:sort select="@id" data-type="number" />
                    </xsl:apply-templates>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates select="*[name()=$innerMatchElement]">
                        <xsl:sort select="*[name()=$sortBy]/@id" data-type="number" />
                    </xsl:apply-templates>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

where outerMatchElement = bulkCmConfigDataFile, innerMatchElement=configData , sortBy=subs:SuMSubscriberProfile

I am getting following result using this stylesheet:

<?xml version="1.0" encoding="ISO-8859-1"?><bulkCmConfigDataFile xmlns="a.xsd" xmlns:xn="b.xsd" xmlns:subs="c.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="configData.xsd configData.xsd">
    <subs:SuMSubscriberProfile id="378460">
    <subs:SuMSubscriptionProfile id="1">
    <subs:ImsServiceProfile id="1" modifier="create">
    <subs:attributes>
    <subs:chargingIdx>2</subs:chargingIdx>
    </subs:attributes>
    </subs:ImsServiceProfile>
    </subs:SuMSubscriptionProfile>
    </subs:SuMSubscriberProfile>
    <subs:SuMSubscriberProfile id="378466">
    <subs:SuMSubscriptionProfile id="1">
    <subs:ImsServiceProfile id="1" modifier="create">
    <subs:attributes>
    <subs:chargingIdx>1</subs:chargingIdx>
    </subs:attributes>
    </subs:ImsServiceProfile>
    </subs:SuMSubscriptionProfile>
    </subs:SuMSubscriberProfile>
    </bulkCmConfigDataFile>

Expected output is following:

<?xml version="1.0" encoding="ISO-8859-1"?>
        <bulkCmConfigDataFile xmlns="a.xsd" xmlns:xn="b.xsd" xmlns:subs="c.xsd"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="configData.xsd configData.xsd">
        <fileHeader fileFormatVersion="32.615 V5.0"/>
        <configData dnPrefix="" log="0" mediation="false">
        <subs:SuMSubscriberProfile id="378460">
        <subs:SuMSubscriptionProfile id="1">
        <subs:ImsServiceProfile id="1" modifier="create">
        <subs:attributes>
        <subs:chargingIdx>2</subs:chargingIdx>
        </subs:attributes>
        </subs:ImsServiceProfile>
        </subs:SuMSubscriptionProfile>
        </subs:SuMSubscriberProfile>
        <subs:SuMSubscriberProfile id="378466">
        <subs:SuMSubscriptionProfile id="1">
        <subs:ImsServiceProfile id="1" modifier="create">
        <subs:attributes>
        <subs:chargingIdx>1</subs:chargingIdx>
        </subs:attributes>
        </subs:ImsServiceProfile>
        </subs:SuMSubscriptionProfile>
        </subs:SuMSubscriberProfile>
        </configData>
        <fileFooter dateTime="2015-03-14T10:10:10"/>
        </bulkCmConfigDataFile>

You will notice difference is File Header and File footer tags are missing in output i am getting, please guide me how to resolve this issue?I am trying to make sorting process generic for my xml files.

1 Answer 1

1

In your given input sample you only have 1 element matching the $innerMatchElement variable, so the second xsl:when applies. However, in the relevant block of code that gets executed in this case, you are only selecting the child elements of the "innerMatchElement" element, so any other children are ignored.

Try changing the second xsl:when to this instead.

<xsl:when test="count(*[name()=$innerMatchElement]) = 1">
   <xsl:apply-templates select="*[following-sibling::*[name()=$innerMatchElement]]" />
   <xsl:apply-templates select="*[name()=$innerMatchElement]/*[name()=$sortBy]">
      <xsl:sort select="@id" data-type="number" />
    </xsl:apply-templates>
    <xsl:apply-templates select="*[preceding-sibling::*[name()=$innerMatchElement]]" />
</xsl:when>

So, you initially select the elements that occur before the "innermatchElement" elements, then you select the inner match one, and then finally select the elements that occur after it.

You may have to add similar lines to the other two conditions.

Note, if you want the "innerMatchElement" tag too, you can achieve this by creating a new one with xsl:element

<xsl:when test="count(*[name()=$innerMatchElement]) = 1">
   <xsl:apply-templates select="*[following-sibling::*[name()=$innerMatchElement]]" />
   <xsl:element name="{$innerMatchElement}">
      <xsl:apply-templates select="*[name()=$innerMatchElement]/@*" />
      <xsl:apply-templates select="*[name()=$innerMatchElement]/*[name()=$sortBy]">
         <xsl:sort select="@id" data-type="number" />
      </xsl:apply-templates>
  </xsl:element>
  <xsl:apply-templates select="*[preceding-sibling::*[name()=$innerMatchElement]]" />
</xsl:when>

Alternatively, you could make better use of template matching. Try this XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="a.xsd" xmlns:xn="b.xsd" xmlns:subs="c.xsd">
    <xsl:output method="xml" indent="yes" version="1.0" encoding="ISO-8859-1" />
    <xsl:strip-space elements="*" />

    <xsl:param name="outerMatchElement" select="'bulkCmConfigDataFile'" />
    <xsl:param name="innerMatchElement" select="'configData'" />
    <xsl:param name="sortBy" select="'subs:SuMSubscriberProfile'" />

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

    <xsl:template match="*[name()=$outerMatchElement]">
        <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:choose>
                <xsl:when test="count(*[name()=$innerMatchElement]) = 0">
                    <xsl:apply-templates select="*[name()=$sortBy]">
                        <xsl:sort select="@id" data-type="number" />
                    </xsl:apply-templates>
                </xsl:when>
                <xsl:when test="count(*[name()=$innerMatchElement]) = 1">
                    <xsl:apply-templates select="*[name()=$innerMatchElement]" mode="single" />
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates select="*[name()=$innerMatchElement]">
                        <xsl:sort select="*[name()=$sortBy]/@id" data-type="number" />
                    </xsl:apply-templates>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*[name()=$innerMatchElement]" mode="single">
        <xsl:apply-templates select="preceding-sibling::*" />
        <xsl:copy>
            <xsl:apply-templates select="@*" />
            <xsl:apply-templates select="*[name()=$sortBy]">
                <xsl:sort select="@id" data-type="number" />
             </xsl:apply-templates>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::*" />
    </xsl:template>
</xsl:stylesheet>

As an aside, it is actually an error in XSLT 1.0 to have a variable in a template match (see http://www.w3.org/TR/xslt#section-Defining-Template-Rules), so this should actually fail

<xsl:template match="*[name()=$outerMatchElement]">

However, some XSLT 1.0 allow it, as is possibly in your case. If you do switch to a processor that gets an error, the solution would be to do something like this...

<xsl:template match="*">
    <xsl:choose>
        <xsl:when test="name()=$outerMatchElement">
            <!-- Existing code -->
        </xsl:when>
        <xsl:otherwise>
           <!-- Identity Template -->
        </xsl:otherwise>
   </xsl:choose>
</xsl:template>
Sign up to request clarification or add additional context in comments.

3 Comments

could you please also tell , how we can get config tag as well.that is also missing.. I had not noticed it earlier, so edited expected output as well. Thanks.
Tim C, I am trying to write some generic code to sort any type of XML means structure of XML can be anything. I just want to provide the sort by parameter and want to sort xml based on id? Is it possible ? or the way i am going is correct ?
XML can vary wildly in structure. It would be a near thankless task trying to write a single XSLT that could sort any XML you pass it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.