Reputation: 13063
I want to map an input XML to another XML, simply writing values from the input to different tags in the output.
As a simple example, the following:
<root1>
<a1>valA<a1>
<b1>valB<b2>
</root1>
Needs to become:
<root2>
<a2>valA</a2>
<b2>valB</b2>
</root2>
Currently I have the following in my XSLT:
<xsl:apply-templates match="root1" />
<xsl:template match="root1">
<a2>
<xsl:value-of select="a1" />
</a2>
<b2>
<xsl:value-of select="b1" />
</b2>
</xsl:template>
The problem is that I don't want empty tags in my output. If valA
and valB
are empty, I would get:
<root2>
<a2></a2>
<b2></b2>
<root2>
But I want to omit the empty tags. I would have thought that there could be an attribute to xsl:output
for this, but there isn't... I came across this question on SO: XSLT: How to exclude empty elements from my result? - but the answer is indirect, it specifies a second stylesheet to strip empty output elements after the first transformation.
I need this to be done with one stylesheet. Surely there must be something more concise then doing:
<xsl:if test="string-length(a1) != 0">
<a2>
<xsl:value-of select="a1" />
</a2>
</xsl:if>
or even:
<xsl:template match="a1[string-length(.) != 0]">
<a2>
<xsl:value-of select="." />
</a2>
</xsl:template>
repeated for each element??
Upvotes: 1
Views: 4529
Reputation: 441
An alternative way would be to "delay" the outputting of your output. Let me explain. By putting your output into a variable, you prevent the output from begin output and then using copy-of you can finally output the output but carefully using an XPath expression that filters the empty tags, as follows:
Let's say your source XML is:
<root1>
<a1>valA</a1>
<b1>valB</b1>
<c1>valC</c1>
<d1></d1>
<e1>valE</e1>
<f1></f1>
</root1>
Then you can put your output in the variable, without caring for xsl:if checks or template matches:
<xsl:template match="/root1">
<xsl:variable name="output" as="node()*">
<a1><xsl:value-of select="a1"/></a1>
<b1><xsl:value-of select="b1"/></b1>
<c1><xsl:value-of select="c1"/></c1>
<d1><xsl:value-of select="d1"/></d1>
<e1><xsl:value-of select="e1"/></e1>
<f1><xsl:value-of select="f1"/></f1>
</xsl:variable>
<root1>
<xsl:copy-of select="$output[text()]"/>
</root1>
</xsl:template>
Using $output[text()] allows you to keep only the tags which have a textual content.
More over, if you want to apply on all tags, you can automate a bit with a xsl:for-each loop:
<xsl:template match="/">
<xsl:variable name="output" as="node()*">
<xsl:for-each select="./*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:variable>
<root1>
<xsl:copy-of select="$output[text()]"/>
</root1>
</xsl:template>
Upvotes: 1
Reputation: 70608
What you could do is have a generic template that matches any 'leaf' element that has no text in it, and then just ignore such an element
<xsl:template match="*[not(*)][not(normalize-space())]" />
You could combine this with a template that matches just a1
, b1
elements without any conditions, which would do your transformation into a2
, b2
as currently
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root1">
<root2>
<xsl:apply-templates select="@*|node()"/>
</root2>
</xsl:template>
<xsl:template match="a1">
<a2>
<xsl:apply-templates select="@*|node()"/>
</a2>
</xsl:template>
<xsl:template match="b1">
<b2>
<xsl:apply-templates select="@*|node()"/>
</b2>
</xsl:template>
<xsl:template match="*[not(*)][not(normalize-space())]" />
</xsl:stylesheet>
Do note the use of the XSLT identity template, which is not needed in your particular example, as you are renaming all elements, but would be used if you did want to keep some elements the same.
Upvotes: 0
Reputation: 167516
Your attempts are fine in my view although instead of testing string-length many people use e.g. <xsl:template match="a1[normalize-space()]"><a2><xsl:value-of select="."/></a2></xsl:template>
instead. But if you need to check whether an element is empty then you need some predicate or test expression, there is no setting you can switch on globally.
Upvotes: 1