I need to ensure that an XML document always contains these nodes:
<group>
<section>0001</section>
<head>0002</head>
<body>0003</body>
</group>
A typical XML input file looks like this (the group node should always precede category and summary):
<story>
<group>
<section>section-content</section>
<head>head-content</head>
<body>body-content</body>
<extra>extra-content</extra>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
But there is no guarantee that the group element or any of it’s child elements will exist.
So given an XML document:
<story>
<category>some text</category>
<summary>some text</summary>
</story>
The output should look like:
<story>
<group>
<section>0001</section>
<head>0002</head>
<body>0003</body>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
The XSLT should not modify existing content eg.
<story>
<group>
<section>existing text</section>
<extra>existing text</extra>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
Should transform to:
<story>
<group>
<section>existing text</section>
<head>0002</head>
<body>0003</body>
<extra>existing text</extra>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
Using Tomalak’s answer as a basis, I came up with this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:subst="http://tempuri.com/mysubst"
>
<xsl:strip-space elements="*"/>
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<!-- These defaults (elements and contents) can be modified at any time -->
<subst:defaults>
<subst:element name="group">
<subst:section>0001</subst:section>
<subst:head>0002</subst:head>
<subst:body>0002</subst:body>
</subst:element>
</subst:defaults>
<!-- this makes the above available as a variable -->
<xsl:variable name="defaults" select="document('')/*/subst:defaults" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="story[not(group)]">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:element name="group"></xsl:element>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="group">
<xsl:copy>
<xsl:copy-of select="@*|*"/>
<xsl:call-template name="create-defaults" />
</xsl:copy>
</xsl:template>
<!-- Insert the defaults-->
<xsl:template name="create-defaults">
<xsl:variable name="this" select="." />
<xsl:for-each select="$defaults/subst:element[@name = name($this)]/*">
<xsl:if test="not($this/*[name() = local-name(current())])">
<xsl:apply-templates select="." />
</xsl:if>
</xsl:for-each>
</xsl:template>
<!-- Remove the namespaces -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@*|node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It seems works if the group element already exists, but I can’t figure out how to get it to work if the group element doesn’t exist.
This XSLT 1.0 transformation
when applied to this input
produces: