Grouping XSLT Element based on a attribute value - xslt

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.

Related

Checking condition in upper level tag in xslt v2.0

I have this requirement that I only need to populate the <Group> tag under the <Section>, if the <Group> tag under the <Data> is not present. I can't get the correct output that I want. For example:
INPUT
<Record>
<Data>
<ID>1234DFD57</ID>
<Group>
<abc-KD>243fds</abc-KD>
</Group>
<Section>
<ID>33-2311</ID>
<Group>
<abc-KD>NORM</abc-KD>
</Group>
<Date>2017-03-25</Date>
</Section>
<Date>2017-03-25</Date>
</Data>
</Record>
EXPECTED OUTPUT
<Record>
<Data>
<ID>1234DFD57</ID>
<Group>
<abc-KD>243fds</abc-KD>
</Group>
<Section>
<ID>33-2311</ID>
<Date>2017-03-25</Date>
</Section>
<Date>2017-03-25</Date>
</Data>
</Record>
XSLT:
<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="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Section">
<xsl:copy>
<xsl:copy-of select="ID"/>
<xsl:if test="normalize-space(string(../Group)) = ''">
<xsl:copy-of select="Group"/>
</xsl:if>
<xsl:copy-of select="Date"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Your feedback is highly appreciated.
Regards,
Your current stylesheet does the work. More efficient way would be:
<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"/>
<!-- identity transform template -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- do nothing for the Group -->
<xsl:template match="Section[normalize-space(parent::Data/Group) != '']/Group"/>
</xsl:stylesheet>
The identity transform template is the one that copies every node in the xml, while recursively processing them in document order, as it is.
The second template matches the Group elements with the desired condition and does nothing for it, hence omitting them in the output.
The x-path in the #match does the trick:
Section[normalize-space(parent::Data/Group) != '']/Group
It matches those Section/Group elements under Data whose Group doesn't exist or has null value(excluding space characters).

xslt-1.0 depth first pre-order traversal numbering

