Duplicate subnodes based on subelement value - xslt

How can one perform a conditional copy on xslt. For instance
<Person>
<Name>John</Name>
<Sex>M</Sex>
</Person>
<Person>
<Name>Jane</Name>
<Sex>F</Sex>
</Person>
So if Name = "John" then:
<Person>
<Name>John</Name>
<Sex>M</Sex>
</Person>
<Copied>
<Name>John</Name>
<Sex>M</Sex>
</Copied>
<Person>
<Name>Jane</Name>
<Sex>F</Sex>
</Person>
So far i have this bit of xslt:
<xsl:template match="Person">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
<Copied>
<xsl:apply-templates select="node()|#*"/>
</Copied>
</xsl:template>
This is also makes a copy for "jane" how can conditionally duplicate this?

You could do:
<xsl:template match="Person">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
<xsl:if test="Name='John'">
<Copied>
<xsl:apply-templates select="node()|#*"/>
</Copied>
</xsl:if>
</xsl:template>
Or perhaps:
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Person[Name='John']">
<xsl:copy-of select="."/>
<Copied>
<xsl:copy-of select="*"/>
</Copied>
</xsl:template>

Related

XSLT: combining two consecutive elements until element list is empty

I have this XML
<participants>
<event>Seminar</event>
<location>City somewhere</location>
<first_name>Carl</first_name>
<last_name>Smith</last_name>
<first_name>John</first_name>
<last_name>Somebody</last_name>
<first_name>Lisa</first_name>
<last_name>Lint</last_name>
<first_name>Gabriella</first_name>
<last_name>Whowho</last_name>
</participants>
Which I would need to be transformed into this:
<participants>
<event>Seminar</event>
<location>City somewhere</location>
<persons>
<person>
<given_name>Carl</given_name>
<surname>Smith</surname>
</person>
<person>
<given_name>John</given_name>
<surname>Somebody</surname>
</person>
<person>
<given_name>Lisa</given_name>
<surname>Lint</surname>
</person>
<person>
<given_name>Gabriella</given_name>
<surname>Whowho</surname>
</person>
</persons>
</participants>
The number of persons can be any number, sometimes there might be empty elements (if both first and last names would be empty, then the person would not be created.
I have hard time getting started with this transformation.
If you simply process the first_name elements and use XPath navigation to select its sibling last_name you get
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="participants">
<xsl:copy>
<xsl:apply-templates select="*[not(self::first_name | self::last_name)]"/>
<xsl:apply-templates select="first_name" mode="person"/>
</xsl:copy>
</xsl:template>
<xsl:template match="first_name" mode="person">
<xsl:variable name="surname" select="following-sibling::last_name[1]"/>
<xsl:if test="normalize-space() and normalize-space($surname)">
<person>
<xsl:apply-templates select=". | $surname"/>
</person>
</xsl:if>
</xsl:template>
<xsl:template match="first_name">
<given_name>
<xsl:apply-templates/>
</given_name>
</xsl:template>
<xsl:template match="last_name">
<surname>
<xsl:apply-templates/>
</surname>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/94AbWAW/1

current-group() except for one element in xslt

The below is my input xml. I'm trying group by using current-group() function but it is not meeting my requirement, below I have provided the details.
<UsrTimeCardEntry>
<Code>1<Code>
<Name>TC1</Name>
<Person>
<Code>074</Code>
</Person>
</UsrTimeCardEntry>
<UsrTimeCardEntry>
<Code>2<Code>
<Name>TC2</Name>
<Person>
<Code>074</Code>
</Person>
</UsrTimeCardEntry>
I want to group it by Person/Code so that it looks like this
<Person Code="074">
<UsrTimeCardEntry>
<Code>1</Code>
<Name>TC1</Name>
</UsrTimeCardEntry>
<UsrTimeCardEntry>
<Code>2</Code>
<Name>TC2</Name>
</UsrTimeCardEntry>
</Person>
For which I'm using the below xslt, but it is again copying the Person which I don't want, what it that I'm missing here, I tried using current-group() except and not[child::Person] but that too did not work.
<xsl:template match="businessobjects">
<xsl:for-each-group select="UsrTimeCardEntry" group-by="Person/Code">
<Person Code="{current-grouping-key()}">
<xsl:copy-of select="current-group()"></xsl:copy-of>
</Person>
</xsl:for-each-group>
</xsl:template>
Instead of using xsl:copy-of here, use xsl:apply-templates, then you can add a template to ignore the Person node
<xsl:template match="Person" />
This assumes you are also using the identity template to copy all other nodes normally.
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="businessobjects">
<xsl:for-each-group select="UsrTimeCardEntry" group-by="Person/Code">
<Person Code="{current-grouping-key()}">
<xsl:apply-templates select="current-group()" />
</Person>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="Person" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT - Copy grandchildren reordered

I'm quite new to XSLT and I was trying to copy an existent XML file I already have but with the elements reordered but I got stuck when trying to reorder grandchildren.
Let's say I have this input:
<grandParent>
<parent>
<c>789</c>
<b>
<b2>123</b2>
<b1>456</b1>
</b>
<a>123</a>
</parent>
....
</grandParent>
What I want to do is get the same XML file but changing the order of the tags to be a,b,c with b = b1, b2 in that order.
So I started with the XSLT file:
<xsl:template match="node()|#*"> <- This should copy everything as it is
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent"> <- parent elements will copy in this order
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="a"/>
<xsl:copy-of select="b"/>
<xsl:copy-of select="c"/>
</xsl:copy>
</xsl:template>
But "xsl:copy-of select="b"" copies the elements as they are specified (b2, b1).
I tried using another xsl:template for "grandParent/parent/b" but wouldn't help.
Maybe I'm not doing things the correct way... Any tips?
Thanks!
SOLUTION - Thanks to Nils
Your solution works just fine Nils, I just customized it a bit more to fit in my current scenario where "b" is optional and the names of the tags might not be correlative.
The final code is like this:
<xsl:template match="node()|#*"> <- This should copy everything as it is
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent"> <- parent elements will copy in this order
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="a"/>
<xslt:if test="b">
<b>
<xsl:copy-of select="b1"/>
<xsl:copy-of select="b2"/>
</b>
</xslt:if>
<xsl:copy-of select="b"/>
<xsl:copy-of select="c"/>
</xsl:copy>
</xsl:template>
I tried using another xsl:template for "grandParent/parent/b" but wouldn't help.
Since you have an identity template you should use <xsl:apply-templates> instead of <xsl:copy-of>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="a"/>
<xsl:apply-templates select="b"/>
<xsl:apply-templates select="c"/>
</xsl:copy>
</xsl:template>
Now you can add a similar template for the b elements
<xsl:template match="parent/b">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="b1"/>
<xsl:apply-templates select="b2"/>
</xsl:copy>
</xsl:template>
This will nicely handle the case where b doesn't exist - if the select="b" doesn't find any elements, then no templates will fire.
In fact, if the sort order is the same in both cases (alphabetically by element name) then you can combine the two templates into one which uses <xsl:sort>, giving a complete transformation of
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*" />
<xsl:output method="xml" indent="yes" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent | parent/b">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*">
<xsl:sort select="name()" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
(for the example XML you've given you don't actually need the #* bits because the XML doesn't include any attributes, but it won't do any harm to leave it there in case there are any in the real XML or you add any in future).
Using xsl:sort.
The following code is off the top of my head and might not work; the thought behind it should be clear though.
<xsl:template match="node()|#*"> <- This should copy everything as it is
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent"> <- parent elements will copy in this order
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="a"/>
<b>
<xsl:for-each select="b/*">
<xsl:sort select="text()" />
<xsl:copy-of select="." />
</xsl:for-each>
</b>
<xsl:copy-of select="c"/>
</xsl:copy>
</xsl:template>
Here is a most generic solution -- using xsl:sort and templates -- no xsl:copy-of and no hardcoding of specific names:
<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="*/*">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<grandParent>
<parent>
<c>789</c>
<b>
<b2>123</b2>
<b1>456</b1>
</b>
<a>123</a>
</parent>
....
</grandParent>
the wanted, correct result is produced:
<grandParent>
<parent>
<a>123</a>
<b>
<b1>456</b1>
<b2>123</b2>
</b>
<c>789</c>
</parent>
....
</grandParent>
Now, let's change all the names in the XML document -- note that none of the other answers works with this:
<someGrandParent>
<someParent>
<z>789</z>
<y>
<y2>123</y2>
<y1>456</y1>
</y>
<x>123</x>
</someParent>
....
</someGrandParent>
We apply the same transformation and it again produces the correct result:
<someGrandParent>
<someParent>
<x>123</x>
<y>
<y1>456</y1>
<y2>123</y2>
</y>
<z>789</z>
</someParent>
....
</someGrandParent>

Copy attribute from parent using xslt

I need to modify a XML using XSLT 1.0. Basically, I need to copy the attribute (name and value) to one child.
This is the xml:
<parent id="3450">
<son1>
<name>Malcom</name>
<age>15</age>
<description>This is the middle son</description>
</son1>
<son2>
<name>Francis</name>
<age>19</age>
<description>This is the oldest son</description>
</son2>
<son3>
<name>Dewey</name>
<age>9</age>
<description>This is the youngest son</description>
</son3>
</parent>
This should be the result:
<parent id="3450">
<son1 id="3450">
<name>Malcom</name>
<age>15</age>
<description>This is the middle son</description>
</son1>
</parent>
This is the XSLT that I'm working with:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="parent/son1">
<xsl:copy>
<xsl:attribute name="id"><xsl:value-of select="../#id"/></xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="parent/son2" />
<xsl:template match="parent/son3" />
The XSLT seems to be working, but my question is: Is this the right way to do it?
Thanks.
I would change
<xsl:template match="parent/son1">
<xsl:copy>
<xsl:attribute name="id"><xsl:value-of select="../#id"/></xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
to
<xsl:template match="parent/son1">
<xsl:copy>
<xsl:apply-templates select="../#id | #* | node()"/>
</xsl:copy>
</xsl:template>

How to create a new list and tag nodes in it using xslt

I am transforming an XML to another using Identity Transformation and during this, based on a condition I want to tag few nodes in a new list.
Suppose, I have an XML like:
<nod>
<a>A</a>
<b>B</b>
<c><p>p1</p></c>
<c><p>p2</p></c>
<c><p>p3</p></c>
<c><p>p4</p></c>
</nod>
From this XML , I want to update name of 'nod' to 'newnod' and create an orderedlist for element 'c' ; so that output comes as:
<newnod>
<a>A</a>
<b>B</b>
<orderedlist>
<listitem><p>p1</p></listitem>
<listitem><p>p2</p></listitem>
<listitem><p>p3</p></listitem>
<listitem><p>p4</p></listitem> </orderedlist>
</newnod>
Can anybody please tell me how to do this.
Thanks !!!
Here is a true "push-style" solution:
<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="/nod">
<newnod>
<xsl:apply-templates select="node()|#*"/>
</newnod>
</xsl:template>
<xsl:template match="c[1]">
<orderedlist>
<xsl:apply-templates mode="wrap"
select=".|following-sibling::c"/>
</orderedlist>
</xsl:template>
<xsl:template match="c" mode="wrap">
<listitem>
<xsl:apply-templates/>
</listitem>
</xsl:template>
<xsl:template match="c"/>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<nod>
<a>A</a>
<b>B</b>
<c><p>p1</p></c>
<c><p>p2</p></c>
<c><p>p3</p></c>
<c><p>p4</p></c>
</nod>
the wanted, correct result is produced:
<newnod>
<a>A</a>
<b>B</b>
<orderedlist>
<listitem>
<p>p1</p>
</listitem>
<listitem>
<p>p2</p>
</listitem>
<listitem>
<p>p3</p>
</listitem>
<listitem>
<p>p4</p>
</listitem>
</orderedlist>
</newnod>
This produces the desired output for your example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/nod">
<newnod>
<xsl:copy-of select="*[not( self::c )]"/>
<orderedlist>
<xsl:apply-templates select="c">
<xsl:sort/>
</xsl:apply-templates>
</orderedlist>
</newnod>
</xsl:template>
<xsl:template match="nod/c">
<listitem>
<xsl:copy-of select="node()"/>
</listitem>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that it might need some tweaking for your actual input. For example, the details of sorting. Or the sequence of a, b and c. And lump all c together or not? But it works for your sample.
You could use something like:
<xsl:template match="nod">
<newnod>
<xsl:copy-of select="a"/>
<xsl:copy-of select="b"/>
<orderedlist>
<xsl:for-each select="c">
<listitem><p><xsl:value-of select="."/></p></listitem>
</xsl:for-each>
</orderedlist>
</newnod>
</xsl:template>
Other approach is traversing the following axis like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="nod">
<newnod>
<xsl:apply-templates select="node()[1]|#*"/>
</newnod>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="c">
<orderedlist>
<xsl:call-template name="wrap"/>
</orderedlist>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="search"/>
</xsl:template>
<xsl:template match="c" name="wrap" mode="wrap">
<listitem>
<xsl:apply-templates select="node()[1]|#*"/>
</listitem>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="wrap"/>
</xsl:template>
<xsl:template match="node()" mode="wrap"/>
<xsl:template match="c" mode="search">
<xsl:apply-templates select="following-sibling::node()[1]"
mode="search"/>
</xsl:template>
<xsl:template match="node()" mode="search">
<xsl:apply-templates select="."/>
</xsl:template>
</xsl:stylesheet>
Or shorter (more "push style"):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="nod">
<newnod>
<xsl:apply-templates select="node()[1]|#*"/>
</newnod>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="c">
<orderedlist>
<xsl:call-template name="wrap"/>
</orderedlist>
<xsl:apply-templates select="following-sibling::node()
[not(self::c)][1]"/>
</xsl:template>
<xsl:template match="c" name="wrap" mode="wrap">
<listitem>
<xsl:apply-templates select="node()[1]|#*"/>
</listitem>
<xsl:apply-templates select="following-sibling::node()[1]
/self::c"
mode="wrap"/>
</xsl:template>
</xsl:stylesheet>
Both output:
<newnod>
<a>A</a>
<b>B</b>
<orderedlist>
<listitem>
<p>p1</p>
</listitem>
<listitem>
<p>p2</p>
</listitem>
<listitem>
<p>p3</p>
</listitem>
<listitem>
<p>p4</p>
</listitem>
</orderedlist>
</newnod>
Note: Even with XSLT 2.0, some complex sequence processing are expressed better with this pattern.