Similar looking XSLT transforms not working - xslt

Input XML
<Web-inf>
<A>
<A1>Val1</A1>
<A1>Val1</A1>
<A1>Val1</A1>
</A>
<A>
<A1>Val2</A1>
<A1>Val2</A1>
<A1>Val2</A1>
</A>
<B>
<B1>Hi</B1>
</B>
<B>
<B1>Bye</B1>
</B>
<C>DummyC</C>
<D>DummyD</D>
</Web-inf>
I want to add <B> tag if it doesn't already exist with <B1> value as "Morning" and "Evening". If it exists i don't do anything. I have written following transform but the strange issue is that only the LATER one works and FIRST one is ignored completely. As a result only <B><B1>Evening</B1></B> is only inserted along with <B> tags. Is that a known issue? If yes, how to correct it?
<xsl:output method="xml" indent="yes" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Web-inf[not(B[B1='Morning'])]/B[last()]">
<xsl:copy-of select="*" />
<B>
<B1>Morning</B1>
</B>
</xsl:template>
<xsl:template match="Web-inf[not(B[B1='Evening'])]/B[last()]">
<xsl:copy-of select="*" />
<B>
<B1>Evening</B1>
</B>
</xsl:template>
I want the O/P XML to be as below
Output.xml
<Web-inf>
<A>
<A1>Val1</A1>
<A1>Val1</A1>
<A1>Val1</A1>
</A>
<A>
<A1>Val2</A1>
<A1>Val2</A1>
<A1>Val2</A1>
</A>
<B>
<B1>Hi</B1>
</B>
<B>
<B1>Bye</B1>
</B>
<B>
<B1>Morning</B1>
</B>
<B>
<B1>Evening</B1>
</B>
<C>DummyC</C>
<D>DummyD</D>
</Web-inf>

For you given input XML, both templates for B[last()] will be matched. When two templates match an element with equal priority, this is considered an error. The XSLT processor will either flag the error, or ignore all but the last matching template.
In this case, it might simply be better to have single template matching B[last()] and have the other conditions as xsl:if statements in the template.
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="Web-inf/B[last()]">
<xsl:copy-of select="*" />
<xsl:if test="not(../B[B1='Morning'])">
<B>
<B1>Morning</B1>
</B>
</xsl:if>
<xsl:if test="not(../B[B1='Evening'])">
<B>
<B1>Evening</B1>
</B>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Related

XSLT Need to count number of nodes where count is more than 1 within the parent node

