Remove duplicates based on condition - xslt

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>

Related

How to have the same XSLT namespace with different URIs?

I am building an RSS parser that takes in the media namespace's items.
Example 1:
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<title>Some channel</title>
<item>
<guid>234wwerwe</guid>
<title>Some title</title>
<media:description>test description 1</media:description>
<pubDate>Tue, 30 Jul 2019 19:24:00 +0000</pubDate>
</item>
</channel>
</rss>
Example 2:
<rss version="2.0" xmlns:media="http://www.rssboard.org/media-rss">
<channel>
<title>Some second channel</title>
<item>
<guid>234wwsdflkjl23we</guid>
<title>Some other title</title>
<media:description>test description 2</media:description>
<pubDate>Tue, 30 Jul 2019 19:24:00 +0000</pubDate>
</item>
</channel>
</rss>
I would like to convert this using the same xsl file but if I do something like the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:boardMedia="http://www.rssboard.org/media-rss">
<xsl:template match="/">
<xsl:for-each select="rss/channel/item">
<item>
<xsl:element name="referenceId">
<xsl:value-of select="guid" />
</xsl:element>
<xsl:element name="title">
<xsl:value-of select="title" />
</xsl:element>
<xsl:element name="description">
<xsl:value-of select="media:description" />
<xsl:value-of select="boardMedia:description" />
</xsl:element>
<xsl:element name="itemPublishDate"><xsl:value-of select="pubDate" /></xsl:element>
</item>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
then it only successfully pulls the description for the first rss, not the second.
Any suggestions on how to do this in one xsl file?
As I mentioned in a comment, the problem cannot be reproduced using your code.
Note that you could simplify your code significantly:
XSLT 2.0
<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:template match="/rss">
<xsl:for-each select="channel/item">
<item>
<referenceId>
<xsl:value-of select="guid" />
</referenceId>
<title>
<xsl:value-of select="title" />
</title>
<description>
<xsl:value-of select="*:description" />
</description>
<itemPublishDate>
<xsl:value-of select="pubDate" />
</itemPublishDate>
</item>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/bFN1yan/1
If you really want to merge these two description elements with your XSLT, you can try the following stylesheet (naming your second XML file example_2.xml):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"
xmlns:boardMedia="http://www.rssboard.org/media-rss">
<xsl:variable name="file2" select="document('example_2.xml')" /> <!-- Added to include the second file -->
<xsl:template match="/">
<xsl:for-each select="rss/channel/item">
<item>
<xsl:element name="referenceId">
<xsl:value-of select="guid" />
</xsl:element>
<xsl:element name="title">
<xsl:value-of select="title" />
</xsl:element>
<xsl:element name="description">
<xsl:value-of select="media:description" />
<xsl:value-of select="$file2/rss/channel/item/boardMedia:description" />
</xsl:element>
<xsl:element name="itemPublishDate"><xsl:value-of select="pubDate" /></xsl:element>
</item>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Its result is rather odd:
<?xml version="1.0" encoding="UTF-8"?>
<item xmlns:media="http://search.yahoo.com/mrss/" xmlns:boardMedia="http://www.rssboard.org/media-rss">
<referenceId>234wwerwe</referenceId>
<title>Some title</title>
<description>test description 1test description 2</description>
<itemPublishDate>Tue, 30 Jul 2019 19:24:00 +0000</itemPublishDate>
</item>
But that seems to be the result you like.

XSLT key only returns a value once

