Reputation: 3775
I got the following template from another post..
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pUncertainElName" select="'second'"/>
<xsl:param name="pParentPath" select="'outerElement/innerElement'" />
<xsl:param name="pOrderedNames" select="'|first|second|third|'"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="outerElement/innerElement">
<xsl:variable name="vrtfFirstPass">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<xsl:apply-templates select=
"self::*[not(*[name() = $pUncertainElName])
or
*[name()=$pUncertainElName and @missing-cause]]"
mode="missing"/>
</xsl:copy>
</xsl:variable>
<xsl:apply-templates select="ext:node-set($vrtfFirstPass)/*" mode="pass2"/>
</xsl:template>
<xsl:template match="*[@missing-cause]"/>
<xsl:template match="*" mode="missing">
<xsl:element name="{$pUncertainElName}">
<CharacterString>INSERTED BY TEMPLATE</CharacterString>
</xsl:element>
</xsl:template>
<xsl:template match="outerElement/innerElement" mode="pass2">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort data-type="number" select=
"string-length(substring-before($pOrderedNames,
concat('|', name(), '|')
)
)"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The purpose is to add missing Elements add specific places that are missing in the source document. The sourcedocument looks like this
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<doc>
<outerElement>
<innerElement>
<first>
<textElement>Some Text</textElement>
</first>
<second missing-cause="bla" />
<third>
<textElement>Some Text</textElement>
</third>
</innerElement>
</outerElement>
</doc>
I have to add a lot of elements like this in a similar way so I want to use parameters to specify the parents path and the element I want to insert.
So here comes the first question: How do I use a parameter in a match? match="$parameter" or variants do not seem to work.
And the second one: There is still a problem with adding the element with this template which i think comes from the second pass.
If my document does look like posted above it flattens the output to
<doc>
<outerElement>Some TextSome TextINSERTED BY TEMPLATE</outerElement>
</doc>
if the is missing its working as it should. There is missing something most likely in the buildup of the second path but i can't figure out how to fix this.
And the last.. Is it ok to call this template with different parameters like 20 times on a single document to transform it or should i try something else?
Thanks for help again and sorry I am new to this ;)
Upvotes: 2
Views: 1346
Reputation: 243459
In both XSLT 1.0 and XSLT 2.0 it isn't possible to evaluate an XPath expression dynamically.
Therefore, what you attempt to do with $pParentPath
will not produce the desired result.
As a workaround you may pass two different parameters: pchildName
and pgrandchildName
and use something like this:
*[name()=$pchildName]/*[name()=$pgrandchildName]
In XSLT 1.0 a variable or parameter reference is forbidden in a match pattern. In XSLT 2.0 it is OK.
Here is the transformation, corrected to work on this specific XML document:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pUncertainElName" select="'second'"/>
<xsl:param name="pOrderedNames" select="'|first|second|third|'"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="innerElement">
<xsl:variable name="vrtfFirstPass">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<xsl:apply-templates select=
"self::*[not(*[name() = $pUncertainElName])
or
*[name()=$pUncertainElName and @missing-cause]]"
mode="missing"/>
</xsl:copy>
</xsl:variable>
<xsl:apply-templates select="ext:node-set($vrtfFirstPass)/*" mode="pass2"/>
</xsl:template>
<xsl:template match="*[@missing-cause]"/>
<xsl:template match="*" mode="missing">
<xsl:element name="{$pUncertainElName}">
<CharacterString>INSERTED BY TEMPLATE</CharacterString>
</xsl:element>
</xsl:template>
<xsl:template match="innerElement" mode="pass2">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort data-type="number" select=
"string-length(substring-before($pOrderedNames,
concat('|', name(), '|')
)
)"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<doc>
<outerElement>
<innerElement>
<first>
<textElement>Some Text</textElement>
</first>
<second missing-cause="bla" />
<third>
<textElement>Some Text</textElement>
</third>
</innerElement>
</outerElement>
</doc>
the wanted, correct result is produced:
<doc>
<outerElement>
<innerElement>
<first>
<textElement>Some Text</textElement>
</first>
<second>
<CharacterString>INSERTED BY TEMPLATE</CharacterString>
</second>
<third>
<textElement>Some Text</textElement>
</third>
</innerElement>
</outerElement>
</doc>
The transformation can be modified so that it processes the children of different elements at different locations of the document hierarchy:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pUncertainElName" select="'second'"/>
<xsl:param name="pOrderedNames" select="'|first|second|third|'"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vrtfFirstPass">
<xsl:apply-templates select="node()|@*"/>
</xsl:variable>
<xsl:apply-templates select="ext:node-set($vrtfFirstPass)/*" mode="pass2"/>
</xsl:template>
<xsl:template match="innerElement">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<xsl:apply-templates select=
"self::*[not(*[name() = $pUncertainElName])
or
*[name()=$pUncertainElName and @missing-cause]]"
mode="missing"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[@missing-cause]"/>
<xsl:template match="*" mode="missing">
<xsl:element name="{$pUncertainElName}">
<CharacterString>INSERTED BY TEMPLATE</CharacterString>
</xsl:element>
</xsl:template>
<xsl:template match="node()|@*" mode="pass2">
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="pass2"/>
</xsl:copy>
</xsl:template>
<xsl:template match="innerElement" mode="pass2">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort data-type="number" select=
"string-length(substring-before($pOrderedNames,
concat('|', name(), '|')
)
)"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (containing two innerElement
elements -- with different parents and at different depths -- whose children need the specialized processing):
<doc>
<outerElement>
<innerElement>
<first>
<textElement>Some Text</textElement>
</first>
<second missing-cause="bla" />
<third>
<textElement>Some Text</textElement>
</third>
</innerElement>
<outerElement2>
<outerElement3>
<innerElement>
<first>
<textElement>Some Text</textElement>
</first>
<third>
<textElement>Some Text</textElement>
</third>
</innerElement>
</outerElement3>
</outerElement2>
</outerElement>
</doc>
the wanted, correct result is produced:
<doc>
<outerElement>
<innerElement>
<first>
<textElement>Some Text</textElement>
</first>
<second>
<CharacterString>INSERTED BY TEMPLATE</CharacterString>
</second>
<third>
<textElement>Some Text</textElement>
</third>
</innerElement>
<outerElement2>
<outerElement3>
<innerElement>
<first>
<textElement>Some Text</textElement>
</first>
<second>
<CharacterString>INSERTED BY TEMPLATE</CharacterString>
</second>
<third>
<textElement>Some Text</textElement>
</third>
</innerElement>
</outerElement3>
</outerElement2>
</outerElement>
</doc>
Finally, we can modify the transformation even further, so that it can process the children of differently named parents -- say innerElement
and someOtherInnerElement
:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pUncertainElName" select="'second'"/>
<xsl:param name="pOrderedNames" select="'|first|second|third|'"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vrtfFirstPass">
<xsl:apply-templates select="node()|@*"/>
</xsl:variable>
<xsl:apply-templates select="ext:node-set($vrtfFirstPass)/*" mode="pass2"/>
</xsl:template>
<xsl:template match="innerElement | someOtherInnerElement">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<xsl:apply-templates select=
"self::*[not(*[name() = $pUncertainElName])
or
*[name()=$pUncertainElName and @missing-cause]]"
mode="missing"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[@missing-cause]"/>
<xsl:template match="*" mode="missing">
<xsl:element name="{$pUncertainElName}">
<CharacterString>INSERTED BY TEMPLATE</CharacterString>
</xsl:element>
</xsl:template>
<xsl:template match="node()|@*" mode="pass2">
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="pass2"/>
</xsl:copy>
</xsl:template>
<xsl:template match="innerElement | someOtherInnerElement" mode="pass2">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort data-type="number" select=
"string-length(substring-before($pOrderedNames,
concat('|', name(), '|')
)
)"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document, where the children to be processed in the required way have parents named with these two names ( innerElement
and someOtherInnerElement
):
<doc>
<outerElement>
<innerElement>
<first>
<textElement>Some Text</textElement>
</first>
<second missing-cause="bla" />
<third>
<textElement>Some Text</textElement>
</third>
</innerElement>
<outerElement2>
<outerElement3>
<someOtherInnerElement>
<first>
<textElement>Some Text</textElement>
</first>
<third>
<textElement>Some Text</textElement>
</third>
</someOtherInnerElement>
</outerElement3>
</outerElement2>
</outerElement>
</doc>
again the wanted, correct result is produced:
<doc>
<outerElement>
<innerElement>
<first>
<textElement>Some Text</textElement>
</first>
<second>
<CharacterString>INSERTED BY TEMPLATE</CharacterString>
</second>
<third>
<textElement>Some Text</textElement>
</third>
</innerElement>
<outerElement2>
<outerElement3>
<someOtherInnerElement>
<first>
<textElement>Some Text</textElement>
</first>
<second>
<CharacterString>INSERTED BY TEMPLATE</CharacterString>
</second>
<third>
<textElement>Some Text</textElement>
</third>
</someOtherInnerElement>
</outerElement3>
</outerElement2>
</outerElement>
</doc>
Explanation:
This is essentially the same logic as for the previous question:
Two-pass processing.
Overriding the identity rule.
Proper use of templates and template match-patterns.
Sorting elements by the preferred order of their names.
Upvotes: 1