Unnest parent nodes by child node - xslt

In a XML there are items with 0-n attributes and an item should be copied for each attribute as a new item but with only one attribute.
Given is a XML like this:
<?xml version="1.0" encoding="utf-8" ?>
<items>
<item>
<name>A</name>
<attributes>
<attribute>
<key>attribute1</key>
<value>1</value>
</attribute>
</attributes>
</item>
<item>
<name>B</name>
</item>
<item>
<name>C</name>
<attributes>
<attribute>
<key>attribute1</key>
<value>5</value>
</attribute>
<attribute>
<key>attribute2</key>
<value>2</value>
</attribute>
<attribute>
<key>attribute3</key>
<value>1</value>
</attribute>
</attributes>
</item>
</items>
Result should be:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<item>
<name>A</name>
<attribute_key>attribute1</attribute_key>
<attribute_value>1</attribute_value>
</item>
<item>
<name>B</name>
</item>
<item>
<name>C</name>
<attribute_key>attribute1</attribute_key>
<attribute_value>5</attribute_value>
</item>
<item>
<name>C</name>
<attribute_key>attribute2</attribute_key>
<attribute_value>2</attribute_value>
</item>
<item>
<name>C</name>
<attribute_key>attribute3</attribute_key>
<attribute_value>1</attribute_value>
</item>
</root>
What I have s far:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:if test="not(attributes/attribute)">
<item>
<xsl:apply-templates select="#* | node()"/>
</item>
</xsl:if>
<xsl:for-each select="./attributes/attribute">
<xsl:copy-of select="ancestor::item"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
So, the parent "item" node gets copied correctly but how do I remove all attributes but the attribute from the for-each and place that attribute as a direct child of "item"?

Instead of copying the item node in its entirity, manually create a new item and copy only its children (apart from attributes)
<xsl:for-each select="attributes/attribute">
<item>
<xsl:copy-of select="ancestor::item/*[not(self::attributes)]"/>
<!-- Process attributes here -->
</item>
</xsl:for-each>
Processing the attributes is then just a matter of processing the children, and using xsl:element to create new ones
<xsl:for-each select="*">
<xsl:element name="attribute_{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:if test="not(attributes/attribute)">
<item>
<xsl:apply-templates select="#* | node()"/>
</item>
</xsl:if>
<xsl:for-each select="attributes/attribute">
<item>
<xsl:copy-of select="ancestor::item/*[not(self::attributes)]"/>
<xsl:for-each select="*">
<xsl:element name="attribute_{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</item>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Or if you wanted to take a more template-based approach, this should also work
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[attributes/attribute]">
<xsl:apply-templates select="attributes/attribute" />
</xsl:template>
<xsl:template match="item">
<item>
<xsl:apply-templates select="#* | node()"/>
</item>
</xsl:template>
<xsl:template match="attribute">
<item>
<xsl:copy-of select="ancestor::item/*[not(self::attributes)]"/>
<xsl:apply-templates select="*" />
</item>
</xsl:template>
<xsl:template match="attribute/*">
<xsl:element name="attribute_{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Have a look at following script that will explain you want are you looking for:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:template match="/">
<root>
<xsl:apply-templates select="items/item" />
</root>
</xsl:template>
<xsl:template match="item">
<xsl:if test="not(attributes/attribute)">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:if>
<xsl:apply-templates select="attributes/attribute">
<!-- sending value of name tag to a template matching attribute tag -->
<xsl:with-param name="name" select="name"></xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<!--Another template for attribute tag that will help individually tracking of attribute -->
<xsl:template match="attribute">
<!-- Taking value of name tag -->
<xsl:param name="name"></xsl:param>
<item>
<name>
<xsl:value-of select="$name" />
</name>
<attribute_key>
<xsl:value-of select="key" />
</attribute_key>
<attribute_value>
<xsl:value-of select="value" />
</attribute_value>
</item>
</xsl:template>

Related

Remove duplicates based on condition

