Split parent XML element into equivalent number of child element using XSLT - xslt

I am trying to transform an XML and split the main Link element as shown below into equivalent number of child link element: i.e. if link element contains 3 child element I want to have 3 individual link element as shown below
<Link>
<Refrence>abc</Refrence>
<PoolLink>def</PoolLink>
<LinkReference>ghi</LinkReference>
</Link>
to look exactly like this:
<Link>
<Refrence>abc</Refrence>
</Link>
<Link>
<PoolLink>def</PoolLink>
</Link>
<Link>
<LinkReference>ghi</LinkReference>
</Link>
Please help.
Thanks!

In XSLT 3 you could use
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Link">
<xsl:copy-of select="*!snapshot()/.."/>
</xsl:template>

Related

XSL 2.0 output only first duplicate node

I tried searching the threads related to duplicate nodes and was almost able to achieve, it avoids duplicate nodes but it outputs the last duplicate node instead of the first node in the duplicate list (hope this is making sense).
Please advise what I'm doing wrong/missing here ?
=====XML =====
<node id="j0dp1s8s">
<name key="">ABC</name>
<link type="page" target="">
<value>abc/index</value>
</link>
</node>
<node id="j0dp1s8se">
<name key="">DEF</name>
<link type="page" target="">
<value>def/index</value>
</link>
</node>
<node id="j0dp1s92">
<name key="">XYZ</name>
<link type="page" target="">
<value>abc/index</value>
</link>
</node>
=======XSL=============
<xsl:variable name="unique-list" select="link[not(value=following::link/value)]" />
<xsl:for-each select="$unique-list">
<li><xsl:value-of select="../name" /></li>
</xsl:for-each>
Output:
DEF
XYZ
Desired Output:
ABC
DEF
Prolog
Your source XML is not valid. Element name can't be closed by label [deprecated]
I do not show your mistakes, I just provide you a much simpler and working code
I added a root nodes to make your source XML valid
XSLT:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html"/>
<xsl:template match="nodes">
<ul>
<xsl:for-each-group select="node" group-by="link/value">
<li><xsl:value-of select="name" /></li>
</xsl:for-each-group>
</ul>
</xsl:template>
</xsl:transform>
Therefore you can use XSLT 2, take the lovly functionality of it. https://www.w3.org/TR/xslt20/#element-for-each-group

How can I select a node if there is a choice for an element without repeating the complete path

