I wonder how it is done by XSLT 1.0 a transformation that passes the result of one template as an input for another within a single .xslt file:
Raw XML Input > call-template(1) > re-formatted XML > call-template(2) on the re-formatted XML
Case:
I want to write a template to re-arrange an xml so that the attributes become elements; and then run another template on the resulting xml of the first one to remove the duplicates. I can have two xsl files and pass the result of the first transformation to the second. However, I want to do it in one xslt.
The raw input xml is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<resource>
<properties>
<property name="name" value="Foo"/>
<property name="service" value="Bar"/>
<property name="version" value="1"/>
</properties>
</resource>
<resource>
<properties>
<property name="name" value="Foo"/>
<property name="service" value="Bar"/>
<property name="version" value="2"/>
</properties>
</resource>
<resource>
<properties>
<property name="name" value="AnotherFoo"/>
<property name="service" value="AnotherBar"/>
<property name="version" value="1"/>
</properties>a
</resource>
</resources>
When I apply the following xslt:
<?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" indent="yes" />
<xsl:template name="attributes-to-elements" match="/resources">
<xsl:call-template name="get-resources" />
</xsl:template>
<xsl:template name="get-resources">
<xsl:text>
</xsl:text>
<products>
<xsl:for-each select="resource">
<xsl:call-template name="convert-attributes-to-elements" />
</xsl:for-each>
</products>
</xsl:template>
<xsl:template name="convert-attributes-to-elements">
<product>
<name>
<xsl:value-of select="properties/property[@name='name']/@value" />
</name>
<service>
<xsl:value-of select="properties/property[@name='service']/@value" />
</service>
</product>
</xsl:template>
</xsl:stylesheet>
I am able to re-format the xml without the versions and get the output which does not contain versions and the attributes has become elements:
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product>
<name>Foo</name>
<service>Bar</service>
</product>
<product>
<name>Foo</name>
<service>Bar</service>
</product>
<product>
<name>AnotherFoo</name>
<service>AnotherBar</service>
</product>
</products>
Now, the thing I want is to pass this modified xml to some template as the input xml and remove the duplicates. Finally, I want to get an xml like:
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product>
<name>Foo</name>
<service>Bar</service>
</product>
<product>
<name>AnotherFoo</name>
<service>AnotherBar</service>
</product>
</products>
I would use a mode to separate processing steps, and of course in XSLT 1.0 you also need an extension function like exsl:node-set
or msxsl:node-set
to be able to further process a result tree fragment created in another template:
<?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"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="msxsl exsl">
<xsl:output method="xml" indent="yes" />
<xsl:key name="group" match="product" use="concat(name, '|', service)"/>
<xsl:template match="@* | node()" mode="step2">
<xsl:copy>
<xsl:apply-templates select="@* | node()" mode="step2"/>
</xsl:copy>
</xsl:template>
<xsl:template match="product[not(generate-id() = generate-id(key('group', concat(name, '|', service))[1]))]" mode="step2"/>
<xsl:template name="attributes-to-elements" match="/resources">
<xsl:variable name="step1-rtf">
<xsl:call-template name="get-resources" />
</xsl:variable>
<xsl:apply-templates select="exsl:node-set($step1-rtf)/*" mode="step2"/>
</xsl:template>
<xsl:template name="get-resources">
<xsl:text>
</xsl:text>
<products>
<xsl:for-each select="resource">
<xsl:call-template name="convert-attributes-to-elements" />
</xsl:for-each>
</products>
</xsl:template>
<xsl:template name="convert-attributes-to-elements">
<product>
<name>
<xsl:value-of select="properties/property[@name='name']/@value" />
</name>
<service>
<xsl:value-of select="properties/property[@name='service']/@value" />
</service>
</product>
</xsl:template>
</xsl:stylesheet>
You will need to check whether your XSLT processor supports exsl:node-set
, MSXML needs msxsl:node-set
instead.