I am trying to remove duplicates from my xml based on a condition in XSLT1.0
Here is the input xml.
<?xml version="1.0" encoding="UTF-8"?>
<Envelope
xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Header>
<MessageId>{D5B72T7A-58E0-4930-9CEB-A06RT56AR21B0}</MessageId>
<Action>http://tempuri.org/TRH_FinalQueryService/find</Action>
</Header>
<Body>
<MessageParts
xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<TRH_FinalQuery
xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/TRH_FinalQuery">
<TRH_UnionView class="entity">
<Company>1</Company>
<CS/>
<Text_1>1</Text_1>
<Text_2>Lotion</Text_2>
<WS/>
</TRH_UnionView>
<TRH_UnionView class="entity">
<Company>1</Company>
<CS>1</CS>
<Text_1>1</Text_1>
<Text_2>Soap</Text_2>
<WS>6</WS>
</TRH_UnionView>
<TRH_UnionView class="entity">
<Company>2</Company>
<CS/>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<WS/>
</TRH_UnionView>
<TRH_UnionView class="entity">
<Company>2</Company>
<CS/>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<WS/>
</TRH_UnionView>
</TRH_FinalQuery>
</MessageParts>
</Body>
</Envelope>
Here is the xslt that I have applied.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message" xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/TRH_FinalQuery" exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="r:TRH_FinalQuery" match="r:TRH_FinalQuery" use="concat(r:Text_1, '|', r:Company)" />
<!-- move all elements to no namespace -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="r:TRH_FinalQuery[r:TRH_UnionView[#class='entity']/r:WessexCostCenter=''][key('r:TRH_FinalQuery',concat(r:Text_1, '|', r:Company))[1]]"/>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<!-- removes Envelope -->
<xsl:template match="m:Envelope">
<xsl:apply-templates />
</xsl:template>
<!-- removes Header,MessageId,Action and Body -->
<xsl:template match="m:*">
<xsl:apply-templates select="*" />
</xsl:template>
<!-- rename MessageParts to Document + skip the Run wrapper -->
<xsl:template match="m:MessageParts">
<DocumentElement>
<xsl:apply-templates select="r:TRH_FinalQuery/*" />
</DocumentElement>
</xsl:template>
<!-- rename RunObject to Item -->
<xsl:template match="r:TRH_UnionView[#class='entity']">
<xsl:choose>
<xsl:when test="r:WS!=''">
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>
<xsl:value-of select="r:Text_1" />
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
</xsl:when>
<xsl:otherwise>
<Item>
<xsl:apply-templates select="r:Text_1" />
<xsl:apply-templates select="r:Text_2" />
<xsl:apply-templates select="r:Company" />
</Item>
<Item>
<xsl:apply-templates select="r:Text_1" />
<xsl:apply-templates select="r:Text_2" />
<Company>0123</Company>
</Item>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Below is the output I am getting
<?xml version="1.0" encoding="utf-8"?>
<DocumentElement>
<Item>
<Text_1>1</Text_1>
<Text_2>Lotion</Text_2>
<Company>1</Company>
</Item>
<Item>
<Text_1>1</Text_1>
<Text_2>Lotion</Text_2>
<Company>0123</Company>
</Item>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>1</Company>
</Item>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>2</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>0123</Company>
</Item>
</DocumentElement>
Below is the expected output
<?xml version="1.0" encoding="utf-8"?>
<DocumentElement>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>1</Company>
</Item>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>2</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>0123</Company>
</Item>
</DocumentElement>
I am trying to remove all duplicates based on condition
If the Text_1 and Company are same.
If the point 1 is true then retain all records having value in WS tag and remove records where there no value in WS tag.
Can you please suggest what I am doing wrong
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/TRH_FinalQuery"
exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="myKey" match="r:TRH_UnionView" use="concat(r:Text_1, '|', r:Company)" />
<!-- Simplify things by not having an identity. Using this approach, you will not have to suppress
any elements.
-->
<xsl:template match="node()">
<xsl:apply-templates select="node()"/>
</xsl:template>
<!-- Start at the root. -->
<xsl:template match="/">
<DocumentElement>
<xsl:apply-templates select="node()" />
</DocumentElement>
</xsl:template>
<xsl:template match="r:TRH_UnionView">
<xsl:choose>
<!-- Handle the duplicates with no value in the WS tag. -->
<xsl:when test="count(key('myKey',concat(r:Text_1, '|', r:Company))) > 1 and
count((key('myKey',concat(r:Text_1, '|', r:Company)))[r:WS!='']) = 0">
<!-- Is this the first of the duplicates? -->
<xsl:if test="generate-id(.) = generate-id(key('myKey',concat(r:Text_1, '|', r:Company))[1])">
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>
<xsl:value-of select="r:Company"/>
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>0123</Company>
</Item>
</xsl:if>
</xsl:when>
<!-- Handle the duplicates with value at least one value in the WS tag. -->
<xsl:when test="count(key('myKey',concat(r:Text_1, '|', r:Company))) > 1">
<xsl:if test="r:WS!=''">
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>
<xsl:value-of select="r:Text_1" />
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>
<xsl:value-of select="r:Company"/>
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>0123</Company>
</Item>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Find nodes with the same couple of attributes with xslt and add a new node

