Uniformly process grouped sibling elements - xslt

Given the following XML document
<root>
<a pos="0" total="2"/>
<a pos="1" total="2"/>
<a pos="0" total="3"/>
<a pos="1" total="3"/>
<a pos="2" total="3"/>
<a pos="0" total="4"/>
<a pos="1" total="4"/>
<a pos="2" total="4"/>
<a pos="3" total="4"/>
</root>
I need to translate it to
<root>
<group>
<a pos="0" total="2"/>
<a pos="1" total="2"/>
</group>
<group>
<a pos="0" total="3"/>
<a pos="1" total="3"/>
<a pos="2" total="3"/>
</group>
<group>
<a pos="0" total="4"/>
<a pos="1" total="4"/>
<a pos="2" total="4"/>
<a pos="3" total="4"/>
</group>
</root>
using an XSLT 1.0 stylesheet.
That is, each <a> element with the #pos attribute of 0 in the document
implicitly starts a group consisting of it and the #total-1 following <a> elements. To explain this in other words, #pos denotes a 0-based index (position) of the element in the group of #total adjacent elements.
I have come up with the following stylesheet, which works:
<?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="/">
<xsl:apply-templates select="root" />
</xsl:template>
<xsl:template match="root">
<xsl:apply-templates select="a[#pos=0]" mode="leader"/>
</xsl:template>
<xsl:template match="a" mode="leader">
<group>
<xsl:apply-templates select="." />
<xsl:apply-templates select="following-sibling::a[position() <= current()/#total - 1]" />
</group>
</xsl:template>
<xsl:template match="a">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
The problem I have with my solution is that it makes those a[#pos=0] elements "special": to further process each <a> element in a prospective group, I have to separately apply the appropriate template first to the "group leader" element and then to the rest of the elements in the group.
In other words I would very much like to have something like (incorrect)
<xsl:template match="a" mode="leader">
<group>
<xsl:apply-templates select=". and following-sibling::a[position() <= current()/#total - 1]" />
</group>
</xsl:template>
which would apply my <xsl:template match="a"> template to all the elements in the group in one go. (To rephrase what I've attempted to spell in the select expression: "select the context element and its following sibling elements matching …".)
Is there a way to have what I want with XSLT 1.0 without resorting to hacks like variables and exslt:node-set()? May be there's a better way to do such grouping based on element counts than the one I have come up with (which inherently makes the first element in each group special)?
I admit the question's title is rather weak but I failed to come up with a succint one which correctly reflect the essense of my question.

You could do:
<xsl:apply-templates select=". | following-sibling::a[position() <= current()/#total - 1]" />
P.S. Using variables or the node-set() function does not qualify as a "hack".

Related

Is it possible to apply template to copy of element?

Is it possible to apply template on copy of element?
Let say I have this XML:
<root>
<a name="a1">
A1
<b name="b1">B1</b>
</a>
<b name="b2">B2</b>
<a name="a2">
<b name="b3">B3</b>
</a>
<b name="b4">B4</b>
<a name="a3">
<b name="b5">B5</b>
</a>
</root>
and want to turn it into this XML:
<root>
<a name="a1">
A1
<b name="b1">B1</b>
<b name="b2">B2</b>
</a>
<a name="a2">
<b name="b3">B3</b>
<b name="b4">B4</b>
</a>
<a name="a3">
<b name="b5">B5</b>
</a>
</root>
and than apply this templates on new structure:
<xsl:template match="/">
1<xsl:apply-templates/>2
</xsl:template>
<xsl:template match="a">
3<xsl:apply-templates select="b[2]"/>4
</xsl:template>
<xsl:template match="b[not(position()=2)]">
5<xsl:apply-templates/>6
</xsl:template>
<xsl:template match="b[2]">
7<xsl:apply-templates/>8
</xsl:template>
so output should be:
1
3
A1
5B16
7B28
4
3
5B36
7B48
4
3
5B56
4
2
Important thing is I want to use normal templates on new structure
So far I have this but it doesn't work (doesn't recognize new structure)
<xsl:template match="/">
1
<xsl:apply-templates select="#*|node()"/>
2
</xsl:template>
<xsl:template match="a">
3
<xsl:for-each select="following-sibling::b[1]">
<xsl:apply-templates select="." mode="aaa"/>
</xsl:for-each>
4
</xsl:template>
<xsl:template match="b"/>
<xsl:template match="b[not(position()=2)]" mode="aaa">
5<xsl:apply-templates/>6
</xsl:template>
<xsl:template match="b[2]" mode="aaa">
7<xsl:apply-templates/>8
</xsl:template>

Merging XSLTs into one flle and calling each template separately matching specific content

I've the below Sample XMLs.
XML1
<root num="1">
<abc></abc>
<cde></cde>
<def></def>
</root>
XML2
<root num="2">
<xyz></xyz>
<cft></cft>
<vft></vft>
</root>
XML3
<root num="3">
<dfg></dfg>
<mnb></mnb>
<gft></gft>
<root>
And i have 3 different XSLTs, each corresponding to XML.
I want to achieve the below.
Make a single XSLT and call the template based on the root number. something like the below.
<xsl:if test="root[#num="1"]>
<!--Call the template matching root 1-->
</xsl:if>
<xsl:if test="root[#num="2"]>
<!--Call the template matching root 2-->
</xsl:if>
<xsl:if test="root[#num="3"]>
<!--Call the template matching root 3-->
</xsl:if>
I just want to put all the XSLTs in a single XSLT, please let me know how can i do this.
Thanks
You can use element.
The element contains rules to apply when a specified node is matched.
The match attribute is used to associate the template with an XML element. The match attribute can also be used to define a template for a whole branch of the XML document (i.e. match="/" defines the whole document).
Note:  is a top-level element.
Example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="cd">
<p>
<xsl:apply-templates select="title"/>
<xsl:apply-templates select="artist"/>
</p>
</xsl:template>
<xsl:template match="title">
Title: <span style="color:#ff0000">
<xsl:value-of select="."/></span>
<br />
</xsl:template>
<xsl:template match="artist">
Artist: <span style="color:#00ff00">
<xsl:value-of select="."/></span>
<br />
</xsl:template>
</xsl:stylesheet>
This was taken from http://www.w3schools.com/xsl/el_template.asp

XSLT: Wrap every 3rd div in a div

I'm looking to get some XSLT (for Umbraco CMS) coded properly and I'm getting kind of stumped. What I'm trying to do is:
Start from a certain node, put each child node into a div; for every 3 children, wrap in a parent div.
Instead of my mess of for-each,choose and when statements, I have tried implementing a apply-template structure but I just can't seem to get the hang of it; so here's my mess of XSLT right now (I'm sure this is bad practice & terrible for performance, but I really don't know what to do at the moment):
<div class="row four">
<h2>Smart Phones see all smart phones →</h2>
<div class="row three"> <!-- This div should be created again for every 3 divs -->
<xsl:for-each select="umbraco.library:GetXmlNodeById('1063')/descendant::*[#isDoc and string(showInMainNavigation) = '1']">
<xsl:choose>
<xsl:when test="position() < 3">
<div class="col">
<a href="{umbraco.library:NiceUrl(./#id)}">
<img class="phonePreviewImg" src="{./previewImage}" style="max-width:117px; max-height:179px;" />
<h4 class="phoneTitle"><xsl:value-of select="./#nodeName" />/h4>
<p class="phonePrice">$<xsl:value-of select="./price" /></p
</a>
</div>
</xsl:when>
<xsl:when test="position() = 3"> <!-- set this div to include class of `omega` -->
<div class="col omega">
<a href="{umbraco.library:NiceUrl(./#id)}">
<img class="phonePreviewImg" src="{./previewImage}" style="max-width:117px; max-height:179px;" />
<h4 class="phoneTitle"><xsl:value-of select="./#nodeName" />/h4>
<p class="phonePrice">$<xsl:value-of select="./price" /></p
</a>
</div>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</div> <!-- End Row Three -->
</div> <!-- End Row Four -->
Obviously this code does not produce the "wrap every three". Can anyone shed some light on what I need to do to accomplish this?
UPDATE - improved the answer
I cannot think of an elegant solution using templates, but this clunky one with a loop works:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="render">
<xsl:param name="node"/>
<xsl:param name="last"/>
<div>
<xsl:if test="$last">
<xsl:attribute name="class">
<xsl:text>omega</xsl:text>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="$node"/>
</div>
</xsl:template>
<xsl:template match="/*">
<root>
<xsl:variable name="nodes" select="*[not(#skip)]"/>
<xsl:for-each select="$nodes">
<xsl:if test="(position() mod 3)=1">
<xsl:variable name="position" select="position()"/>
<div>
<xsl:call-template name="render">
<xsl:with-param name="node" select="."/>
<xsl:with-param name="last" select="false()"/>
</xsl:call-template>
<xsl:if test="$nodes[$position+1]">
<xsl:call-template name="render">
<xsl:with-param name="node" select="$nodes[$position+1]"/>
<xsl:with-param name="last" select="false()"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="$nodes[$position+2]">
<xsl:call-template name="render">
<xsl:with-param name="node" select="$nodes[$position+2]"/>
<xsl:with-param name="last" select="true()"/>
</xsl:call-template>
</xsl:if>
</div>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
applied to:
<root>
<node>1</node>
<node skip="1">to be skipped</node>
<node>2</node>
<node>3</node>
<node skip="1">to be skipped</node>
<node skip="1">to be skipped</node>
<node>4</node>
<node skip="1">to be skipped</node>
<node>5</node>
<node>6</node>
<node>7</node>
<node skip="1">to be skipped</node>
</root>
produces:
<root>
<div>
<div>1</div>
<div>2</div>
<div class="omega">3</div>
</div>
<div>
<div>4</div>
<div>5</div>
<div class="omega">6</div>
</div>
<div>
<div>7</div>
</div>
</root>
You need to replace the select used to set the $nodes variable the XPath selecting the nodes you want, and the render template with the code needed to generate the result you need for each node.
As simple and short as this:
<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="num[position() mod 3 = 1]">
<div>
<xsl:apply-templates mode="inGroup"
select=".|following-sibling::*[not(position() >2)]"/>
</div>
</xsl:template>
<xsl:template match="num" mode="inGroup">
<p><xsl:apply-templates mode="inGroup"/></p>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is produced:
<div>
<p>01</p>
<p>02</p>
<p>03</p>
</div>
<div>
<p>04</p>
<p>05</p>
<p>06</p>
</div>
<div>
<p>07</p>
<p>08</p>
<p>09</p>
</div>
<div>
<p>10</p>
</div>
Here's an elegant solution using templates.
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:param name="pNumInGroup" select="3" />
<xsl:template match="/*">
<html>
<xsl:apply-templates select="*[position() mod $pNumInGroup = 1]" />
</html>
</xsl:template>
<xsl:template match="node">
<div>
<xsl:for-each
select=".|following-sibling::*[not(position() > $pNumInGroup - 1)]">
<div>
<xsl:apply-templates />
</div>
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
...is applied to the sample XML provided by #MiMo:
<root>
<node>1</node>
<node>2</node>
<node>3</node>
<node>4</node>
<node>5</node>
<node>6</node>
<node>7</node>
</root>
...the correct result is produced:
<html>
<div>
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<div>
<div>4</div>
<div>5</div>
<div>6</div>
</div>
<div>
<div>7</div>
</div>
</html>
If the parameter value is changed to 5:
<xsl:param name="pNumInGroup" select="5" />
...the correct result is still produced:
<html>
<div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
<div>
<div>6</div>
<div>7</div>
</div>
</html>
Explanation:
We define a pNumInGroup parameter at the top of the document (with a default value of 3). This is useful, as it allows the XSLT to be used more flexibly (i.e., if you need a different number of <div> elements per group, simply pass them as a parameter).
The first template matches the root node, recreates it, and tells the XSLT processor to apply templates to the first element of each grouping (here's a refresher in modular arithmetic in case you need it).
The second template matches the <node> elements that are selected by the previous template. For each, a new <div> element is created and populated with the remaining items appropriate to that wrapped group.
NOTE: I generally stay away from <xsl:for-each> unless I really need it; in the case of the last template, I don't really need it (I could just as easily have specified the wrapping/secondary <div> logic with another template). However, for the sake of "crispness" and not over-templating the XSLT, I chose this route.

xslt required to transformed elements with 'name' attribute

my xml: The input xml to be transformed
<content>
<conditionalText name="Masked_Account_Number">
<text>
Hi 2<dynamicVariable name="asked_Account"/> is needed.
</text>
</conditionalText>
<dynamicInclude name="xyz" />
</content>
So I have to use above xml,identify all the tags containing 'name' as attribute,extract value of it and create tag 'name' and under it , itshould have 'a' tag
the format is as follows :
transformed output for all tags containing 'name' as their attribute in input xml should be as:
<name>
<a href="#" id="dynamicvariable"
name="value of name attribute"
xmlns="http://www.w3.org/1999/xhtml">[value of name attribute]
</a>
</name>
and only for input xml file's 'dynamicVariable' Tag it should only create 'a' tag as
transformed output for 'dynamicVariable' tag of input xml should look like:
<a href="#" id="dynamicvariable"
name="value of name attribute"
xmlns="http://www.w3.org/1999/xhtml">[value of name attribute]
</a>
so my output xml after transformation should look like
<content1>
<conditionalText>
<text>
Hi 2
<a href="#" id="dynamicvariable"
name="asked_Account"
xmlns="http://www.w3.org/1999/xhtml">[asked_Account]
</a>is needed
</text>
<name>
<a href="#" id="dynamicvariable"
name="Masked_Account_Number"
xmlns="http://www.w3.org/1999/xhtml">[Masked_Account_Number]
</a>
</name>
</conditionalText>
<dynamicInclude>
<name>
<a href="#" id="dynamicvariable" name="xyz"
xmlns="http://www.w3.org/1999/xhtml">[xyz]
</a>
</name>
</dynamicIncude>
</content1>
XSLT tried is given below:
I have took two templates.First template will identify all the tags containing 'name' as their attribute and extract value of it and creates in format as
<name>
<a href="#" id="dynamicvariable"
name="value of name attribute"
xmlns="http://www.w3.org/1999/xhtml">[value of name attribute]
</a>
</name>
And the second template will override the first template and it will identify only tag with name 'dynamicVariable' and extracts its 'name' attribute value and creates tag with format as
<a
href="#" id="dynamicvariable"
name="value of name attribute"
xmlns="http://www.w3.org/1999/xhtml">[value of name attribute]
</a>
so my final xslt is as below:
<?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="content/*">
<content1>
<xsl:apply-templates />
</content1>
</xsl:template>
<xsl:template match= "*/#name" >
<name>
<a href ='#' id="dynamicvariable" xmlns="http://www.w3.org/1999/xhtml" >
<xsl:copy-of select="#*"/>
[<xsl:value-of select="#name"/>]
</a>
</name>
<xsl:for-each select="child::*">
<xsl:if test="name()='text'">
<text>
<xsl:apply-templates />
</text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match= "*[name()='dynamicVariable']" >
<a href ='#' id="dynamicvariable" xmlns="http://www.w3.org/1999/xhtml" >
<xsl:copy-of select="#*"/>
[<xsl:value-of select="#name"/>]
</a>
</xsl:template>
</xsl:stylesheet>
but didnt get the required transformed xml .
Can anyone help me out .
and the transformed xml file having conditionalText element should have then as subchilds in the same
sequence as specified in the transformed xml.
This transformation:
<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()|#*[not(name()='name')]"/>
<xsl:apply-templates select="#name"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/#name">
<name>
<a href="#" id="dynamicvariable"
name="{.}"
xmlns="http://www.w3.org/1999/xhtml">[<xsl:value-of select="."/>]
</a>
</name>
</xsl:template>
<xsl:template match="dynamicVariable">
<a href="#" id="dynamicvariable"
name="{#name}"
xmlns="http://www.w3.org/1999/xhtml">[<xsl:value-of select="#name"/>]
</a>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<content>
<conditionalText name="Masked_Account_Number">
<text>
Hi 2<dynamicVariable name="asked_Account"/> is needed.
</text>
</conditionalText>
<dynamicInclude name="xyz" />
</content>
produces the wanted (the renaming of content is left as an exercise to the reader), correct result:
<content>
<conditionalText>
<text>
Hi 2<a xmlns="http://www.w3.org/1999/xhtml" href="#" id="dynamicvariable" name="asked_Account">[asked_Account]
</a> is needed.
</text>
<name>
<a xmlns="http://www.w3.org/1999/xhtml" href="#" id="dynamicvariable" name="Masked_Account_Number">[Masked_Account_Number]
</a>
</name>
</conditionalText>
<dynamicInclude>
<name>
<a xmlns="http://www.w3.org/1999/xhtml" href="#" id="dynamicvariable" name="xyz">[xyz]
</a>
</name>
</dynamicInclude>
</content>

Sort XML by attribute names using XSLT

Can any one please suggest how to sort an XML by attribute names using XSLT?
For example: my XML is as below
<?xml version="1.0" encoding="UTF-8"?>
<root>
<!-- test 1 -->
<test g="r">
<a g="c" d="e">one</a>
<!-- a k z d b -->
<a k="z" d="b">two</a>
<a s="h" d="5">three</a>
<!-- a a b d 4 -->
<a a="b" d="4">four</a>
<a b="q" d="3">five</a>
<a s="a" d="8">3three</a>
<a x="i" d="2">six</a>
<!-- six 2 a f h i 2 -->
<a f="h" i="2">six</a>
<a l="t" d="1">seven</a>
</test>
<!-- test 2 -->
<test t="b">
<!-- six 2 a z i d 2 -->
<a z="i" d="2">six</a>
<a r="z" d="b">two</a>
<a a="c" d="e">one</a>
<a u="h" d="5">three</a>
<!-- four -->
<a c="b" d="4">four</a>
<a h="q" d="3">five</a>
<a p="t" d="1">seven</a>
</test>
</root>
expected output should be:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<!-- test 1 -->
<test g="r">
<!-- a a b d 4 -->
<a a="b" d="4">four</a>
<a b="q" d="3">five</a>
<a g="c" d="e">one</a>
<!-- six 2 a f h i 2 -->
<a f="h" i="2">six</a>
<!-- a k z d b -->
<a k="z" d="b">two</a>
<a l="t" d="1">seven</a>
<a s="a" d="8">3three</a>
<a s="h" d="5">three</a>
<a x="i" d="2">six</a>
</test>
<!-- test 2 -->
<test t="b">
<a a="c" d="e">one</a>
<!-- four -->
<a c="b" d="4">four</a>
<a h="q" d="3">five</a>
<a p="t" d="1">seven</a>
<a r="z" d="b">two</a>
<a u="h" d="5">three</a>
<!-- six 2 a z i d 2 -->
<a z="i" d="2">six</a>
</test>
</root>
I suspect you might be wanting it to sort on the name of the first attribute. That can't be done, because the order of attributes has no significance in XML, and you can't predict which attribute #*[1] will select.
<xsl:template match="test">
<xsl:apply-templates>
<xsl:sort select="#d"/>
</xsl:apply-templates>
</xsl:template>
will get you the elements under test sorted by attribute d.
You can do multiple sort-levels by adding another xsl:sort.
More about sorting here: http://www.w3.org/TR/xslt#sorting
I found the solution myself.. Below is the line of code to be used for sorting by attribute names.
<xsl:sort select="local-name(#*)"/>
Guys, thanks for all your efforts.
<?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:strip-space elements="*"/>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="#*">
<xsl:sort select="name()"/>
</xsl:apply-templates>
<xsl:apply-templates>
<xsl:sort select="name()"/>
<xsl:sort select= "name(#*)"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="#*">
<xsl:copy />
</xsl:template>
</xsl:stylesheet>