XSLT Grouping and merging nodes - xslt

Is it possible to group nodes and merge with a specified key? Some of my xml files have information about one person in two child nodes and I would like to find if nodes are about the same person and then marge it into one node
Input xml looks like this:
<db>
<next>
<name>John</name>
<surname>Smith</surname>
<number>304888</number>
<details>
<city>Westfield</city>
<zip-code>07090</zip-code>
<address>23 Victoria Street</address>
<phone>123456789</phone>
<fax/>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>304888</number>
<details>
<city>Westfield</city>
<zip-code>07090</zip-code>
<address>23 Victoria Street</address>
<phone>223344123</phone>
<fax>993456789</fax>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>113190</number>
<details>
<city>Richmond</city>
<zip-code>3121</zip-code>
<address>18 Seasame Street</address>
<phone>123456222</phone>
<fax/>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>113190</number>
<details>
<city>Richmond</city>
<zip-code>3133</zip-code>
<address>23 Baker Street</address>
<phone>113344123</phone>
<fax>133456789</fax>
</details>
</next>
</db>
'number' is an ID of the person and there can be more people with the same name and surename but with different 'number' value. Also, there can be the same person more than once (with the same value of 'name', 'surename' and 'number') but with different details. If the combination of 'city', 'zip-code' and 'address' are the same but 'phone' or/and 'fax' are differend then I would like to marge it.
And I want it to look like this:
<db>
<next>
<name>John</name>
<surname>Smith</surname>
<number>304888</number>
<details>
<city>Westfield</city>
<zip-code>07090</zip-code>
<address>23 Victoria Street</address>
<phone>123456789</phone>
<phone>223344123</phone>
<fax>993456789</fax>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>113190</number>
<details>
<city>Richmond</city>
<zip-code>3121</zip-code>
<address>18 Seasame Street</address>
<phone>123456222</phone>
<fax/>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>113190</number>
<details>
<city>Richmond</city>
<zip-code>3133</zip-code>
<address>23 Baker Street</address>
<phone>113344123</phone>
<fax>133456789</fax>
</details>
</next>
</db>
I have tried muenchian grouping but I don't know how to do it with phone and fax nodes.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="index" match="next" use="number" />
<xsl:key name="details-key" match="details" use="city,'_',zip-code,'_',address" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="db">
<xsl:copy>
<xsl:for-each select="//next[generate-id() = generate-id(key('index', number)[1])]">
<xsl:element name="name">
<xsl:value-of select="name"/>
</xsl:element>
<xsl:element name="surname">
<xsl:value-of select="surname"/>
</xsl:element>
<xsl:element name="number">
<xsl:value-of select="number"/>
</xsl:element>
<next>
<xsl:for-each select="//details[generate-id() = generate-id(key('details-key', concat(city,'_',zip-code,'_',address))[1])]">
<xsl:element name="city">
<xsl:value-of select="city"/>
</xsl:element>
<xsl:element name="zip-code">
<xsl:value-of select="zip-code"/>
</xsl:element>
<xsl:element name="address">
<xsl:value-of select="address"/>
</xsl:element>
</xsl:for-each>
</next>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

First thing, your stylesheet will produce an error because the expression you used in:
<xsl:key name="details-key" match="details" use="city,'_',zip-code,'_',address" />
is not a valid expression in XPath 1.0.
If you want to group the details elements by the combined values of city, zip-code and address, you should define your key as:
<xsl:key name="details-key" match="details" use="concat(city, '_', zip-code, '_', address)" />
Now, if I go by your expected output instead of by your description, you actually want to create a group for each unique combination of number and address. So something along the lines of:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="k1" match="next" use="concat(number, '_', details/city,'_', details/zip-code,'_', details/address)" />
<xsl:template match="db">
<xsl:copy>
<xsl:for-each select="next[generate-id() = generate-id(key('k1', concat(number, '_', details/city,'_', details/zip-code,'_', details/address))[1])]">
<next>
<xsl:copy-of select="name | surname | number"/>
<details>
<xsl:copy-of select="city | zip-code | address"/>
<xsl:variable name="group-details" select="key('k1', concat(number, '_', details/city,'_', details/zip-code,'_', details/address))/details" />
<xsl:copy-of select="$group-details/phone[text()] | $group-details/fax[text()]"/>
</details>
</next>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

xsl to move node under precibling parent based on matching of parent attribute

