Reputation: 315
Say I have the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<Test xmlns="http://someorg.org">
<name>
<use value="usual"/>
<family value="family"/>
<given value="given"/>
<suffix value="MSc"/>
</name>
</Test>
and I would like to change the order by for example having given before family so that we have:
<?xml version="1.0" encoding="UTF-8"?>
<Test xmlns="http://someorg.org">
<name>
<use value="usual"/>
<given value="given"/>
<family value="family"/>
<suffix value="MSc"/>
</name>
</Test>
I tried using xsl:paply.template as below but the problem is that since apply-templates applies templates to all child nodes, it would also output nodes whose order has being changed again and I do not know how I can prevent it from applying templates to nodes whose order has already being changed.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://someorg.org"
xmlns="http://someorg.org"
exclude-result-prefixes="f xsl">
<xsl:template match="*">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="@* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match = "f:name">
<xsl:apply-templates select = "f:given"/>
<xsl:apply-templates select = "f:family"/>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Upvotes: 2
Views: 5577
Reputation: 243449
Here is a generic and short, complete XSLT 1.0 solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://someorg.org">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vOrdered" select="'|use|given|family|suffix|'"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="my:name">
<xsl:copy>
<xsl:apply-templates select="*">
<xsl:sort select="substring-before($vOrdered, concat('|',name(),'|'))"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Test xmlns="http://someorg.org">
<name>
<use value="usual"/>
<family value="family"/>
<given value="given"/>
<suffix value="MSc"/>
</name>
</Test>
the wanted, correct result, is produced:
<Test xmlns="http://someorg.org">
<name>
<use value="usual"/>
<given value="given"/>
<family value="family"/>
<suffix value="MSc"/>
</name>
</Test>
Do Note:
Only a single <xsl:apply-templates>
instruction is used and the code of the templates will not be changed if we want to specify new order of the children of the f:name
element.
The ordering of elements can be specified in a separate XML document that can be dynamically obtained using a standard function, such as document()
into the current XSLT stylesheet and used as an independent "configuration file". Then the transformation will never have to be modified when new ordering is wanted -- only the config file needs to be modified.
Upvotes: 3
Reputation: 167516
To change the order you need to have the apply-templates
in the order you want, i.e.
<xsl:template match = "f:name">
<xsl:copy>
<xsl:apply-templates select="f:use"/>
<xsl:apply-templates select="f:given"/>
<xsl:apply-templates select="f:family"/>
<xsl:apply-templates select="f:suffix"/>
</xsl:copy>
</xsl:template>
With XSLT 2.0 it is easier to write
<xsl:template match = "f:name">
<xsl:copy>
<xsl:apply-templates select="f:use, f:given, f:family, f:suffix"/>
</xsl:copy>
</xsl:template>
Upvotes: 3