I am looking for an identity transform that adds an ordering attribute to each node. I want to have the explicit document position of each node available as an integer number.
I believe the desired ordering (as in https://en.wikipedia.org/wiki/Tree_traversal#/media/File:Sorted_binary_tree_preorder.svg) is the default ordering indeed as a selection on the descendants axis produces the correct ordering numbers:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method='xml' encoding='utf-8' indent='yes'/>
<xsl:template match="/*">
<root>
<xsl:apply-templates select="descendant::*">
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:attribute name="doc_order">
<xsl:value-of select="position()"/>
</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
but I need to preserve the input document structure as well,
as code above generates a flat list of all nodes instead.
example input:
<root>
<f>
<b>
<a/>
<d>
<c/>
<e/>
</d>
</b>
</f>
<g>
<i>
<h/>
</i>
</g>
</root>
desired output with explicit document order:
<root>
<f doc_order="1">
<b doc_order="2">
<a doc_order="3"/>
<d doc_order="4">
<c doc_order="5"/>
<e doc_order="6"/>
</d>
</b>
</f>
<g doc_order="7">
<i doc_order="8">
<h doc_order="9"/>
</i>
</g>
</root>
Learn about xsl:number, it is powerful:
<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="/*//*">
<xsl:copy>
<xsl:attribute name="doc_order">
<xsl:number level="any" count="*" from="root"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See http://xsltransform.net/94rmq6n for an online sample.

How to Add to an Element, Creating it First If Needed

I need to add elements to an element, creating it first if it doesn't already exist.
My desired final result, after adding ABC and DEF, is:
<?xml version="1.0" encoding="utf-8"?>
<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Q/>
<B>
<string>ABC</string>
<string>DEF</string>
</B>
<A>
I thought that the following would accomplish this:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Identity transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Insert a B element if it doesn't exist. -->
<xsl:template match="A[not(B)]">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<B/>
</xsl:copy>
</xsl:template>
<xsl:template match="B">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<string>ABC</string>
<string>DEF</string>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If I start with the following, in which <B> already exists, it works fine, returning the result above:
<?xml version="1.0" encoding="utf-8"?>
<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org
<Q/>
<B/>
</A>
However, if I don't have a <B>, as in below:
<?xml version="1.0" encoding="utf-8"?>
<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org
<Q/>
</A>
then it creates the <B> as below, but doesn't insert ABC and DEF:
<?xml version="1.0" encoding="utf-8"?>
<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org
<Q/>
<B/>
</A>
So, what am I missing? Thanks in advance.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- Identity transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Insert a B element with string elements if it doesn't exist. -->
<xsl:template match="A[not(B)]">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<B>
<xsl:call-template name="add-strings"/>
</B>
</xsl:copy>
</xsl:template>
<!-- Add string elements to existing B if missing -->
<xsl:template match="B[not(string)]">
<xsl:copy>
<xsl:call-template name="add-strings"/>
</xsl:copy>
</xsl:template>
<!-- Add string elements to caller -->
<xsl:template name="add-strings">
<string>ABC</string>
<string>DEF</string>
</xsl:template>
</xsl:stylesheet>
You will have to add the sub tags of B too when B does not exist, as follows:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- Identity transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Insert a B element if it doesn't exist. -->
<xsl:template match="A[not(B)]">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<B>
<string>ABC</string>
<string>DEF</string>
</B>
</xsl:copy>
</xsl:template>
<xsl:template match="B">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<string>ABC</string>
<string>DEF</string>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied to
<?xml version="1.0" encoding="UTF-8"?>
<root>
<A>
<Q/>
</A>
<A>
<Q/>
<B/>
</A>
</root>
this gives
<?xml version="1.0" encoding="UTF-8"?>
<root>
<A>
<Q/>
<B>
<string>ABC</string>
<string>DEF</string>
</B>
</A>
<A>
<Q/>
<B>
<string>ABC</string>
<string>DEF</string>
</B>
</A>
</root>
Empo's answer was very close, but if <B> already contained <string> elements, the new <string>s weren't added. I made two minor changes, which solved that:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Identity transform. -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Insert a B element with string elements if it doesn't exist. -->
<xsl:template match="A[not(B)]">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<B>
<xsl:call-template name="add-strings"/>
</B>
</xsl:copy>
</xsl:template>
<!-- Add string elements to existing B element. -->
<xsl:template match="B"> <!-- Whether there are <string>s or not. -->
<xsl:copy>
<xsl:apply-templates select="#* | node()"/> <!-- Keep existing <string>s. -->
<xsl:call-template name="add-strings"/>
</xsl:copy>
</xsl:template>
<!-- Add string elements to caller. -->
<xsl:template name="add-strings">
<string>ABC</string>
<string>DEF</string>
</xsl:template>
</xsl:stylesheet>

How to parse nested tags using XSLT in sequence?

I have below scenario for my XML.
<content>
<para>text-1 <emphasis type="bold">text-2</emphasis> text-3</para>
</content>
I want to parse it like below
<content>
<p>text-1 <b>text-2</b> text-3</p>
</content>
I have created my XSLT as below
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" encoding="ISO-8859-1" indent="no"/>
<xsl:template name="para">
<p>
<xsl:value-of select="text()" disable-output-escaping="yes"/>
<xsl:for-each select="child::*">
<xsl:if test="name()='emphasis'">
<xsl:call-template name="emphasis"/>
</xsl:if>
</xsl:for-each>
</p>
</xsl:template>
<xsl:template name="emphasis">
<xsl:if test="attribute::type = 'bold'">
<b>
<xsl:value-of select="text()" disable-output-escaping="yes"/>
</b>
</xsl:if>
</xsl:template>
<xsl:template match="/">
<content>
<xsl:for-each select="content/child::*">
<xsl:if test="name()='para'">
<xsl:call-template name="para"/>
</xsl:if>
</xsl:for-each>
</content>
</xsl:template>
</xsl:stylesheet>
XSLT provided above is generating output like below
<content>
<p>text-1 text-3<b>text-2 </b></p>
</content>
Please guide me with your suggestions, how can I get my desire output?
To do this, you just need to extend the standard Identity Transform with special cases for matching your para and emphasis elements. For example, for para elements you would the following to replace para with p and then continue matching all the child nodes
<xsl:template match="para">
<p>
<xsl:apply-templates select="#*|node()"/>
</p>
</xsl:template>
So, given the following XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<!-- This is the Identity Transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Replace para with p -->
<xsl:template match="para">
<p>
<xsl:apply-templates select="#*|node()"/>
</p>
</xsl:template>
<!-- Replace emphasis with b -->
<xsl:template match="emphasis[#type='bold']">
<b>
<xsl:apply-templates select="node()"/>
</b>
</xsl:template>
</xsl:stylesheet>
When applied to the following input XML
<content>
<para>text-1 <emphasis type="bold">text-2</emphasis> text-3</para>
</content>
The following is output
<content>
<p>text-1 <b>text-2</b> text-3</p>
</content>
You should be able to see how easy it is to extend to other cases should you input XML have more tags to transform.
do it like this ;)
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="content">
<content><xsl:apply-templates select="para" /></content>
</xsl:template>
<xsl:template match="emphasis [#type='bold']">
<b><xsl:value-of select="." /></b>
</xsl:template>
</xsl:stylesheet>
when you do it like this the default template will catch text-1 and text-3

How to remove XML elements dynamically

I am basically looking for an XSLT (say a callable template), which will take as input an xml AND an element to be deleted in the XML and give me back the final XML after deleting that particular element in the XML.
Example:
<Request>
<Activity1>XYZ</Activity1>
<Activity2>ABC</Activity2>
</Request>
Now i need an xslt for which i must give the above xml as input and the element to be deleted (Say <Activity1>) as input. The XSLT must return the final xml after deleting the element passed to it.
You can use a modified copy-template:
<xsl:stylesheet ...>
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:variable name="removeNode">Activity1</xsl:variable>
<xsl:template match="node()">
<xsl:if test="not(name()=$removeNode)">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="#*">
<xsl:copy>
<xsl:apply-templates select="#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
How to pass the parameter to yout template depends on your used XSLT-processor.
Edit
Another possibility is to ignore the node when needed:
<xsl:template match="/">
<xsl:apply-templates select="*/*[not(self::element-to-ignore)]"
mode="renderResult"/>
</xsl:template>
<xsl:template match="#*|node()" mode="renderResult">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="renderResult"/>
</xsl:copy>
</xsl:template>
This is a generic transformation that accepts a global (externally specified) parameter with the name of the element to be deleted:
<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:param name="pDeleteName" select="'c'"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:if test="not(name() = $pDeleteName)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (for example the following):
<a>
<b>
<c/>
<d>
<e>
<c>
<f/>
</c>
<g/>
</e>
</d>
</b>
</a>
the correct result is produced -- the source XML document in which any element having name the same as the string in the pDeleteName parameter -- is deleted:
<a>
<b>
<d>
<e>
<g/>
</e>
</d>
</b>
</a>
As can be clearly seen, any occurence of the element <c> has been deleted.