I have the following XSLT template
<xsl:template match="/root">
<r>
<xsl:value-of select="transport/route[not(enddate)]/ship|car/fuel/litres"/>
</r>
</xsl:template>
Which I use to translate the following example XML
<root>
<transport>
<route>
<ship> <!-- this can be a car -->
<fuel>
<litres>
42
</litres>
</fuel>
</ship>
</route>
<route>
<enddate>2015-08-21</enddate>
<car>
<fuel>
<litres>
42
</litres>
</fuel>
</car>
</route>
</transport>
</root>
Notice that under route I can have a ship OR a car.
I can't find a way to reduce the xpath exression to only cover for the choice between ship and car
<xsl:value-of select="transport/route[not(enddate)]/ship|car/fuel/litres"/>
To make the above work I have to change it to this:
<xsl:value-of
select="transport/route[not(enddate)]/ship/fuel/litres |
transport/route[not(enddate)]/car/fuel/litres"/>
but I that feels as I'm copying to much of the expression and violates my DRY nature. I tried transport/route[not(enddate)][car|ship]/fuel/litres but without success.
What should the expression be if I want to get rid of the duplication of the xpath?
One way to write your expression is as follows:
<xsl:value-of select="transport/route[not(enddate)]/*[self::car or self::ship]/fuel/litres"/>
Alternatively, if a route element can contain only only child element (whether ship, car or something else, you could also write this:
<xsl:value-of select="transport/route[not(enddate)][ship or car]/*/fuel/litres"/>
Using an XSLT 2.0 or 3.0 processor you can use <xsl:value-of
select="transport/route[not(enddate)]/(ship | car)/fuel/litres"/> but be aware that value-of with a version="2.0" or version="3.0" stylesheet outputs a sequence of values of the selected sequence and not the first value in the selected sequence like version="1.0" does.

XSLT 2.0: How to replace() part of an element's value and use the result in a select operation?

I've done a bit of XPath in C++ and C# applications, but this is my first time really using it directly in XSLT. I have an XML file that is formatted like this:
<topLevel>
<foo>
<bar>prefix1_Taxi</bar>
...
</foo>
<foo>
<bar>prefix1_SchoolBus</bar>
...
</foo>
...
<foo>
<bar>prefix2_Taxi</bar>
...
</foo>
<foo>
<bar>prefix2_SchoolBus</bar>
...
</foo>
</topLevel>
First, I want to select only the <foo> elements that have a <bar> element that starts with "prefix1_." This appears to work:
<xsl:for-each select="foo[bar[starts-with(., 'prefix1_')]]">
<!-- Style and format the values from child elements of the "prefix1_" <foo> -->
</xsl:for-each>
From inside the for-each block, I then want to select the <foo> element that contains the corresponding "prefix2_" element. I then want to pull data out of each as I see fit.
For example, when the for-each has selected "prefix1_Taxi", I want to then select the foo element that contains the "prefix2_Taxi" bar element.
<xsl:for-each select="foo[bar[starts-with(., 'prefix1_')]]">
<!-- Retrieve the corresponding "prefix2_" foo -->
<!-- Style and format the values from child elements of the "prefix1_" <foo> -->
<!-- Style and format the values from child elements of the "prefix2_" <foo> -->
</xsl:for-each>
Unfortunately, I have no idea how to go about this. In a traditional program I would do something like the following pseudocode:
String buf = barElement.Name.Replace("prefix1_", String.Empty);
XmlNode otherFoo = document.SelectSingleNode("foo[bar[" + buf + "]]");
However XSLT obviously works with a different paradigm for retrieving values and manipulating data, so I'm trying to break out of my old mode of thinking.
Using what I've gathered from some googling on XSLT, I came up with something pretty ugly:
Select the foo element containing a bar that starts with some text:
foo[bar[starts-with(., ...)
Replace the "prefix1_" in our current <bar> element:
replace(<xsl:value-of select='bar'/>, 'prefix1_', '')
This yields a pretty ugly mess:
<xsl:value-of select="foo[bar[starts-with(., replace(<xsl:value-of select='bar'/>, 'prefix1_', ''))]]" />
I'm also pretty sure that the <xsl:value-of> element isn't correct.
How do I go about this? I suspect that I'm missing some core concepts of how to express this concept in XSLT. I'm slogging through the w3.org page on XSLT but I still need much more study and practice.
This XSL stylesheet should give you some flexibility about what you do with the foo elements that contain "prefix2".
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<!-- Matches the top level element and selects the children with prefix1 text. -->
<xsl:template match="topLevel">
<xsl:copy>
<xsl:apply-templates select="foo[bar[starts-with(., 'prefix1_')]]"/>
</xsl:copy>
</xsl:template>
<!-- We're at a foo element with prefix1 text. Select the siblings of the
same vehicle type, with prefix2 text. -->
<xsl:template match="foo[bar[starts-with(., 'prefix1_')]]">
<xsl:variable name="vehicle" select="substring-after(bar, 'prefix1_')"/>
<xsl:apply-templates select="../foo[bar = concat('prefix2_', $vehicle)]"/>
</xsl:template>
<!-- In this template, you can do whatever you want with the foo element containing
the prefix2 vehicles. This example just copies the element and its children. -->
<xsl:template match="foo[bar[starts-with(., 'prefix2_')]]">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
<xsl:for-each select="foo[bar[starts-with(., 'prefix1_')]]">
<xsl:variable name="new-text" as=xs:string" select="replace(bar/text(), '1', '2')" />
<xsl:value-of select="concat(../foo/bar[$new-text]/text(), ' OR DO SOMETHING MORE USEFUL THAN APPENDING TEXT')" />
</xsl:for-each>
You are on the foo element containing the "prefix1" text within the loop, and then change to the other bar element's text and operate on it, whatever you want to do.
Remark: You might need to add xmlns:xs="http://www.w3.org/2001/XMLSchema" to your XSLT root element for the xs:stringin my sample to work. And probably it would also work without the intermediate variable, but I think it makes the code slightly more readable.

XSLT: How to categorize individual elements which are located on the same level

Dear community,
I would like to transform an initial xml which has this format:
<h2>title1</h2>
<div>sometext1</div>
<div>sometext2</div>
<h2>title2</h2>
<div>sometext3</div>
<div>sometext4</div>
<h2>title3</h2>
<div>sometext5</div>
<div>sometext6</div>
into
<cat name="title1">
<div>sometext1</div>
<div>sometext2</div>
</cat>
<cat name="title2">
<div>sometext3</div>
<div>sometext4</div>
</cat>
<cat name="title3">
<div>sometext5</div>
<div>sometext6</div>
</cat>
I have tried to execute a double for-each and create a variable to hold the "select" option to execute the inner for-each, but seems like it is required to use the node-set() function. Even if I try to include it, it does not work. Do you have any thoughts on how to resolve this issue, using XSLT 1.0 and preferrably without using any other namespaces?
Here's one way of doing this that does not depend on nested loops.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="x" match="div" use="preceding-sibling::h2[1]"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()[not(name()='div')]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="h2">
<cat name="{text()}">
<xsl:apply-templates select="key('x',.)"/>
</cat>
</xsl:template>
</xsl:stylesheet>
It first builds an index (xsl:key) that maps each div to its immediately preceding h2. Then we have a simple identity transform that skips the div entries. For each h2 encountered we generate the <cat> and then output the <div...> tags indexed from that h2.

xml creation with xslt (items per page with delta)

I'm using XSLT 1.0 to transform some XML documents. I have a problem to do one thing, in that I would need some help please:
I have a set of items in my XML document something like :
<item1>...</item1>
<item2>...</item2>
<!-- ... -->
<itemN>...</itemN>
I want to create something like this based on delta (items per page), for example delta is three:
<page>
<item1>...</item1>
<item2>...</item2>
<item3>...</item3>
</page>
<page>
<item4>...</item4>
<item5>...</item5>
<item6>...</item6>
</page>
So, basically to create a XML structure in XSLT that will put fixed number of items per page.
If tried something like this (in order to use node-set):
<xsl:variable name="all_items_per_delta">
<xsl:for-each select="item">
<!-- if position is one or if mod of 3 then insert a page node,
so for first contract and for 3th, 6th, 9th... item -->
<xsl:if test="position() = 1">
<page>
</xsl:if>
<!-- in the meantime for every contract insert the contract node -->
<item>
<!-- ... -->
</item>
<!-- if position is one or if mod of 3 then insert a page node,
so for first item and for 3th, 6th, 9th... item-->
<xsl:if test="( (position() mod 3) = 0) ) and ( position() != last() )">
</page>
<page>
</xsl:if>
<!-- if the position is last just end the page element -->
<xsl:if test="position() = last()"> `
</page>
</xsl:if>
</xsl:for-each>
</xsl:variable>
But this doesn't work because the xslt parser says
'Expected end of tag 'page'
and this is for the first <page> tag I want to add. It seems that I have to end the page tab </page> in the same line. With <xslt:if> it doesn't work unfortunately.
Please advise, how can I make this kind of tree fragment structure in order to later extract the node-set using EXSL extension.
Thank you.
The reason why it doesn't work is because XSLT must itself be well-formed XML; and e.g. this:
<xsl:if test="...">
</page>
</xsl:if>
isn't well-formed XML. Simply put, you can't have an opening tag inside one construct, and closing one inside another.
Now as to how to handle this properly. One simple way is to take every _n_th element in the outer loop, and generate <page> element for it; then put that element, and the following n-1 elements, inside that <page>:
<xsl:for-each select="item[position() mod $n = 0]">
<page>
<xsl:copy-of select=". | following-sibling::item[position() < $n)]">
</page>
</xsl:for-each>
Alternatively, you might want to use Muenchean method, grouping items by position() mod $n.
I used Pavel's answer, slightly altered, for pagination:
<xsl:param name="itemsPerPage" select="10"/>
<xsl:for-each select="item[position() mod $itemsPerPage = 1 or position() = 1]">
<page>
<xsl:for-each select=". | following-sibling::item[position() < $itemsPerPage]" >
<!-- code to print out items -->
</xsl:for-each>
</page>
</xsl:for-each>
I think you need the or position() = 1 to include the first items in the list. Also, note the mod $itemsPerPage = 1. For example, if you want to have 10 items per page, you want to select items 1, 11, 21, etc in the first for-each loop.
This requires an approach to grouping in XSLT. XSLT 1.0 provides little support and it needed the genius of the Munchean method to solve it. XSLT 2.0 has more support (but not all environments, e.g. some MS environments) support XSLT2.0.
Some resources (from helpfule XML experts) are:
http://www.jenitennison.com/xslt/grouping/
http://www.dpawson.co.uk/xsl/sect2/N4486.html
http://www.xml.com/pub/a/2003/11/05/tr.html