Given the following xml :
<tree>
<val>0</val>
<tree>
<val>1</val>
<tree>
<val>3</val>
</tree>
<tree>
<val>4</val>
</tree>
</tree>
<tree>
<val>2</val>
<tree>
<val>5</val>
</tree>
<tree>
<val>6</val>
</tree>
</tree>
</tree>
I need to transform it into this xml:
<root>0
<frst>1
<leaf>3</leaf>
<leaf>4</leaf>
</frst>
<second>2
<leaf>5</leaf>
<leaf>6</leaf>
</second>
</root>
This is my attempt that gives the same result, I recently started learning XSLT i'm not sure what other option I have, can this be improved or done in another way ?
Thank you for your help
This is my attempt :
<?xml version='1.0' encoding='utf-8'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="/tree">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="val">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="tree">
<xsl:choose>
<!-- IF HAS CHILDREN -->
<xsl:when test="child::tree">
<xsl:if test="(count(preceding-sibling::tree)+1) = 1">
<frst>
<xsl:apply-templates/>
</frst>
</xsl:if>
<xsl:if test="(count(preceding-sibling::tree)+1) = 2">
<second>
<xsl:apply-templates/>
</second>
</xsl:if>
</xsl:when>
<!-- ELSE IS A LEAF -->
<xsl:otherwise>
<leaf>
<xsl:apply-templates/>
</leaf>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
test test
I would think the conditions can be written in match patterns:
<xsl:template match="tree[tree][1]">
<frst>
<xsl:apply-templates/>
</frst>
</xsl:template>
<xsl:template match="tree[tree][2]">
<second>
<xsl:apply-templates/>
</second>
</xsl:template>
<xsl:template match="tree[not(tree)]">
<leaf>
<xsl:apply-templates/>
</leaf>
</xsl:template>
although I guess your current code doesn't process the third/fourth/fifth.. tree children so an additional <xsl:template match="tree[tree][position() > 2]"/> might be necessary.
Related
I am try to sort the p element entry e.g. (1(a), 1.1, 1, 1(a)-(b)). How to sort the entry?
Input
<?xml version="1.0" encoding="UTF-8"?>
<root>
<sec>
<title>Title 1</title>
<p><bold>10(a)</bold></p>
<p><bold>10</bold></p>
<p><bold>10(b)</bold></p>
<p><bold>10(d)</bold></p>
<p><bold>10(c)-(d)</bold></p>
<p><bold>1</bold></p>
<sec>
<title>Title 1(a)</title>
<p><bold>14(1)</bold></p>
<p><bold>14</bold></p>
<p><bold>14(2)</bold></p>
<p><bold>14(4)</bold></p>
<p><bold>14(3)-(4)</bold></p>
<p><bold>10.1</bold></p>
<p><bold>10</bold></p>
<p><bold>10.8</bold></p>
</sec>
</sec>
</root>
Expected Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<sec>
<title>Title 1</title>
<p><bold>1</bold></p>
<p><bold>10</bold></p>
<p><bold>10(a)</bold></p>
<p><bold>10(b)</bold></p>
<p><bold>10(c)-(d)</bold></p>
<p><bold>10(d)</bold></p>
<sec>
<title>Title 1(a)</title>
<p><bold>10</bold></p>
<p><bold>10.1</bold></p>
<p><bold>10.8</bold></p>
<p><bold>14</bold></p>
<p><bold>14(1)</bold></p>
<p><bold>14(2)</bold></p>
<p><bold>14(3)-(4)</bold></p>
<p><bold>14(4)</bold></p>
</sec>
</sec>
</root>
XSLT
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="sec">
<xsl:copy>
<xsl:copy-of select="title"/>
<xsl:for-each select="p">
<xsl:sort select="bold" data-type="number" order="ascending"/>
<xsl:apply-templates/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
Perhaps the following XSLT 3 captures your intent:
<xsl:template match="sec">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent=". instance of element(p)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="sort(current-group(), 'http://www.w3.org/2013/collation/UCA?numeric=yes')"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
Assumes the identity transformation is in place as the base template e.g. in XSLT 3 declared as <xsl:mode on-no-match="shallow-copy"/>.
I'm new to XSLT and i'm trying to apply a template to a certain node which is reached via nested for-each functions.
I have tried to simplify it and moved the apply-templates in only 1 for-each and there i got it to work.
This is my sample input xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<getPartMasterDataCompleteResponse >
<result>
<partMasterDataCompletes>
<partMasterDataComplete>
<part>A0001506450</part>
<version>11</version>
<partMasterData>
<language>german</language>
<part>A1234567890</part>
<releaseDateFrom>2018-09-06T08:52:18+02:00</releaseDateFrom>
<testTag>
<test></test>
<test></test>
<test></test>
</testTag>
</partMasterData>
</partMasterDataComplete>
</partMasterDataCompletes>
</result>
</getPartMasterDataCompleteResponse>
This is the xslt i'm using:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="getPartMasterDataCompleteResponse/result/partMasterDataCompletes">
<xsl:for-each select="partMasterDataComplete">
<Part>
<UserData>
<xsl:for-each select="partMasterData/*">
<xsl:if test="not(*)">
<xsl:choose>
<xsl:when test="local-name() = 'part'">
<xsl:apply-templates select="part"/>
</xsl:when>
<xsl:otherwise>
<UserValue>
<xsl:attribute name="title">
<xsl:value-of select="local-name()"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="current()"/>
</xsl:attribute>
</UserValue>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each>
</UserData>
</Part>
</xsl:for-each>
</xsl:template>
<xsl:template match="part">
<UserValue title="PartNumber">
<xsl:attribute name="value">
<xsl:value-of select="current()"/>
</xsl:attribute>
</UserValue>
</xsl:template>
</xsl:stylesheet>
The result i get from Online-transformation tools is:
<Part>
<UserData>
<UserValue title="language" value="german"/>
<UserValue title="releaseDateFrom" value="2018-09-06T08:52:18+02:00"/>
</UserData>
</Part>
What i would expect is:
<Part>
<UserData>
<UserValue title="language" value="german"/>
<UserValue title="PartNumber" value="A0001506450"/>
<UserValue title="releaseDateFrom" value="2018-09-06T08:52:18+02:00"/>
</UserData>
</Part>
Any help is appreciated.
Thanks
When you do apply-templates, you are already positioned on a part element, so by doing <xsl:apply-templates select="part"/> you are actually looking for a child element called part on the current element. (i.e. it is actually the same as doing <xsl:apply-templates select="child::part"/>)
You want to apply the template to the current element, so do this instead:
<xsl:apply-templates select="."/>
Or, if you really wanted to make it explicit...
<xsl:apply-templates select="self::part"/>
As an aside, you can simplify your XSLT greatly, by better use of xsl:apply-templates and by using Attribute Value Templates to create attributes.
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="getPartMasterDataCompleteResponse/result/partMasterDataCompletes">
<xsl:for-each select="partMasterDataComplete">
<Part>
<UserData>
<xsl:apply-templates select="partMasterData/*[not(*)]" />
</UserData>
</Part>
</xsl:for-each>
</xsl:template>
<xsl:template match="partMasterData/*">
<UserValue title="{local-name()}" value="{current()}" />
</xsl:template>
<xsl:template match="part">
<UserValue title="PartNumber" name="{current()}" />
</xsl:template>
</xsl:stylesheet>
I am trying to find the last element in my input xml, which looks like
<ROOT>
<Items>
<Item>
<FieldTag>AMOUNT</FieldTag>
</Item>
<Item>
<FieldTag>CAT_TYPE</FieldTag>
</Item>
<Item>
<FieldTag>NUMBER</FieldTag>
</Item>
</Items>
<getResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MAIN>
<ArrayItem>
<NUMBER>123456789</NUMBER>
<CTRY>GB</CTRY>
<NAME>TEST NAME</NAME>
<RS>
<ArrayOfRSItem>
<DATE_1>2014-12-12T10:14:02-05:00</DATE_1>
<DATE_2>2014-12-12T10:13:53-05:00</DATE_2>
<AMOUNT>11111</AMOUNT>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-13T17:16:19-05:00</DATE_1>
<DATE_2>2014-12-13T16:33:07-05:00</DATE_2>
<AMOUNT>22222</AMOUNT>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-12T10:14:02-05:00</DATE_1>
<DATE_2>2014-12-12T10:13:53-05:00</DATE_2>
<CAT_TYPE>10000</CAT_TYPE>
</ArrayOfRSItem>
<ArrayOfRSItem>
<DATE_1>2014-12-13T17:16:19-05:00</DATE_1>
<DATE_2>2014-12-13T16:33:07-05:00</DATE_2>
<CAT_TYPE>20000</CAT_TYPE>
</ArrayOfRSItem>
</RS>
</ArrayItem>
</MAIN>
</getResponse>
</ROOT>
and I use following XSLT file for transformation
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:urn="urn:iso:std:iso:20022:tech:xsd:pain.001.001.02"
xmlns:func="http://www.test.com/"
exclude-result-prefixes="urn func" xmlns:saxon="http://saxon.sf.net/"
extension-element-prefixes="saxon">
<xsl:output method ="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:if test="count(/ROOT/getResponse/MAIN/ArrayItem) > 0" >
<Data>
<xsl:for-each select="/ROOT/getResponse/MAIN/ArrayItem" >
<xsl:element name="NodeItem">
<xsl:variable name="TempNo" select="NUMBER"/>
<xsl:element name="Number">
<xsl:value-of select="NUMBER"/>
</xsl:element>
<xsl:element name="Country">
<xsl:value-of select="CTRY"/>
</xsl:element>
<xsl:element name="Name">
<xsl:value-of select="NAME"/>
</xsl:element>
<xsl:element name="DataChanges">
<xsl:for-each select="./RS/ArrayOfRSItem/*" >
<xsl:if test="name()!='DATE_1' and name()!='DATE_2' ">
<xsl:variable name="ChangedNodeName" select="name()"></xsl:variable>
<xsl:for-each select="/ROOT/Items/Item">
<xsl:variable name="DisplayNodeName" select="FieldTag"></xsl:variable>
<xsl:if test="$DisplayNodeName = $ChangedNodeName">
<xsl:element name="FieldItem">
<xsl:attribute name="Tag">
<xsl:value-of select="FieldTag"/>
</xsl:attribute>
<xsl:variable name="NodeFullText" select="concat('../../getResponse/MAIN/ArrayItem[NUMBER=',$TempNo,']/RS/ArrayOfRSItem/',$DisplayNodeName)"/>
<xsl:attribute name="Value">
<xsl:value-of select="saxon:evaluate($NodeFullText)"/>
</xsl:attribute>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:for-each>
</Data>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
to get the desired output in the form of
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="22222"/>
<FieldItem Tag="CAT_TYPE" Value="20000"/>
</DataChanges>
</NodeItem>
</Data>
But I am getting the output xml as
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="11111 22222"/>
<FieldItem Tag="AMOUNT" Value="11111 22222"/>
<FieldItem Tag="CAT_TYPE" Value="10000 20000"/>
<FieldItem Tag="CAT_TYPE" Value="10000 20000"/>
</DataChanges>
</NodeItem>
</Data>
I need to get the latest AMOUNT entry and CAT_TYPE. Any help or guidance would be much appreciated. Thanks in advance.
Couldn't this be (much) simpler?
XSLT 1.0 (or 2.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:template match="/">
<Data>
<xsl:apply-templates select="ROOT/getResponse/MAIN/ArrayItem"/>
</Data>
</xsl:template>
<xsl:template match="ArrayItem">
<NodeItem>
<Number><xsl:value-of select="NUMBER"/></Number>
<Country><xsl:value-of select="CTRY"/></Country>
<Name><xsl:value-of select="NAME"/></Name>
<DataChanges>
<xsl:apply-templates select="RS/ArrayOfRSItem/AMOUNT">
<xsl:sort select="../DATE_1" data-type="text" order="descending"/>
</xsl:apply-templates>
<xsl:apply-templates select="RS/ArrayOfRSItem/CAT_TYPE">
<xsl:sort select="../DATE_1" data-type="text" order="descending"/>
</xsl:apply-templates>
</DataChanges>
</NodeItem>
</xsl:template>
<xsl:template match="AMOUNT | CAT_TYPE">
<xsl:if test="position() = 1">
<FieldItem Tag="{local-name()}" Value="{.}"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Result, when applied to your example input:
<?xml version="1.0" encoding="utf-8"?>
<Data>
<NodeItem>
<Number>123456789</Number>
<Country>GB</Country>
<Name>TEST NAME</Name>
<DataChanges>
<FieldItem Tag="AMOUNT" Value="22222"/>
<FieldItem Tag="CAT_TYPE" Value="20000"/>
</DataChanges>
</NodeItem>
</Data>
Input:
<persons>
<person name="John" role="Writer"/>
<person name="John" role="Poet"/>
<person name="Jacob" role="Writer"/>
<person name="Jacob" role="Poet"/>
<person name="Joe" role="Poet"/>
</persons>
Expected Output:
<groups>
<group roles="Wriet, Poet" persons="John, Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
As in the above example, I first need to group on person names and find everyone's roles. If more than one person is found to have the same set of roles (e.g. both John and Jacob are both Writer and Poet), then I need to group on each set of roles and list the person names.
I can do this for the first level of grouping using Muenchian method or EXSLT set:distinct etc.
<groups>
<group roles="Wriet, Poet" persons="John"/>
<group roles="Wriet, Poet" persons="Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
The above was transformed using XSLT 1.0 and EXSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sets="http://exslt.org/sets" extension-element-prefixes="sets">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="persons-by-name" match="person" use="#name"/>
<xsl:template match="persons">
<groups>
<xsl:for-each select="sets:distinct(person/#name)">
<group>
<xsl:attribute name="persons"><xsl:value-of select="."/></xsl:attribute>
<xsl:attribute name="roles">
<xsl:for-each select="key('persons-by-name', .)">
<xsl:value-of select="#role"/>
<xsl:if test="position()!=last()"><xsl:text>, </xsl:text></xsl:if>
</xsl:for-each>
</xsl:attribute>
</group>
</xsl:for-each>
</groups>
</xsl:template>
</xsl:stylesheet>
However, I need help to understand how to group on the grouped roles.
If XSLT 1.0 solution is not available, please feel free to recommend XSLT 2.0 approach.
Try it this way?
XSLT 1.0
(using EXSLT node-set() and distinct() functions)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:set="http://exslt.org/sets"
extension-element-prefixes="exsl set">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:key name="person-by-name" match="person" use="#name" />
<xsl:key name="person-by-roles" match="person" use="#roles" />
<xsl:variable name="distinct-persons">
<xsl:for-each select="set:distinct(/persons/person/#name)">
<person name="{.}">
<xsl:attribute name="roles">
<xsl:for-each select="key('person-by-name', .)/#role">
<xsl:sort/>
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</person>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<groups>
<xsl:for-each select="set:distinct(exsl:node-set($distinct-persons)/person/#roles)">
<group roles="{.}">
<xsl:attribute name="names">
<xsl:for-each select="key('person-by-roles', .)/#name">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</group>
</xsl:for-each>
</groups>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group roles="Poet, Writer" names="John, Jacob"/>
<group roles="Poet" names="Joe"/>
</groups>
I did exactly the same thing as you already did and then went a step further and grouped again. Now I get the following output with your input:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group roles="Writer,Poet" persons="John,Jacob"/>
<group roles="Poet" persons="Joe"/>
</groups>
This is the XSLT 2.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns="" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:avintis="http://www.avintis.com/esb" exclude-result-prefixes="#all" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/persons">
<groups>
<xsl:variable name="persons" select="."/>
<!-- create a temporary variable containing all roles of a person -->
<xsl:variable name="roles">
<xsl:for-each select="distinct-values(person/#name)">
<xsl:sort select="."/>
<xsl:variable name="name" select="."/>
<xsl:element name="group">
<xsl:attribute name="roles">
<!-- sort the roles of each person -->
<xsl:variable name="rolesSorted">
<xsl:for-each select="$persons/person[#name=$name]">
<xsl:sort select="#role"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="string-join($rolesSorted/person/#role,',')"/>
</xsl:attribute>
<xsl:attribute name="persons" select="."/>
</xsl:element>
</xsl:for-each>
</xsl:variable>
<!-- now loop again over all roles of the persons and group persons having the same roles -->
<xsl:for-each select="distinct-values($roles/group/#roles)">
<xsl:element name="group">
<xsl:variable name="name" select="."/>
<xsl:attribute name="roles" select="$name"/>
<xsl:attribute name="persons">
<xsl:value-of select="string-join($roles/group[#roles=$name]/#persons,',')"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</groups>
</xsl:template>
<xsl:template match="*|text()|#*">
<xsl:copy>
<xsl:apply-templates select="*|text()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The roles get also sorted - so independent from the input order of the roles and persons.
I have a xml file main.xml with following markup and data.
main.xml
<xml>
<content>
<para>
This is a para.
</para>
<sub para>
This is para.
</sub para>
</content>
</xml>
I have another xml file keyword.xml with list of keywords that we need to find any where in above xml and replace the keyword value.
keyword.xml
<xml>
<keywordList>
<keyword>
<value>para</value>
<replace> paragraph </replace>
</keyword>
<keyword>
<value>is</value>
<replace>IS</replace>
</keyword>
</xml>
Can we do it in xslt so that the output should be
output
<xml>
<content>
<para>
This IS a paragraph.
</para>
<sub para>
This IS paragraph.
</sub para>
</content>
</xml>
Try the following
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="keywords" select="document('keyword.xml')"/>
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:analyze-string select="." regex="[A-Za-z]+">
<xsl:matching-substring>
<xsl:variable name="repl" select="$keywords//keyword[value = current()]"/>
<xsl:choose>
<xsl:when test="$repl">
<xsl:value-of select="$repl/replace"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="current()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="current()"/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
Note that the replace value for para includes spaces around the new word, hence the additional spaces:
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<content>
<para>
This IS a paragraph .
</para>
<subpara>
This IS paragraph .
</subpara>
</content>
</xml>
This is an XSLT 1.0 solution (of course, can be used with XSLT 2.0, too):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:params xml:space="preserve">
<pattern>
<old>para</old>
<new> paragraph </new>
</pattern>
<pattern>
<old> is </old>
<new> IS </new>
</pattern>
</my:params>
<xsl:variable name="vrtfPats">
<xsl:for-each select="document('')/*/my:params/*">
<xsl:sort select="string-length(old)"
data-type="number" order="descending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vPats" select=
"ext:node-set($vrtfPats)/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" name="multiReplace" priority="2">
<xsl:param name="pText" select="."/>
<xsl:param name="pPatterns" select="$vPats"/>
<xsl:if test= "string-length($pText) >0">
<xsl:variable name="vPat" select=
"$vPats[starts-with($pText, old)][1]"/>
<xsl:choose>
<xsl:when test="not($vPat)">
<xsl:copy-of select="substring($pText,1,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$vPat/new/node()"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="multiReplace">
<xsl:with-param name="pText" select=
"substring($pText,
1 + not($vPat) + string-length($vPat/old/node())
)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document (corrected to be well-formed):
<xml>
<content>
<para>
This is a para.
</para>
<sub_para>
This is para.
</sub_para>
</content>
</xml>
the wanted, correct result is produced:
<xml>
<content>
<para>
This IS a paragraph .
</para>
<sub_para>
This IS paragraph .
</sub_para>
</content>
</xml>
Explanation: The text is scanned character by character and the longest possible target string starting at that position in the text is replaced with its specified replacement.