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

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>

Related

Find distinct subchild element for each element with xpath

I'm just lost at using xsl1.0
I'm trying to get the distinct output of each 'row' when the data is split into multiple subchilds.
Provided this data source..
<?xml version="1.0" encoding="utf-8"?>
<Root>
<row>
<data>
<a>
<b>
<c>
<Vehicle>
<ManufacturerName>BM</ManufacturerName>
</Vehicle>
</c>
<c>
<Vehicle>
<ManufacturerName>FORD</ManufacturerName>
</Vehicle>
</c>
<c>
<Vehicle>
<ManufacturerName>VAUXHALL</ManufacturerName>
</Vehicle>
</c>
<c>
<Vehicle>
<ManufacturerName>VAUXHALL</ManufacturerName>
</Vehicle>
</c>
</b>
</a>
<a>
<b>
<c>
<Vehicle>
<ManufacturerName>VAUXHALL</ManufacturerName>
</Vehicle>
</c>
<c>
<Vehicle>
<ManufacturerName>ALPHA</ManufacturerName>
</Vehicle>
</c>
<c>
<Vehicle>
<ManufacturerName>BRAVO</ManufacturerName>
</Vehicle>
</c>
</b>
</a>
</data>
</row>
<row>
<data>
<a>
<b>
<c>
<Vehicle>
<ManufacturerName>CHARLIE</ManufacturerName>
</Vehicle>
</c>
<c>
<Vehicle>
<ManufacturerName>ALPHA</ManufacturerName>
</Vehicle>
</c>
<c>
<Vehicle>
<ManufacturerName>VAUXHALL</ManufacturerName>
</Vehicle>
</c>
</b>
</a>
<a>
<b>
<c>
<Vehicle>
<ManufacturerName>BM</ManufacturerName>
</Vehicle>
</c>
<c>
<Vehicle>
<ManufacturerName>ALPHA</ManufacturerName>
</Vehicle>
</c>
</b>
</a>
</data>
</row>
</Root>
I want to get an output that looks like this.
BM, FORD, VAUXHALL, ALPHA, BRAVO,
CHARLIE, ALPHA, VAUXHALL, BM,
I'm sorry if this has been asked before, but I couldn't figure out how to select the distinct values for each row. My current results only show the values in the first row, with any repeating values in the other rows are not shown.
Any help would be greatly appreciated.
Currently my xsl is
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="Root">
<xsl:apply-templates select="row"/>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="data/a/b/c/Vehicle[not(ManufacturerName=preceding::Vehicle/ManufacturerName)]"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="data/a/b/c/Vehicle">
<xsl:value-of select="ManufacturerName"/>
<xsl:text>,</xsl:text>
</xsl:template>
</xsl:stylesheet>
with this result.
BM,FORD,VAUXHALL,ALPHA,BRAVO,
CHARLIE,
First, I would recommend you use Muenchian grouping to get distinct values, instead of the inefficient method you are using now.
To perform separate grouping for each row, include the row's unique id in the grouping key:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="vehicle" match="Vehicle" use="concat(ManufacturerName, '|', generate-id(ancestor::row))" />
<xsl:template match="/Root">
<xsl:for-each select="row">
<xsl:variable name="row-id" select="generate-id()" />
<!-- unique vahicles in this row -->
<xsl:for-each select="data/a/b/c/Vehicle[count(. | key('vehicle', concat(ManufacturerName, '|', $row-id))[1]) = 1]">
<xsl:value-of select="ManufacturerName"/>
<xsl:text>, </xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note that some XSLT 1.0 processors support the EXSLT set:distinct() extension function, which makes this kind of thing much simpler.

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>

Similar looking XSLT transforms not working

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>

Hey guyz i have been searching solution all over but didn't find any thing similar:

I have following structured source xml document:
<a>
<k>
<l> l_hello </l>
</k>
<b id="0">
<c>
<d>d_hello</d>
</c>
</b>
<e id="0">
<f>
<g>
<h>h_hello</h>
</g>
</f>
</e>
</a>
Now what i want to do is ...... i want to repeat whole <b> tag 5 times and parallel to it <e> tag 5 times with all the attributes in place . I mean to say i want following out to get generated:
<a>
<k>
<l> l_hello </l>
</k>
<b id="0">
<c>
<d>d_hello</d>
</c>
</b>
<e id="0">
<f>
<g>
<h>h_hello</h>
</g>
</f>
</e>
<b id="0">
<c>
<d>d_hello</d>
</c>
</b>
<e id="0">
<f>
<g>
<h>h_hello</h>
</g>
</f>
</e>
<b id="0">
<c>
<d>d_hello</d>
</c>
</b>
<e id="0">
<f>
<g>
<h>h_hello</h>
</g>
</f>
</e>
<b id="0">
<c>
<d>d_hello</d>
</c>
</b>
<e id="0">
<f>
<g>
<h>h_hello</h>
</g>
</f>
</e>
<b id="0">
<c>
<d>d_hello</d>
</c>
</b>
<e id="0">
<f>
<g>
<h>h_hello</h>
</g>
</f>
</e>
</a>
Anyone if come up with a solution would be of great help....I have generated code but that's all wrong so no point in pasting it here.....
Here's a very simple way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/a">
<xsl:copy>
<xsl:copy-of select="k"/>
<xsl:copy-of select="b"/>
<xsl:copy-of select="e"/>
<xsl:copy-of select="b"/>
<xsl:copy-of select="e"/>
<xsl:copy-of select="b"/>
<xsl:copy-of select="e"/>
<xsl:copy-of select="b"/>
<xsl:copy-of select="e"/>
<xsl:copy-of select="b"/>
<xsl:copy-of select="e"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
tell me some way where all tags other than b and e should come once
and b and e should be repeated 5 times
That too can be simple:
<xsl:template match="/a">
<xsl:copy>
<xsl:copy-of select="*[not(self::b or self::e)]"/>
<xsl:variable name="be" select="b|e" />
<xsl:copy-of select="$be"/>
<xsl:copy-of select="$be"/>
<xsl:copy-of select="$be"/>
<xsl:copy-of select="$be"/>
<xsl:copy-of select="$be"/>
</xsl:copy>
</xsl:template>

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.