I need an XSLT transform that copies the whole input document to the output document, inserting a specific element with a default value if the input document does not contain that element.
Specifically, I want to transform input XML of this general form
<Begin>
<tag1>a</tag1>
<tag2>b</tag2>
</Begin>
to itself, but if the input has no /Begin/tag1
then I want to provide one. That is, I want to transform
<Begin>
<tag2>c</tag2>
</Begin>
to
<Begin>
<tag1>x</tag1>
<tag2>c</tag2>
</Begin>
This is my my current XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It copies the whole input document, but it does not provide for supplying a default <tag1>
element. I cannot use <xsl:choose>
inside the template in that stylesheet, as it will insert a <tag1>
into ever element that does not have one, and I cannot place a choose
after the template as it's not valid. How can I provide a default <tag1>
element only for the <Begin>
element?
You are correct that you cannot put an <xsl:choose>
element outside a template. XSL operates by transforming nodes of the input document; you can express the details of the transformation only in terms of how specific nodes are transformed -- i.e. via templates.
Moreover, it is usually more natural to express alternatives by providing alternative templates or select
expressions than by using logical elements such as <xsl:choose>
of <xsl:if>
inside templates.
In this case you have two alternatives to consider:
- The input document's
/Begin
element has a tag1
child
- The input document's
/Begin
element does not have a tag1
child
In the first case, you don't need to do anything special -- the identity transform you've already written will do the right thing. All you need, then, is an appropriate template to handle the latter alternative. As long as it has higher priority than the identity transform, it will be used for the Begin
element instead of the identity transform, wherever it applies.
For example,
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Begin[count(tag1) = 0]">
<xsl:copy>
<tag1>x</tag1>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note the added template, which applies (only) to Begin
elements that have no <tag1>
child. It copies the whole element and its child nodes, but also inserts a <tag1>
.
You can add a template that matches Begin
if it has no tag1
and inserts the tag1
. Note here that this works for your very simple example, but if you have a more complex case with a lot of elements or multiple elements to insert, then the solution could become more complex.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Begin[not(tag1)]">
<xsl:copy>
<xsl:apply-templates select="@*" />
<tag1>x</tag1>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>