I'm trying to handle a xml converted from a pdf to another xml file in some format. First I want to move / group some text / node together based on the geometry of the text but failed to do so. The following is my input & what I wanted:
input xml:
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5.</Text>
</Box>
<Box llx="81.84" lly="560.64" urx="194.39" ury="571.68">
<Text>Equipment list</Text>
</Box>
<Box llx="257.40" lly="560.64" urx="265.36" ury="571.68">
<Text>C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
<Page>
same structure as above...
</Page>
</Pages>
Output xml:
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5. Equipment list C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
<Page>
same structure as above...
</Page>
</Pages>
What i have:
<xsl:template match="#*|node()" name = "identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Box">
<xsl:choose>
<xsl:when test="#ury = following-sibling::Box/#ury">
<xsl:call-template name="identity"/>
<xsl:apply-templates select ="#*"/>
<xsl:copy-of select="following-sibling::Box/Text"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
1.It doesn't copy the wanted nodes 2. i don't know how to exclude the following nodes. I hope someone can help me on this. Many thanks in advance.
I tried the following to exclude the duplicates but it doesn't copy what i want anyways:
<xsl:template match="Box[#ury != preceding-sibling::Box/#ury]/Text">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
This is a case of muenchian grouping in which you need to group the nodes based on certain common criteria and process them to provide an output.
Based on the version of XSLT being used, the solution differs for XSLT 1.0 and XSLT 2.0
XSLT 1.0
Version 1.0 uses a <xsl:key> to group the elements based on common criteria. In this case, the grouping is being done based on the value of attribute #ury so we define a key
<xsl:key name="groupingKey" match="Box" use="#ury" />
Using this key, the templates are grouped together for processing.
<xsl:template match="Box[generate-id() = generate-id(key('groupingKey', #ury)[1])]">
Finally within the grouped elements, a loop is run over the <Text> elements to concatenate its values.
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="key('groupingKey', #ury)/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
Below is the complete XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="groupingKey" match="Box" use="#ury" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Box[generate-id() = generate-id(key('groupingKey', #ury)[1])]">
<xsl:copy>
<xsl:apply-templates select="#*" />
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="key('groupingKey', #ury)/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
</xsl:copy>
</xsl:template>
<xsl:template match="Box" />
</xsl:stylesheet>
XSLT 2.0
Version 2.0 is advanced and provides a simpler approach as compared to XSLT 1.0. The <xsl:for-each-group> and group-by feature can be used to group the elements together.
<xsl:for-each-group select="Box" group-by="#ury">
Below is the complete XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Page">
<xsl:copy>
<xsl:apply-templates select="PAGENUMBER" />
<xsl:for-each-group select="Box" group-by="#ury">
<xsl:copy>
<xsl:apply-templates select="#*" />
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="current-group()/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Both the XSLT provide the required output
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5. Equipment list C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
</Pages>

Grouping of grouped 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.

XSLT transform a document with group element

I have a document like this:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<data attribute1="12" attribute2="1" attribute3="1">Director</data>
<data attribute1="12" attribute2="1" attribute3="5">James</data>
<data attribute1="12" attribute2="1" attribute3="8">Male</data>
<data attribute1="12" attribute2="1" attribute3="9">10-Dec-1965</data>
<data attribute1="12" attribute2="2" attribute3="18">James#gmail.com</data>
<data attribute1="12" attribute2="2" attribute3="1">Chief Account</data>
<data attribute1="12" attribute2="2" attribute3="5">Anna</data>
<data attribute1="12" attribute2="2" attribute3="8">Female</data>
<data attribute1="12" attribute2="1" attribute3="9">5-Aug-1980</data>
<data attribute1="12" attribute2="2" attribute3="18">Anna#gmail.com</data>
</document>
I want to transform it to this:
<Person>
<Title>Director</Title>
<FullName>James</FullName>
<Gender>Male</Gender>
<DateOfBirth>10-Dec-1965</DateOfBirth>
<EmailAddress>James#gmail.com</EmailAddress>
</Person>
<Person>
<Title>Chief Account</Title>
<FullName>Anna</FullName>
<Gender>Female</Gender>
<DateOfBirth>5-Aug-1980</DateOfBirth>
<EmailAddress>Anna#gmail.com</EmailAddress>
</Person>
I am using this xslt:
<xsl:for-each select="document/data[#attribute1=12]">
<Person>
<xsl:choose>
<xsl:when test="boolean(./#attribute3 = '1')">
<Title>
<xsl:value-of select="./."/>
</Title>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '5')">
<FullName>
<xsl:value-of select="./."/>
</FullName>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '8')">
<Gender>
<xsl:value-of select="./."/>
</Gender>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '9')">
<DateOfBirth>
<xsl:value-of select="./."/>
</DateOfBirth>
</xsl:when>
<xsl:when test="boolean(./#attribute3 = '18')">
<EmailAddress>
<xsl:value-of select="./."/>
</EmailAddress>
</xsl:when>
</xsl:choose>
</Person>
</xsl:for-each>
The problem is that I get the following output, where the <Person> tag is duplicated.
<Person>
<Title>Director</Title>
</Person>
<Person>
<FullName>James</FullName>
</Person>
<Person>
<Gender>Male</Gender>
</Person>
<Person>
<DateOfBirth>10-Dec-1965</DateOfBirth>
</Person>
<Person>
<EmailAddress>James#gmail.com</EmailAddress>
</Person>
<Person>
<Title>Chief Account</Title>
</Person>
<Person>
<FullName>Anna</FullName>
</Person>
<Person>
<Gender>Female</Gender>
</Person>
<Person>
<DateOfBirth>5-Aug-1980</DateOfBirth>
</Person>
<Person>
<EmailAddress>Anna#gmail.com</EmailAddress>
</Person>
Could anyone help me solving this problem? Thank you!
It looks like you wish to create a new Person element for each data element with an attribute3 value of 1. Rather than iterate over all data elements as you are currently doing, just select the elements with the relevant attribute
<xsl:apply-templates select="data[#attribute3='1']"/>
Then, you would have a template to output the Person element only for these data elements.
<xsl:template match="data[#attribute3='1']">
<Person>
<Title><xsl:value-of select="." /></Title>
<!-- Select other elements here -->
</Person>
</xsl:template>
Now, to get the other elements, another way to achieve this is to make use of a key. Effectively, you will group the data elements by the first most preceding data element with an attribute3 value of "1".
<xsl:key name="data"
match="data[#attribute3 != '1']"
use="generate-id(preceding-sibling::data[#attribute3 = '1'][1])" />
Then, to select the other elements, you can use this key, using the unique ID of the current data element as the lookup.
<xsl:apply-templates select="key('data', generate-id())" />
This would only select the data elements that make up that particular person element. You would then have template to match the other data elements for each possible attribute value:
Try this XSLT :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="data" match="data[#attribute3 != '1']" use="generate-id(preceding-sibling::data[#attribute3 = '1'][1])" />
<xsl:template match="document">
<xsl:apply-templates select="data[#attribute3='1']"/>
</xsl:template>
<xsl:template match="data[#attribute3='1']">
<Person>
<Title><xsl:value-of select="." /></Title>
<xsl:apply-templates select="key('data', generate-id())" />
</Person>
</xsl:template>
<xsl:template match="data[#attribute3='5']">
<FullName><xsl:value-of select="." /></FullName>
</xsl:template>
<xsl:template match="data[#attribute3='8']">
<Gender><xsl:value-of select="." /></Gender>
</xsl:template>
<xsl:template match="data[#attribute3='9']">
<DateOfBirth><xsl:value-of select="." /></DateOfBirth>
</xsl:template>
<xsl:template match="data[#attribute3='18']">
<EmailAddress><xsl:value-of select="." /></EmailAddress>
</xsl:template>
</xsl:stylesheet>
Here is my suggestion based on the position of elements:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="size" select="5"/>
<xsl:template match="document">
<Persons>
<xsl:apply-templates select="data[position() mod $size = 1]" mode="group"/>
</Persons>
</xsl:template>
<xsl:template match="data" mode="group">
<Person>
<xsl:apply-templates select=". | following-sibling::data[position() < $size]"/>
</Person>
</xsl:template>
<xsl:template match="data[#attribute3 = '1']">
<Title>
<xsl:value-of select="."/>
</Title>
</xsl:template>
<xsl:template match="data[#attribute3 = '5']">
<FullName>
<xsl:value-of select="."/>
</FullName>
</xsl:template>
<xsl:template match="data[#attribute3 = '8']">
<Gender>
<xsl:value-of select="."/>
</Gender>
</xsl:template>
<xsl:template match="data[#attribute3 = '9']">
<DateOfBirth>
<xsl:value-of select="."/>
</DateOfBirth>
</xsl:template>
<xsl:template match="data[#attribute3 = '18']">
<EmailAddress>
<xsl:value-of select="."/>
</EmailAddress>
</xsl:template>
</xsl:stylesheet>