I think I'm missing something obvious here but here goes. I have the below xml and I need to group the KEY nodes of the matched instances together. This is specified by the match attribute and it can contain more than one item number. There can be any number of ITEM nodes and any number of KEY nodes. Also, there is no limit to the depth of the ITEM nodes. And, the matched instances need not be under the same parent. I'm also limited to XSLT 1.0 and the Microsoft parser.
<?xml version="1.0" encoding="utf-8" ?>
<ITEM number='1'>
<ITEM number='2'>
<ITEM number='3' match='5,11'>
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
<ITEM number ='4' />
</ITEM>
<ITEM number='5' match='3,11'>
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
</ITEM>
<ITEM number='6' match='10'>
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key4' value='a' />
</ITEM>
<ITEM number='7' />
<ITEM number='8'>
<KEY name='key1' value='x' />
</ITEM>
</ITEM>
<ITEM number='9'>
<ITEM number='10' match='6'>
<KEY name='key1' value='x' />
<KEY name='key3' value='z' />
<KEY name='key5' value='b' />
</ITEM>
</ITEM>
<ITEM number='11' match='3,5'>
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
</ITEM>
</ITEM>
My expected result would look something like this...
<?xml version="1.0" encoding="utf-8" ?>
<Result>
<Group number="1" />
<Group number="2" />
<Group number="3,5,11">
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
</Group>
<Group number="4" />
<Group number="6,10">
<KEY name='key1' value='x' />
<KEY name='key2' value='y' />
<KEY name='key3' value='z' />
<KEY name='key4' value='a' />
<KEY name='key5' value='b' />
</Group>
<Group number="7" />
<Group number="8">
<KEY name='key1' value='x' />
</Group>
<Group number="9" />
</Result>
What I actually get is...
<?xml version="1.0" encoding="utf-8"?>
<Result>
<Group number="1" />
<Group number="2" />
<Group number="3,5,11">
<KEY name="key1" value="x" />
<KEY name="key2" value="y" />
<KEY name="key3" value="z" />
</Group>
<Group number="4" />
<Group number="6,10">
<KEY name="key4" value="a" />
<KEY name="key5" value="b" />
</Group>
<Group number="7" />
<Group number="8" />
<Group number="9" />
</Result>
I'm using a key and it looks like once I access that particular value from the key function, I cannot access it again. Group number 6,10 should contain all 5 keys but is missing the first 3 which are already present in group number 3,5. Similarly for group number 8, it should contain 1 key. I've used recursion to skip over the matched instances but I don't think there is any issue over there, it seems to be related to the key functionality. I've attached my xslt below, please take a look and tell me what I'm doing wrong. Any tips for performance improvements are also appreciated :)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kKeyByName" match="KEY" use="#name" />
<xsl:template name="ProcessItem">
<!--pItemsList - node set containing items that need to be processed-->
<xsl:param name="pItemsList" />
<!--pProcessedList - string containing processed item numbers in the format |1|2|3|-->
<xsl:param name="pProcessedList" />
<xsl:variable name="vCurrItem" select="$pItemsList[1]" />
<!--Recursion exit condition - check if we have a valid Item-->
<xsl:if test="$vCurrItem">
<xsl:variable name="vNum" select="$vCurrItem/#number" />
<!--Skip processed instances-->
<xsl:if test="not(contains($pProcessedList, concat('|', $vNum, '|')))">
<xsl:element name="Group">
<!--If the item is matched with another item, only the distinct keys of the 2 should be displayed-->
<xsl:choose>
<xsl:when test="$vCurrItem/#match">
<xsl:attribute name="number">
<xsl:value-of select="concat($vNum, ',', $vCurrItem/#match)" />
</xsl:attribute>
<xsl:for-each select="(//ITEM[#number=$vNum or #match=$vNum]/KEY)[generate-id(.)=generate-id(key('kKeyByName', #name)[1])]">
<xsl:apply-templates select="." />
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="number">
<xsl:value-of select="$vNum" />
</xsl:attribute>
<xsl:apply-templates select="KEY" />
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:if>
<!--Append processed instances to list to pass on in recursive function-->
<xsl:variable name="vNewList">
<xsl:value-of select="$pProcessedList" />
<xsl:value-of select="concat($vNum, '|')" />
<xsl:if test="$vCurrItem/#match">
<xsl:value-of select="concat($vCurrItem/#match, '|')" />
</xsl:if>
</xsl:variable>
<!--Call template recursively to process the rest of the instances-->
<xsl:call-template name="ProcessItem">
<xsl:with-param name="pItemsList" select="$pItemsList[position() > 1]" />
<xsl:with-param name="pProcessedList" select="$vNewList" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="KEY">
<xsl:copy>
<xsl:copy-of select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:element name="Result">
<xsl:call-template name="ProcessItem">
<xsl:with-param name="pItemsList" select="//ITEM" />
<xsl:with-param name="pProcessedList" select="'|'" />
</xsl:call-template>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
IF there is only one match or none to each item you can give the following xslt a try:
<?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:strip-space elements="*"/>
<xsl:key name="kItemNr" match="ITEM" use="#number" />
<xsl:key name="kNumberKey" match="KEY" use="concat(../#number, '|', #name )" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM">
<xsl:if test="not(preceding::ITEM[#number = current()/#match])" >
<Group>
<xsl:attribute name="number">
<xsl:value-of select="#number"/>
<xsl:if test="#match" >
<xsl:text>,</xsl:text>
<xsl:value-of select="#match"/>
</xsl:if>
</xsl:attribute>
<xsl:variable name="itemNr" select="#number"/>
<xsl:apply-templates select="KEY | key('kItemNr',#match )/KEY[
not (key('kNumberKey', concat($itemNr, '|', #name) ) )] ">
<xsl:sort select="#name"/>
</xsl:apply-templates>
</Group>
</xsl:if>
</xsl:template>
<xsl:template match="/" >
<Result>
<xsl:for-each select="//ITEM[count(. | key('kItemNr',number ) ) = 1 ]" >
<xsl:apply-templates select="." />
</xsl:for-each>
</Result>
</xsl:template>
</xsl:stylesheet>
Which will generate the following output:
<?xml version="1.0"?>
<Result>
<Group number="1"/>
<Group number="2"/>
<Group number="3,5">
<KEY name="key1" value="x"/>
<KEY name="key2" value="y"/>
<KEY name="key3" value="z"/>
</Group>
<Group number="4"/>
<Group number="6,10">
<KEY name="key1" value="x"/>
<KEY name="key2" value="y"/>
<KEY name="key3" value="z"/>
<KEY name="key4" value="a"/>
<KEY name="key5" value="b"/>
</Group>
<Group number="7"/>
<Group number="8">
<KEY name="key1" value="x"/>
</Group>
<Group number="9"/>
</Result>
Update because of changed request:
<?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:key name="kItemNr" match="ITEM" use="#number" />
<xsl:template match="#*|node()">
<xsl:copy >
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM">
<xsl:variable name="matchStr" select=" concat(',', current()/#match, ',')"/>
<xsl:if test="not(preceding::ITEM[ contains($matchStr, concat(',', #number, ',') )])" >
<Group>
<xsl:attribute name="number">
<xsl:value-of select="#number"/>
<xsl:if test="#match" >
<xsl:text>,</xsl:text>
<xsl:value-of select="#match"/>
</xsl:if>
</xsl:attribute>
<xsl:apply-templates select="(KEY |
//ITEM[
contains( $matchStr, concat(',', #number, ',') )
]/KEY[
not((preceding::ITEM[
contains( $matchStr, concat(',', #number, ',') )
] | current() )/KEY/#name = #name)
]) ">
<xsl:sort select="#name"/>
</xsl:apply-templates>
</Group>
</xsl:if>
</xsl:template>
<xsl:template match="/" >
<Result>
<xsl:for-each select="//ITEM[count(. | key('kItemNr',number ) ) = 1 ]" >
<xsl:apply-templates select="." />
</xsl:for-each>
</Result>
</xsl:template>
</xsl:stylesheet>
This may be quite slow for bigger input data but any way.

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>

Flat XML into tree with XSLT. Show one branch only

I have flat xml structure, i need to convert into hierarchy. With the help of stackoverflow I was able to do it.
Question: Is it possible to show only one branch using the same flat structure?
Here is my xml and xsl files:
XML
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="Stack.xsl"?>
<Items>
<Item>
<Id>1</Id>
<ParentId>0</ParentId>
<Name>1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>2</Id>
<ParentId>1</ParentId>
<Name>1.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>3</Id>
<ParentId>1</ParentId>
<Name>1.2</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>4</Id>
<ParentId>1</ParentId>
<Name>1.3</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>5</Id>
<ParentId>1</ParentId>
<Name>1.4</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>6</Id>
<ParentId>0</ParentId>
<Name>2</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>7</Id>
<ParentId>6</ParentId>
<Name>2.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>8</Id>
<ParentId>6</ParentId>
<Name>2.2</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>9</Id>
<ParentId>0</ParentId>
<Name>3</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>10</Id>
<ParentId>3</ParentId>
<Name>1.2.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>11</Id>
<ParentId>8</ParentId>
<Name>2.2.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>11</Id>
<ParentId>5</ParentId>
<Name>1.4.1</Name>
<SortOrder>0</SortOrder>
</Item>
</Items>
XSL
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:param name="SelectedId" select="'10'"/>
<xsl:key name="ChildNodes" match="/Items/Item" use="ParentId"/>
<xsl:template match="Items">
<ul>
<xsl:apply-templates select="Item[ParentId = 0]" />
</ul>
</xsl:template>
<xsl:template match="Item">
<li>
<xsl:choose>
<xsl:when test="Id = $SelectedId">
<b><xsl:value-of select="Name" /></b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="Name" />
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="Descendants" select="key ('ChildNodes', Id)" />
<xsl:if test="count ($Descendants) > 0">
<ul>
<xsl:apply-templates select="$Descendants" />
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
Current output I have:
1
1.1
1.2
1.2.1
1.3
1.4
1.4.1
2
2.1
2.2
2.2.1
3
Desireable result example:
1
1.1
1.2
1.2.1
1.3
1.4
2
3
One way to do this is to make use of node-set function, which will require the use of an extension namespace in XSLT.
What you could do is that instead of outputing the Descendants variable directly as currently:
<ul>
<xsl:apply-templates select="$Descendants"/>
</ul>
You instead store the results in a variable
<xsl:variable name="list">
<ul>
<xsl:apply-templates select="$Descendants"/>
</ul>
</xsl:variable>
Then you can convert this 'result tree fragment' into a node-set, which you can then check for whether the selected element (held in a b element) exists. If so, you can then output it
<xsl:if test="exsl:node-set($list)//li[b]">
<xsl:copy-of select="$list"/>
</xsl:if>
Here is the full XSLT
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="exsl">
<xsl:output method="html"/>
<xsl:param name="SelectedId" select="'10'"/>
<xsl:key name="ChildNodes" match="/Items/Item" use="ParentId"/>
<xsl:template match="Items">
<ul>
<xsl:apply-templates select="Item[ParentId = 0]"/>
</ul>
</xsl:template>
<xsl:template match="Item">
<li>
<xsl:choose>
<xsl:when test="Id = $SelectedId">
<b>
<xsl:value-of select="Name"/>
</b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="Name"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="Descendants" select="key ('ChildNodes', Id)"/>
<xsl:if test="count ($Descendants) > 0">
<xsl:variable name="list">
<ul>
<xsl:apply-templates select="$Descendants"/>
</ul>
</xsl:variable>
<xsl:if test="exsl:node-set($list)//li[b]">
<xsl:copy-of select="$list"/>
</xsl:if>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<ul>
<li>1
<ul>
<li>1.1</li>
<li>1.2
<ul>
<li>
<b>1.2.1</b>
</li>
</ul></li>
<li>1.3</li>
<li>1.4</li>
</ul></li>
<li>2</li>
<li>3</li>
</ul>
Note, because I am using Microsoft XML here, the extension namespace is "urn:schemas-microsoft-com:xslt". For other processors, you will probably have to use "http://exslt.org/common"

XSLT 1.0: apply-templates and template mode

I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Order>
<Item>
<RECORD_ID>RECORD_ID</RECORD_ID>
<ENTITY_CODE>ENTITY_CODE</ENTITY_CODE>
<USER_CODE>USER_CODE</USER_CODE>
<RECORD_DATE>RECORD_DATE</RECORD_DATE>
<ITEM_CODE>ITEM_CODE</ITEM_CODE>
<LINE_QUANTITY>LINE_QUANTITY</LINE_QUANTITY>
<LINE_FREE_STOCK>LINE_FREE STOCK</LINE_FREE_STOCK>
<LINE_PRICE>LINE_PRICE</LINE_PRICE>
<LINE_DISCOUNT_PERCENT>LINE_DISCOUNT PERCENT</LINE_DISCOUNT_PERCENT>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008165</ITEM_CODE>
<LINE_QUANTITY>2</LINE_QUANTITY>
<LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008161</ITEM_CODE>
<LINE_QUANTITY>1</LINE_QUANTITY>
<LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008225</ITEM_CODE>
<LINE_QUANTITY>5</LINE_QUANTITY>
</Item>
</Order>
Sometimes within the item tag I have the element <LINE_FREE_STOCK>. If that occurs I have to create an additional position in the output XML.
Now I came up with this style sheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" method="xml" indent="yes"/>
<xsl:template match="/">
<ORDERS05>
<IDOC BEGIN="1">
<xsl:apply-templates select="Order"/>
</IDOC>
</ORDERS05>
</xsl:template>
<xsl:template match="Order">
<Header>
<xsl:value-of select="'some header data'"/>
</Header>
<xsl:apply-templates select="Item[position() >1]"/>
<xsl:apply-templates select="Item[position() >1 and child::LINE_FREE_STOCK]" mode="freestock"/>
</xsl:template>
<xsl:template match="Item">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<quantity><xsl:value-of select="LINE_QUANTITY"/></quantity>
</item>
</position>
</xsl:template>
<xsl:template match="Item[position() >1 and child::LINE_FREE_STOCK]" mode="freestock">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<freestock_quant><xsl:value-of select="LINE_FREE_STOCK"/></freestock_quant>
</item>
</position>
</xsl:template>
</xsl:stylesheet>
It creates this (simplified) wanted output:
<?xml version="1.0" encoding="UTF-8"?>
<ORDERS05>
<IDOC BEGIN="1">
<Header>some header data</Header>
<position>
<item>
<number>804-008165</number>
<quantity>2</quantity>
</item>
</position>
<position>
<item>
<number>804-008161</number>
<quantity>1</quantity>
</item>
</position>
<position>
<item>
<number>804-008225</number>
<quantity>5</quantity>
</item>
</position>
<position>
<item>
<number>804-008165</number>
<freestock_quant>1</freestock_quant>
</item>
</position>
<position>
<item>
<number>804-008161</number>
<freestock_quant>1</freestock_quant>
</item>
</position>
</IDOC>
</ORDERS05>
804-008165 and 804-008161 show up twice - once as a standard item and once as the free stock item with the respective quantities.
But did I forget anything here? Is there some sort of pitfall I don't see?
Is that XSLT robust enough?
As others have noted, the problem is in this code:
<xsl:apply-templates select="Item"/>
<xsl:apply-templates select="Item[child::LINE_FREE_STOCK]" mode="freestock"/>
If there is a child Item that has a child LINE_FREE_STOCK, templates would be applied on this Item element twice -- here is how you get the repetitions in the output.
The transformation can be significantly shortened and it doesn't need modes or explicit conditional instructions at all:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<ORDERS05>
<IDOC BEGIN="1">
<xsl:apply-templates select="Order"/>
</IDOC>
</ORDERS05>
</xsl:template>
<xsl:template match="Order">
<Header>
<xsl:value-of select="'some header data'"/>
</Header>
<xsl:apply-templates select="Item[position() >1]"/>
</xsl:template>
<xsl:template match="Item">
<position>
<item>
<number>
<xsl:value-of select="ITEM_CODE"/>
</number>
<xsl:apply-templates select=
"self::node()[not(LINE_FREE_STOCK)]/LINE_QUANTITY
|
LINE_FREE_STOCK"/>
</item>
</position>
</xsl:template>
<xsl:template match="LINE_QUANTITY">
<quantity>
<xsl:value-of select="."/>
</quantity>
</xsl:template>
<xsl:template match="LINE_FREE_STOCK">
<freestock_quant>
<xsl:value-of select="."/>
</freestock_quant>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Order>
<Item>
<RECORD_ID>RECORD_ID</RECORD_ID>
<ENTITY_CODE>ENTITY_CODE</ENTITY_CODE>
<USER_CODE>USER_CODE</USER_CODE>
<RECORD_DATE>RECORD_DATE</RECORD_DATE>
<ITEM_CODE>ITEM_CODE</ITEM_CODE>
<LINE_QUANTITY>LINE_QUANTITY</LINE_QUANTITY>
<LINE_FREE_STOCK>LINE_FREE STOCK</LINE_FREE_STOCK>
<LINE_PRICE>LINE_PRICE</LINE_PRICE>
<LINE_DISCOUNT_PERCENT>LINE_DISCOUNT PERCENT</LINE_DISCOUNT_PERCENT>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008165</ITEM_CODE>
<LINE_QUANTITY>2</LINE_QUANTITY>
<LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008161</ITEM_CODE>
<LINE_QUANTITY>1</LINE_QUANTITY>
<LINE_FREE_STOCK>1</LINE_FREE_STOCK>
</Item>
<Item>
<RECORD_ID>9046</RECORD_ID>
<ENTITY_CODE>12010601</ENTITY_CODE>
<USER_CODE>122</USER_CODE>
<RECORD_DATE>2011-08-24</RECORD_DATE>
<ITEM_CODE>804-008225</ITEM_CODE>
<LINE_QUANTITY>5</LINE_QUANTITY>
</Item>
</Order>
the wanted, correct result is produced:
<ORDERS05>
<IDOC BEGIN="1">
<Header>some header data</Header>
<position>
<item>
<number>804-008165</number>
<freestock_quant>1</freestock_quant>
</item>
</position>
<position>
<item>
<number>804-008161</number>
<freestock_quant>1</freestock_quant>
</item>
</position>
<position>
<item>
<number>804-008225</number>
<quantity>5</quantity>
</item>
</position>
</IDOC>
</ORDERS05>
This is because you have two match templates for Item:
<xsl:template match="Item">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<quantity><xsl:value-of select="LINE_QUANTITY"/></quantity>
</item>
</position>
</xsl:if>
</xsl:template>
<xsl:template match="Item[child::LINE_FREE_STOCK]" mode="freestock">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<freestock_quant><xsl:value-of select="LINE_FREE_STOCK"/></freestock_quant>
</item>
</position>
</xsl:if>
</xsl:template>
First the default Item template matches and then the Item's with LINE_FREE_STOCK also matches the Item with child LINE_FREE_STOCK template, hence the duplicate for Item's with LINE_FREE_STOCK.
Instead why not just use one template, like this:
<xsl:template match="Item">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<xsl:choose>
<xsl:when test="child::LINE_FREE_STOCK">
<freestock_quant><xsl:value-of select="LINE_FREE_STOCK"/></freestock_quant>
</xsl:when>
<xsl:otherwise>
<quantity><xsl:value-of select="LINE_QUANTITY"/></quantity>
</xsl:otherwise>
</xsl:choose>
</item>
</position>
</xsl:if>
</xsl:template>
Using the single template your Order template is also simplified:
<xsl:template match="Order">
<Header>
<xsl:value-of select="'some header data'"/>
</Header>
<xsl:apply-templates select="Item"/>
</xsl:template>
This way you do not need to use Modes either.
It's not clear what is the wanted output. Perhaps you want:
<xsl:apply-templates select="Item[not(LINE_FREE_STOCK)"/>
<xsl:apply-templates select="Item[LINE_FREE_STOCK]" mode="freestock"/>
in place of your
<xsl:apply-templates select="Item"/>
<xsl:apply-templates select="Item[child::LINE_FREE_STOCK]" mode="freestock"/>
You would need an addition filtering
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" method="xml" indent="yes"/>
<xsl:template match="/">
<ORDERS05>
<IDOC BEGIN="1">
<xsl:apply-templates select="Order"/>
</IDOC>
</ORDERS05>
</xsl:template>
<xsl:template match="Order">
<Header>
<xsl:value-of select="'some header data'"/>
</Header>
<xsl:apply-templates select="Item[not(child::LINE_FREE_STOCK)]"/>
<xsl:apply-templates select="Item[child::LINE_FREE_STOCK]" mode="freestock"/>
</xsl:template>
<xsl:template match="Item">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<quantity><xsl:value-of select="LINE_QUANTITY"/></quantity>
</item>
</position>
</xsl:if>
</xsl:template>
<xsl:template match="Item[child::LINE_FREE_STOCK]" mode="freestock">
<xsl:if test="position() > 1">
<position>
<item>
<number><xsl:value-of select="ITEM_CODE"/></number>
<freestock_quant><xsl:value-of select="LINE_FREE_STOCK"/></freestock_quant>
</item>
</position>
</xsl:if>
</xsl:template>
</xsl:stylesheet>