I need to analyse the following XML input:
<LIST>
<PRODUCT TYPE="1" REP="0">
<SUB CAT="1.1" COUNT="2">
<ITEM NAME="OCC" BEGIN="0" ND="49">
</ITEM>
<ITEM NAME="OCC" BEGIN="0" END="49">
</ITEM>
</SUB>
</PRODUCT>
<PRODUCT TYPE="1" REP="1">
<SUB CAT="1.1" COUNT="1">
<ITEM NAME="PRC" BEGIN="0" END="49">
</ITEM>
</SUB>
</PRODUCT>
</LIST>
and transform it with Xslt to obtain the following result:
<LIST>
<PRODUCT TYPE="1" REP="0">
<SUB CAT="1.1" COUNT="2">
<MULTIPLE />
<ITEM NAME="OCC" BEGIN="0" END="49">
</ITEM>
<MULTIPLE />
<ITEM NAME="OCC" BEGIN="0" END="49">
</ITEM>
</SUB>
</PRODUCT>
<PRODUCT TYPE="1" REP="1">
<SUB CAT="1.1" COUNT="1">
<MULTIPLE />
<ITEM NAME="PRC" BEGIN="0" END="49">
</ITEM>
</SUB>
</PRODUCT>
</LIST>
What I need to do is to check that the BEGIN and END of the ITEMS in two different PRODUCT node are the same, and if this is the case add the MULTIPLE node as a flag.
Any idea on how to proceed?
This is how I thought it could work:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//PRODUCT[#TYPE='1']/SUB[#CAT='1.1']/ITEM">
<xsl:if test="//PRODUCT[#TYPE='1']/SUB[#CAT='1.1']/ITEM /RULE (#BEGIN <= current()/#BEGIN) and (#END >= current()/#END)]">
<xsl:element name="MULTIPLE">
</xsl:element>
</xsl:if>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This can be achieved by means of a key to look up ITEM elements
<xsl:key name="item" match="ITEM" use="concat(#BEGIN, '|', #END)" />
Then, you just need a template that matches ITEM elements where there is at least 2 items in the key
<xsl:template match="ITEM[key('item', concat(#BEGIN, '|', #END))[2]]">
Using this in conjunction with the XSLT identity transform, gives you this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item" match="ITEM" use="concat(#BEGIN, '|', #END)" />
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM[key('item', concat(#BEGIN, '|', #END))[2]]">
<MULTIPLE />
<xsl:call-template name="identity" />
</xsl:template>
</xsl:stylesheet>
If you wish to restrict it to look for matches in the same product and sub-category, change the key to this...
<xsl:key name="item" match="ITEM" use="concat(../../#TYPE, '|', ../#CAT, '|', #BEGIN, '|', #END)" />
And adjust the template match accordingly....
<xsl:template match="ITEM[key('item', concat(../../#TYPE, '|', ../#CAT, '|', #BEGIN, '|', #END))[2]]">
You can try like this way by match the ITEM context:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM">
<xsl:if test="(
(#BEGIN = ancestor::PRODUCT/following-sibling::PRODUCT/descendant::ITEM/#BEGIN) and
(#END = ancestor::PRODUCT/following-sibling::PRODUCT/descendant::ITEM/#END))
or (
(#BEGIN = ancestor::PRODUCT/preceding-sibling::PRODUCT/descendant::ITEM/#BEGIN) and
(#END = ancestor::PRODUCT/preceding-sibling::PRODUCT/descendant::ITEM/#END)
)
">
<xsl:element name="MULTIPLE"/>
</xsl:if>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

copy a descendant node and place it after the current node

In the below xml. I want to do following
match wrapper/item whose child or descendant is <olr>.
If found, copy <olr> and all its following-siblings, and paste it
after the current node i.e. end of wrapper/item. (other word,<olr> and its following-siblings will always be direct child of <wrapper>)
rest should be copied as it is.
Input XML:
<root>
<wrapper>
<item>
<item>1.1</item>
<item>
<olr>outlier1</olr>
</item>
</item>
<item>
<item>2.1</item>
<item>
<item>
<item>
<item>preceedingsibling1</item>
<item>preceedingsibling2</item>
<olr>outlier2</olr>
<item>followingsibling1</item>
<item>followingsibling2</item>
</item>
</item>
</item>
</item>
<item>
<item>3.1</item>
<item>
<item>
<item>3.3.1</item>
</item>
</item>
</item>
</wrapper>
<root>
<wrapper>
<item>
<item>1.1</item>
<item>
</item>
</item>
<olr>outlier1</olr>
<item>
<item>2.1</item>
<item>
<item>
<item>
<item>preceedingsibling1</item>
<item>preceedingsibling2</item>
</item>
</item>
</item>
</item>
<olr>outlier2</olr>
<item>followingsibling1</item>
<item>followingsibling2</item>
<item>
<item>3.1</item>
<item>
<item>
<item>3.3.1</item>
</item>
</item>
</item>
</wrapper>
I am trying something:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[descendant::olr]">
<xsl:apply-templates select="node() except descendant::olr"/>
<!-- ? not sure what to do here -->
</xsl:template>
The below XSLT-2.0 solution would do this:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<!-- identity transform template with mode='olr' -->
<xsl:template match="#* | node()" mode="olr">
<xsl:copy>
<xsl:apply-templates select="#*, node()[1]" mode="olr"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]" mode="olr"/>
</xsl:template>
<!-- identity transform template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!-- copy the olr and it's following-elements in the current element -->
<xsl:template match="wrapper/item[descendant::olr]">
<xsl:copy>
<xsl:apply-templates select="#*, node()[1]" />
</xsl:copy>
<xsl:apply-templates select="descendant::olr[not(preceding-sibling::olr)]" mode="olr"/>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!-- "do nothing for olr" template -->
<xsl:template match="olr[ancestor::item/parent::wrapper]"/>
</xsl:stylesheet>
There are two identity transform templates(1st and 2nd) to copy the elements as-is, recursively.
The third template matches the wrapper/item with descendant::olr and specially processes olr(and its following-siblings) by copying it as its own following-sibling.
The fourth template is to do nothing for olr in the normal process.
One way to achieve this is with
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="wrapper/item[descendant::olr]">
<xsl:next-match/>
<xsl:copy-of select="descendant::olr/(., following-sibling::node())"/>
</xsl:template>
<xsl:template match="wrapper/item//olr | wrapper/item//node()[preceding-sibling::olr]"/>
</xsl:transform>
http://xsltransform.net/bFWR5Fg. I am not sure what happens if you have several olr elements.

Transform to nest items within one list when source contains flat tagging

I have the following XML file:
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525" href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526" href="#s9781452281988.n39.i3312">Interpersonal conflict</a></li>
And I want the output to be the following:
<item>See also
<list rend="runon">
<item><term>Emotion</term></item>
<item><term>Interpersonal conflict</term></item>
</list>
</item>
Basically if I have multiple a[#class='term-ref'], the first instance should start the list rend="runon" and subsequent a[#class='term-ref'] should be included as item/term within the list.
The below was my try, but it is not working as I had hoped, and is closing the list before the second item/term (elements which are also not being output):
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.0">
<xsl:template match="li">
<xsl:element name="item">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="a[#class='term-ref'][1]">
<xsl:element name="list">
<xsl:attribute name="rend" select="'runon'"/>
<xsl:element name="item">
<xsl:element name="term">
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
<xsl:if test="a[#class='term-ref'][position() >1]">
<xsl:element name="item">
<xsl:element name="term">
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="translate(., '.,;', '')"/>
</xsl:template>
</xsl:stylesheet>
On the source, XML, the above stylesheet produces this output:
<item>See also
<list rend="runon">
<item><term>Emotion</term></item>
</list>
Interpersonal conflict</item>
Which is incorrect.
What am i doing wrong?
This short transformation (almost completely "push style", with no conditional instructions, no xsl:element and no unnecessary function calls like translate() or replace()):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="li">
<item><xsl:apply-templates/></item>
</xsl:template>
<xsl:template match="a[#class='term-ref'][1]">
<list rend="runon">
<xsl:apply-templates mode="group"
select="../a[#class='term-ref']"/>
</list>
</xsl:template>
<xsl:template match="a[#class='term-ref']" mode="group">
<item><term><xsl:apply-templates/></term></item>
</xsl:template>
<xsl:template match="a[#class='term-ref']|li/text()" priority="-1"/>
</xsl:stylesheet>
when applied on the provided XML document -- which is well-formed:
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525"
href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526"
href="#s9781452281988.n39.i3312">Interpersonal conflict.</a>.
</li>
produces the wanted, correct result:
<item>See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict.</term>
</item>
</list>
</item>
This should work...
XML Input (well-formed)
<doc>
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525" href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526" href="#s9781452281988.n39.i3312">Interpersonal conflict.</a>.
</li>
</doc>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="li">
<item>
<xsl:apply-templates select="i/text()"/>
<xsl:if test="a">
<list rend="runon">
<xsl:apply-templates select="a"/>
</list>
</xsl:if>
</item>
</xsl:template>
<xsl:template match="a">
<item><term><xsl:apply-templates select="node()"/></term></item>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="replace(.,'[.,;]','')"/>
</xsl:template>
</xsl:stylesheet>
Output
<doc>
<item>See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict</term>
</item>
</list>
</item>
</doc>
This should do what you are looking to do:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="li">
<xsl:element name="item">
<xsl:apply-templates select="node()" />
<xsl:apply-templates select="." mode="items" />
</xsl:element>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="normalize-space(translate(., '.,;', ''))"/>
</xsl:template>
<xsl:template match="a[#class = 'term-ref']" />
<xsl:template match="node()" mode="items" />
<xsl:template match="li" mode="items">
<xsl:apply-templates mode="items" />
</xsl:template>
<xsl:template match="li[count(a[#class = 'term-ref']) > 1]" mode="items">
<list rend="runon">
<xsl:apply-templates select="a[#class = 'term-ref']" mode="items" />
</list>
</xsl:template>
<xsl:template match="a[#class = 'term-ref']" mode="items">
<item>
<term>
<xsl:value-of select="."/>
</term>
</item>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, this produces:
<item>
See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict</term>
</item>
</list>
</item>
When run on an input file with just one a.term-ref, this produces:
<item>
See also<item>
<term>Interpersonal conflict</term>
</item>
</item>

xslt pattern match transformation

How can I transform the following XML with XSLT from this:
<root>
<list>
<item label="21(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
to this:
<root>
<list label="21">
<item label="(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
So, if there is a number before a parenthesis on the label attribute of the first item, that number needs to be aded as the value of the label attribute for the parent list item.
The pattern to match the attribute would be something like:
/(\d+)\([^\)]+\)/
As mentioned by Nikolaus you can use the substring-before and substring-after XPath functions. A sample XSL transformation would look like this:
<?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" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="list">
<list>
<xsl:variable name="prefix" select="substring-before(./item/#label, '(')" />
<xsl:if test="$prefix != '' and number($prefix)">
<xsl:attribute name="label">
<xsl:value-of select="substring-before(./item/#label, '(')"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates />
</list>
</xsl:template>
<xsl:template match="item">
<item>
<xsl:attribute name="label">
<xsl:variable name="prefix" select="substring-before(#label, '(')" />
<xsl:choose>
<xsl:when test="$prefix != '' and number($prefix)">
<xsl:value-of select="concat('(', substring-after(#label, '('))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#label"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates />
</item>
</xsl:template>
</xsl:stylesheet>
you can use the xslt function substring-before to get the substring befor '('
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[1][boolean(number(substring-before(#label,'(')))]">
<xsl:attribute name="label">
<xsl:value-of select="substring-before(#label,'(')"/>
</xsl:attribute>
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="item[1]/#label[boolean(number(substring-before(.,'(')))]">
<xsl:attribute name="label">
<xsl:value-of select="concat('(',substring-after(.,'('))"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<list label="21">
<item label="(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
Edit: Compact predicate.
Edit 2: Test number before parentesis. Explicity strip white space only nodes.
This XSLT 1.0 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:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"list[number(substring-before(item[1]/#label, '('))
=
number(substring-before(item[1]/#label, '('))
]">
<list label="{substring-before(item[1]/#label, '(')}">
<xsl:apply-templates select="node()|#*"/>
</list>
</xsl:template>
<xsl:template match=
"item[1]/#label[number(substring-before(., '('))
=
number(substring-before(., '('))
]">
<xsl:attribute name="label">
<xsl:value-of select="concat('(',substring-after(.,'('))"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<list>
<item label="21(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
produces the wanted, correct result:
<root>
<list label="21">
<item label="(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>