search keyword and replace the text in xml file based on external xml file

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.

Multiple File input in XSLT transformation

I want to update one xml values with other xml.
Suppose i have a xml having root node
<Customer>
<Fname>John</Fname>
<Lname>Smith<Lname>
</Customer>
The other xml is having
<Customer>
<Lname>Smith<Lname>
</Customer>
I want to transfer <Fname>John</Fname> from 1st to 2nd xml if that information is not present in 2nd xml.
Is it possible by using xslt in .net?
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kElementByAncestors" match="*"
use="concat(name(../..),'+',name(..))"/>
<xsl:key name="kAttributeByAncestors" match="#*"
use="concat(name(../..),'+',name(..))"/>
<xsl:param name="pSource2" select="'source2.xml'"/>
<xsl:variable name="vSource2" select="document($pSource2,/)"/>
<xsl:template match="*">
<xsl:variable name="vKey" select="concat(name(..),'+',name())"/>
<xsl:variable name="vCurrent" select="."/>
<xsl:copy>
<xsl:for-each select="$vSource2">
<xsl:variable name="vNames">
<xsl:text>|</xsl:text>
<xsl:for-each select="$vCurrent/*">
<xsl:value-of select="concat(name(),'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="key('kAttributeByAncestors',$vKey)"/>
<xsl:copy-of select="$vCurrent/#*"/>
<xsl:copy-of
select="key('kElementByAncestors',
$vKey)[not(contains($vNames,
concat('|',
name(),
'|')))]"/>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With this input:
<Customer>
<Lname>Smith</Lname>
<Data>Data</Data>
</Customer>
And "source2.xml":
<Customer test="test">
<Fname>John</Fname>
<Lname>Smith</Lname>
</Customer>
Output:
<Customer test="test">
<Fname>John</Fname>
<Lname>Smith</Lname>
<Data>Data</Data>
</Customer>