Given the below simplified XML, I need to count the number of nodes where there is more than 1 node under /root/a/. So even though there are a total of 8 nodes, the correct answer for me is 3 since the first and last /root/a have more than 1 , 3 total. The xslt I have here will just give me the value 8. I've looked at using keys, position function, etc. unsuccessfully... but I'm thinking there is probably simpler answers. Thanks for any help.
<root>
<a>
<b>
<c>valuex</c>
</b>
<b>
<c>valuey</c>
</b>
</a>
<a>
<b>
<c>valuez</c>
</b>
</a>
<a>
<b>
<c>valuex</c>
</b>
</a>
<a>
<b>
<c>valuex</c>
</b>
</a>
<a>
<b>
<c>valuex</c>
</b>
<b>
<c>valuey</c>
</b>
<b>
<c>valuey</c>
</b>
</a>
<a>
</a>
</root>
My stylesheet
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="addCount">
<xsl:choose>
<xsl:when test="/root/a/b">
<xsl:value-of select="count(/root/a/b)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$addCount"/>
</xsl:template>
</xsl:stylesheet>
Based upon your comment, it sounds as if you want to find the count of the b elements that are children of /root/a , but are not the first b element under each of the a elements.
You can use the following to select all of the b elements that are children of /root/a and exclude the first b from each set:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="count(/root/a/b[position() > 1])"/>
</xsl:template>
</xsl:stylesheet>
You could also use the following to obtain the count of all the b elements that have a preceding-sibling that is a b element:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="count(/root/a/b[preceding-sibling::b])"/>
</xsl:template>
</xsl:stylesheet>
If I understand your clarification correctly (and that's a big if!), you want to do:
<xsl:template match="/root">
<xsl:variable name="a" select="a[count(b) > 1]" />
<xsl:value-of select="count($a/b) - count($a)"/>
</xsl:template>
which could be shortened to:
<xsl:template match="/root">
<xsl:value-of select="count(a/b[position() != 1])"/>
</xsl:template>

XSLT combining child nodes where the parent nodes have a certain (unknown) value

I have XML that contains sibling nodes that have identical attribute values, but have different contents. This occurs at both the parent and the child level, as follows:
<myxml>
<a myattr="valuetop1">
<b myattr="valuechild1">
<c>Stuff1</c>
<c>Stuff2</c>
</b>
</a>
<a myattr="valuetop1">
<b myattr="valuechild2">
<c>Stuff3</c>
</b>
</a>
<a myattr="valuetop1">
<b myattr="valuechild2">
<c>Stuff4</c>
</b>
</a>
<a myattr="valuetop1">
<b myattr="valuechild2">
<c>Stuff5</c>
<c>Stuff6</c>
</b>
</a>
<a myattr="valuetop2">
<b myattr="valuechild1">
<c>Stuff1</c>
</b>
</a>
<a myattr="valuetop2">
<b myattr="valuechild3">
<c>Stuff2</c>
</b>
</a>
<a myattr="valuetop2">
<b myattr="valuechild2">
<c>Stuff3</c>
<c>Stuff2</c>
</b>
</a>
<a myattr="valuetop2">
<b myattr="valuechild2">
<c>Stuff4</c>
</b>
</a>
</myxml>
If there are nodes with identical attribute values that exist at the same level, I want to combine their contents under a single instance of that node. In other words, I'm looking for a neat hierarchy like this:
<myxml>
<a myattr="valuetop1">
<b myattr="valuechild1">
<c>Stuff1</c>
<c>Stuff2</c>
</b>
<b myattr="valuechild2">
<c>Stuff3</c>
<c>Stuff4</c>
<c>Stuff5</c>
<c>Stuff6</c>
</b>
</a>
<a myattr="valuetop2">
<b myattr="valuechild1">
<c>Stuff1</c>
</b>
<b myattr="valuechild3">
<c>Stuff2</c>
</b>
<b myattr="valuechild2">
<c>Stuff3</c>
<c>Stuff2</c>
<c>Stuff4</c>
</b>
</a>
</myxml>
The catch is that I don't know what the values of valuetopx or valuechildx will be. I've been banging my head over this one for a couple of days, but can't get my brain around it.
As mentioned in comments, you can use a technique called Muenchian Grouping in XSLT 1.0, but in your case you are doing it on two-levels.
First for the parent, you define the key like so
<xsl:key name="parent" match="a" use="#myattr" />
Then, for the child, you need to take into account both the parent ID and child ID (in the case where a child id may have different parent ids, and so would be a different group)
<xsl:key name="child" match="b" use="concat(../#myattr, '|', #myattr)" />
Then, to get the distinct parent ids, you do this....
<xsl:apply-templates select="a[generate-id() = generate-id(key('parent', #myattr)[1])]" />
And within a distinct parent, to get the distinct child elements, do this...
<xsl:apply-templates select="key('parent', #myattr)/b
[generate-id() = generate-id(key('child', concat(../#myattr, '|', #myattr))[1])]" />
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="parent" match="a" use="#myattr" />
<xsl:key name="child" match="b" use="concat(../#myattr, '|', #myattr)" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="myxml">
<xsl:copy>
<xsl:apply-templates select="a[generate-id() = generate-id(key('parent', #myattr)[1])]" />
</xsl:copy>
</xsl:template>
<xsl:template match="a">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:apply-templates select="key('parent', #myattr)/b[generate-id() = generate-id(key('child', concat(../#myattr, '|', #myattr))[1])]" />
</xsl:copy>
</xsl:template>
<xsl:template match="b">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:apply-templates select="key('child', concat(../#myattr, '|', #myattr))/c" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Grouping XSLT Element based on a attribute value

I have a requirement to transform the student records uniquely.
Sample IP:
<Root>
<A>
<B>
<C>
<qty>1</qty>
<item id="1"></stud>
</C>
<C>
<qty>2</qty>
<item id="1"></stud>
</C>
</B>
</A>
O/P Needed:
<Root>
<A>
<B>
<C>
<qty>3</qty>
<item id="1"></stud>
</C>
</B>
</A>
How do I do this in xslt 1.0? I tried Muenchian grouping! But failed. pls guide me!
Define a key <xsl:key name="c-by-id" match"B/C" use="item/#id"/>, then use that to suppress copying the items that are duplicates with
<xsl:template match="B/C[not(generate-id() = generate-id(key('c-by-id', item/#id)[1]))]"></xsl:template>
and to compute the sum with
<xsl:template match="B/C/qty">
<xsl:copy>
<xsl:value-of select="sum(key('c-by-id', ../item/#id)/qty)"/>
</xsl:copy>
</xsl:template>
Together with the identity transformation template you have the full stylesheet
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="c-by-id" match="B/C" use="item/#id"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="B/C[not(generate-id() = generate-id(key('c-by-id', item/#id)[1]))]"></xsl:template>
<xsl:template match="B/C/qty">
<xsl:copy>
<xsl:value-of select="sum(key('c-by-id', ../item/#id)/qty)"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
online as http://xsltransform.net/nc4NzQs.

XSLT - replace reoccuring parts of XML document (at any level)

pros,
I need to convert the 'B' tag with 'X' tag in the following document:
<a>
<B marker="true">
<c>
<B marker="true">
<d>
<B marker="true">
</B>
<d>
</B>
</c>
</B>
</a>
Note the reoccurring 'B', it can appear at any depth in dynamic XML.
Here's what I did:
<xsl:template match="//*[#marker='true']">
<X>
<xsl:copy-of select="./node()"/>
</X>
</xsl:template>
It worked for the top-most 'B' tag, but ignored all the nested ones.
I think I know what the problem is - 'copy-of' just flushes the content of the top-most 'B' tag, without evaluating it. What can I do to make 'copy-of' reevaluate my template?
Thanks!
Baruch.
I would go with identity transform.
This code:
<?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="B[#marker = 'true']">
<X>
<xsl:apply-templates/>
</X>
</xsl:template>
</xsl:stylesheet>
Against this XML input:
<a>
<B marker="true">
<c test="test">
testText
<B marker="true">
<d>
testText2
<B marker="true">
testText3
</B>
</d>
</B>
testText4
</c>
testText5
</B>
</a>
Will provide this correct result:
<a>
<X>
<c test="test">
testText
<X>
<d>
testText2
<X>
testText3
</X>
</d></X>
testText4
</c>
testText5
</X>
</a>

XSLT: select distinct but slightly different to other examples

I have the following XML:
<a>
<b>
<d>D1 content (can include child nodes)</d>
</b>
<b>
<c>C1 content (can include child nodes)</c>
</b>
<b>
<e>E1 content (can include child nodes)</e>
</b>
<b>
<c>C2 content (can include child nodes)</c>
</b>
</a>
Using XSLT 1.0, I need to produce from this simply: "cde"; i.e. a distinct list of the names of the immediate children of /a/b/ ordered by the node name. Each b has exactly one child of arbitrary name.
I can produce "ccde":
<xsl:for-each select="/a/b/*">
<xsl:sort select="name(.)"/>
<xsl:value-of select="name(.)" />
</xsl:for-each>
I've tried using the usual preceding-sibling:: comparison, but as each b only has one child, the preceding sibling is always nothing.
First add this key element to the top of your XSL:-
<xsl:key name="tagNames" match="/a/b/*" use="name()" />
Now your for each loop can look like this:-
<xsl:template match="/*">
<xsl:for-each select="/a/b/*[count(. | key('tagNames', name())[1]) = 1]">
<xsl:sort select="name()" />
<xsl:value-of select="name()" />
</xsl:for-each>
</xsl:template>
You can use Muenchian method:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="groupIndex" match="*" use="name()" />
<xsl:template match="/">
<xsl:apply-templates select="a/b"/>
</xsl:template>
<xsl:template match="b">
<xsl:apply-templates select="*[1][generate-id(.) = generate-id(key('groupIndex', name())[1])]" mode="group" />
</xsl:template>
<xsl:template match="*" mode="group">
<xsl:value-of select="name()"/>
</xsl:template>
</xsl:stylesheet>