Reputation: 437
I would like to replace an element in XML (large) with multiple elements as below:
Original XML:
<root>
<cr>
<id>1</id>
<release>A</release>
</cr>
<cr>
<id>2</id>
<release>B</release>
</cr>
</root>
I would like the output to be:
<root>
<cr>
<id>1</id>
<release>Aa</release>
<release>Ab</release>
<release>Ad</release>
</cr>
<cr>
<id>2</id>
<release>Bd</release>
<release>Be</release>
</cr>
</root>
The principle is, whenever there is //release[text()='A'], replace the element with three above, whenever there is //release[text()='B'], replace the element with two above etc. If the release text() is "C" or "D" or other values, they remain the same values.
My attempt:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//cr/release/text()">
<xsl:if test=".='A'">
<xsl:value-of select="Aa"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It works with one -> one replacement, but how to do multiple? Thanks a lot,
Upvotes: 1
Views: 1561
Reputation: 243479
Here is a completely generic and short XSLT 1.0 solution. It uses a mapping xml file that specifies the replacements for each wanted release:
(Please, find at the end of this answer the ultimately-generic and short solution written in XSLT 2.0.)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vMap" select="document('mapping.xml')/*/*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="release[. = document('mapping.xml')/*/release/@old]">
<xsl:copy-of select="$vMap[@old = current()]/*"/>
</xsl:template>
</xsl:stylesheet>
If the file mapping.xml
is in the same directory as the transformation (.xsl file), and is:
<map>
<release old="A">
<release>Aa</release>
<release>Ab</release>
<release>Ad</release>
</release>
<release old="B">
<release>Ba</release>
<release>Bb</release>
<release>Bd</release>
</release>
</map>
then when the transformation is applied on this XML document:
<root>
<cr>
<id>1</id>
<release>A</release>
</cr>
<cr>
<id>2</id>
<release>B</release>
</cr>
<cr>
<id>3</id>
<release>C</release>
</cr>
<cr>
<id>4</id>
<release>D</release>
</cr>
</root>
the wanted, correct result is produced:
<root>
<cr>
<id>1</id>
<release>Aa</release>
<release>Ab</release>
<release>Ad</release>
</cr>
<cr>
<id>2</id>
<release>Ba</release>
<release>Bb</release>
<release>Bd</release>
</cr>
<cr>
<id>3</id>
<release>C</release>
</cr>
<cr>
<id>4</id>
<release>D</release>
</cr>
</root>
Note: Many, differently named elements may be replaced with the same transformation.
If we have this mapping file:
<map>
<release old="A">
<release>Aa</release>
<release>Ab</release>
<release>Ad</release>
</release>
<release old="B">
<release>Ba</release>
<release>Bb</release>
<release>Bd</release>
</release>
<history old="p">
<history>Pp</history>
<history>Pq</history>
<history>Pr</history>
</history>
</map>
and this source XML document:
<root>
<cr>
<id>1</id>
<history>p</history>
<release>A</release>
</cr>
<cr>
<id>2</id>
<history>q</history>
<release>B</release>
</cr>
<cr>
<id>3</id>
<history>r</history>
<release>C</release>
</cr>
<cr>
<id>4</id>
<history>t</history>
<release>D</release>
</cr>
</root>
then this transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vMap" select="document('mapping.xml')/*/*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="release[. = document('mapping.xml')/*/release/@old]">
<xsl:copy-of select="$vMap[@old = current()]/*"/>
</xsl:template>
<xsl:template match="history[. = document('mapping.xml')/*/history/@old]">
<xsl:copy-of select="$vMap[@old = current()]/*"/>
</xsl:template>
</xsl:stylesheet>
when applied on the above XML document,produces the wanted result -- where more than one differently-named elements are mapped and replaced:
<root>
<cr>
<id>1</id>
<history>Pp</history>
<history>Pq</history>
<history>Pr</history>
<release>Aa</release>
<release>Ab</release>
<release>Ad</release>
</cr>
<cr>
<id>2</id>
<history>q</history>
<release>Ba</release>
<release>Bb</release>
<release>Bd</release>
</cr>
<cr>
<id>3</id>
<history>r</history>
<release>C</release>
</cr>
<cr>
<id>4</id>
<history>t</history>
<release>D</release>
</cr>
</root>
Finally: An even shorter and more generic transformation can be written in XSLT 2.0:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vMap" select="document('mapping.xml')/*/*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"*[. = $vMap[name() eq name(current())]/@old]">
<xsl:copy-of select="$vMap[name() eq name(current()) and @old eq current()]/*"/>
</xsl:template>
</xsl:stylesheet>
Do note: No element name is hardcoded in this transformation !
An even greater advantage: We can modify the transformation, so we can pass the URI of the mapping document as a global parameter on invoking the transformation -- thus we can have a single, generic transformation that works with any, unknown in advance mapping.
Only these are the required changes:
<xsl:param name="pmapUrl" select="'file:///c:/temp/mapping.xml'"/>
<xsl:variable name="vMap" select="document($pmapUrl)/*/*"/>
The complete transformation (the global parameter pmapUrl
can and typically will be specified dynamically by the invoker of the transformation):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pmapUrl" select="'file:///c:/temp/mapping.xml'"/>
<xsl:variable name="vMap" select="document($pmapUrl)/*/*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"*[for $name in name()
return
. = $vMap[name() eq $name]/@old]">
<xsl:copy-of select="$vMap[@old = current()]/*"/>
</xsl:template>
</xsl:stylesheet>
Upvotes: 2
Reputation: 117003
Try it this way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="release[.='A']">
<release>Aa</release>
<release>Ab</release>
<release>Ad</release>
</xsl:template>
<xsl:template match="release[.='B']">
<release>Bd</release>
<release>Be</release>
</xsl:template>
</xsl:stylesheet>
Test input
<root>
<cr>
<id>1</id>
<release>A</release>
</cr>
<cr>
<id>2</id>
<release>B</release>
</cr>
<cr>
<id>3</id>
<release>C</release>
</cr>
</root>
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<cr>
<id>1</id>
<release>Aa</release>
<release>Ab</release>
<release>Ad</release>
</cr>
<cr>
<id>2</id>
<release>Bd</release>
<release>Be</release>
</cr>
<cr>
<id>3</id>
<release>C</release>
</cr>
</root>
Upvotes: 2