Grouping of similer nodes - xslt-2.0

I have follwing problem in grouping the similer nodes with xsl:
input:
<?xml version="1.0" encoding="UTF-8"?>
<concept id="ads">
<p>para1 content goes here</p>
<Bullet>1</Bullet>
<Bullet>2</Bullet>
<Bullet>3</Bullet>
<p>para2 content goes here</p>
<Bullet>4</Bullet>
<Bullet>5</Bullet>
<p>para2 content goes here</p>
</concept>
Output should be:
<?xml version="1.0" encoding="UTF-8"?>
<concept id="ads">
<p>para1 content goes here</p>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<p>para2 content goes here</p>
<ul>
<li>4</li>
<li>5</li>
</ul>
<p>para2 content goes here</p>
</concept>
All the "Bullet" element which does not have imideate preceding sibling as "Bullet" should wrap up in "UL" and "Li" elements.
I am trying something like this but not able to achive the result:
<xsl:for-each select="Bullet[not(preceding-sibling::Bullet)]">
<ul>
<xsl:copy-of select="."/>
<xsl:variable name="current-name" select="generate-id(.)"/>
<xsl:for-each select="following-sibling::*[Bullet][generate-id(preceding-sibling::*[Bullet]) = $current-name]">
<xsl:copy-of select="."/>
</xsl:for-each>
</ul>
</xsl:for-each>
Please help.
---Related Question ----
This works fine on given xml input, but
1. In my actual xml these <Bullet> tags can appear anywhere under concept node, so grouping with <p> element does not work in that case.
Currently I am processing all the nodes except "Bullet" nodes, so i need to match only <Bullet> nodes which is having immediate following sibling as <Bullet> and wrap the sequence in <ul> and each <Bullet> in <li>. My current context node is <concept>.
Current stylesheet as follows:
nodes which should render at the same place as in input.-->
Actual XML pattern:
<?xml version="1.0" encoding="UTF-8"?>
<concept id="ads">
<p>para1 content goes here</p>
<Body-text>Body1 content goes here</Body-text>
<Bullet>1</Bullet>
<Bullet>2</Bullet>
<Bullet>3</Bullet>
<Body-text>Body2 content goes here</Body-text>
<p>para2 content goes here</p>
<Bullet>4</Bullet>
<Bullet>5</Bullet>
<p>para3 content goes here</p>
<Bullet>6</Bullet>
<Bullet>7</Bullet>
<Body-text>Body2 content goes here</Body-text>
<Bullet>6</Bullet>
<Bullet>7</Bullet>
</concept>
<concept id="ads">
<p>para1 content goes here</p>
<Body-text>Body1 content goes here</Body-text>
<Bullet>1</Bullet>
<Bullet>2</Bullet>
<Bullet>3</Bullet>
<Body-text>Body2 content goes here</Body-text>
<p>para2 content goes here</p>
<Bullet>4</Bullet>
<Bullet>5</Bullet>
<p>para3 content goes here</p>
<Bullet>6</Bullet>
<Bullet>7</Bullet>
<Body-text>Body2 content goes here</Body-text>
<Bullet>6</Bullet>
<Bullet>7</Bullet>
</concept>

This stylesheet:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<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="Bullet[preceding-sibling::node()[1]
[not(self::Bullet)]]">
<ul>
<xsl:call-template name="makeLi"/>
</ul>
<xsl:apply-templates select="following-sibling::node()
[not(self::Bullet)][1]"/>
</xsl:template>
<xsl:template match="Bullet" name="makeLi">
<li>
<xsl:value-of select="."/>
</li>
<xsl:apply-templates select="following-sibling::node()[1]
[self::Bullet]"/>
</xsl:template>
</xsl:stylesheet>
EDIT: Just changing the following applying "first Bullet" rule to first next not Bullet instead of p.
Output (wrapping your input with root element to be wellformed):
<concept id="ads">
<p>para1 content goes here</p>
<Body-text>Body1 content goes here</Body-text>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<Body-text>Body2 content goes here</Body-text>
<p>para2 content goes here</p>
<ul>
<li>4</li>
<li>5</li>
</ul>
<p>para3 content goes here</p>
<ul>
<li>6</li>
<li>7</li>
</ul>
<Body-text>Body2 content goes here</Body-text>
<ul>
<li>6</li>
<li>7</li>
</ul>
</concept>
<concept id="ads">
<p>para1 content goes here</p>
<Body-text>Body1 content goes here</Body-text>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<Body-text>Body2 content goes here</Body-text>
<p>para2 content goes here</p>
<ul>
<li>4</li>
<li>5</li>
</ul>
<p>para3 content goes here</p>
<ul>
<li>6</li>
<li>7</li>
</ul>
<Body-text>Body2 content goes here</Body-text>
<ul>
<li>6</li>
<li>7</li>
</ul>
</concept>
Note: Fine grained traversal.
With grouping, this stylesheet:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="concept">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:for-each-group select="*"
group-adjacent="boolean(self::Bullet)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<ul>
<xsl:apply-templates select="current-group()"/>
</ul>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="Bullet">
<li>
<xsl:value-of select="."/>
</li>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
EDIT: Ussing group-adjacent.

This XSLT 2.0 transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:for-each-group select="*" group-starting-with="p">
<xsl:copy-of select="current-group()[1]"/>
<xsl:if test="current-group()[2]">
<ul>
<xsl:apply-templates select="current-group()[position() gt 1]"/>
</ul>
</xsl:if>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="Bullet">
<li><xsl:value-of select="."/></li>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<concept id="ads">
<p>para1 content goes here</p>
<Bullet>1</Bullet>
<Bullet>2</Bullet>
<Bullet>3</Bullet>
<p>para2 content goes here</p>
<Bullet>4</Bullet>
<Bullet>5</Bullet>
<p>para2 content goes here</p>
</concept>
produces the wanted, correct result:
<concept id="ads">
<p>para1 content goes here</p>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<p>para2 content goes here</p>
<ul>
<li>4</li>
<li>5</li>
</ul>
<p>para2 content goes here</p>
</concept>
Do note: The use of <xsl:for-each-group> with the group-starting-with attribute and the current-group() function.

Related

Need to add 'br' tag after the last <group> element under 'block' element

I want to add 'br' tag after 'group' element under the block element. Basically after the 'group' element in we want break page, that's why we are trying to add break page in output. Below is our input xml structure.
I tried on below XSLT code but unable to get result, please help on this issue:
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<page>
<stream>
<block>
<group>content here</group>
<group>content here</group>
<group>content here</group>
</block>
</stream>
<stream>
<block>
<group>content here</group>
<group>content here</group>
<group>content here</group>
<!-- please add here br tag -->
</block>
</stream>
</page>
XSLT CODE:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="group">
<xsl:element name="p">
<xsl:apply-templates/>
<xsl:if test="position() = last()">
<br></br>
</xsl:if>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?><page>
<stream>
<block>
<p>content here</p>
<p>content here</p>
<p>content here</p>
</block>
</stream>
<stream>
<block>
<p>content here</p>
<p>content here</p>
<p>content here</p>
<!-- please add here br tag -->
</block>
</stream>
</page>
I would match on <xsl:template match="group[last()]"> e.g.
<xsl:template match="group[last()]">
<p>
<xsl:apply-templates/>
</p>
<br/>
</xsl:template>
and for the other groups it seems you want
<xsl:template match="group">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
It is not clear, however, why your verbal description asks to add a br after the last group of a block while your sample has two blocks and you only add the br in the last block of the last stream. So perhaps you want <xsl:template match="stream[last()]/block/group[last()]"> e.g.
<xsl:template match="stream[last()]/block/group[last()]">
<p>
<xsl:apply-templates/>
</p>
<br/>
</xsl:template>

update more than one result in same uri

I want to update the same result document to accumulate all the meta elements. I have tried to recreate the multiple dita-ot templates to explain the problem here. My question is, is it possible to update keyword.xml in the
<xsl:template match="html" mode="pages">
template itself? maybe using xsl:stream or xsl:accumulator? XSLT 3 and Saxon-HE-9.8.0-12
Input XML
<root>
<article>
<html name="firsthtm">
<head>Head1</head>
<meta>keyword;firsthtm</meta>
</html>
<html name="secondhtm">
<head>Head2</head>
<meta>keyword;secondhtm</meta>
</html>
<html name="thirdhtm">
<head>Head3</head>
<meta>keyword;thirdhtm</meta>
</html>
</article>
XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="article">
<xsl:apply-templates mode="pages"/>
</xsl:template>
<xsl:template match="html" mode="pages">
<xsl:result-document href="{#name}.html">
<html>
<title>
<xsl:value-of select="#name"/>
</title>
</html>
</xsl:result-document>
<!-- update keyword.xml for each html -->
<xsl:result-document href="keyword.xml">
<root>
<xsl:copy-of select="meta"/>
</root>
</xsl:result-document>
</xsl:template>
firsthtm.htm
<html>
<title>firsthtm</title>
</html>
secondhtm.htm
<html>
<title>secondhtm</title>
</html>
thirdhtm.htm
<html>
<title>thirdhtm</title>
</html>
keyword.xml
<root>
<meta>keyword;secondhtm</meta>
<meta>keyword;secondhtm</meta>
<meta>keyword;thridhtm</meta>
</root>
Just create the result document in the template matching article:
<xsl:template match="article">
<xsl:apply-templates mode="pages"/>
<xsl:result-document href="keyword.xml">
<root>
<xsl:copy-of select="html/meta"/>
</root>
</xsl:result-document>
</xsl:template>
If you want to use the match="html" mode="pages" then you have to decide on which match you want to construct that result e.g on the first
<xsl:template match="html" mode="pages">
<xsl:result-document href="{#name}.html">
<html>
<title>
<xsl:value-of select="#name"/>
</title>
</html>
</xsl:result-document>
<!-- update keyword.xml for first html -->
<xsl:variable name="html-index" as="xs:integer">
<xsl:number/>
</xsl:variable>
<xsl:if test="$html-index = 1">
<xsl:result-document href="keyword.xml">
<root>
<xsl:copy-of select="ancestor::article/html/meta"/>
</root>
</xsl:result-document>
</xsl:if>
</xsl:template>
In simple cases (there are only those html element children for the article and you have used xsl:strip-space) it might suffice to simply test <xsl:if test="position() = 1">.
A simple solution would be moving the xsl:result-document to the article template and copy all html/meta elements from there:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="article">
<xsl:apply-templates mode="pages"/>
<xsl:result-document href="keyword.xml">
<root>
<xsl:copy-of select="html/meta"/>
</root>
</xsl:result-document>
</xsl:template>
<xsl:template match="html" mode="pages">
<xsl:result-document href="{#name}.html">
<html>
<title>
<xsl:value-of select="#name"/>
</title>
</html>
</xsl:result-document>
<!-- update keyword.xml for each html -->
</xsl:template>
</xsl:stylesheet>

Create a list of element from nested elements in xslt?

I have a document with nested elements.Now I want to list them one element after another element using XSLT 2.0
Here is the input:
<?xml version="1.0" encoding="UTF-8"?>
<a>
<b>
test text
<b>
this text is in b
</b>
</b>
<b>
this text is
<b>
this text is test
</b>
</b>
</a>
This is what I expect:
<a>
<b>test text</b>
<b>this text is in b</b>
<b>this text is </b>
<b>this text is test</b>
</a>
I have no any idea to do this..I tried grouping concept..but it was not succeeded.Please help me to resolve this.
try this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="b[b]">
<xsl:copy>
<xsl:apply-templates select="text()"/>
</xsl:copy>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>
</xsl:stylesheet>

How to implemented nested list in XSLT?

I wanted to add <p> tag for every <li> elements, but there is a special situation when it is a nested list. If there is descendant <ol> tag, this adding <p> must be closed before this <ol> tag. Although it is happened, I could not be able to remove the <s>test tag</s><br/>part repetition.
Please help!
Input:
<a>
<ol>
<li>
<s>test tag</s><br/>
<ol>
<li>list item1</li>
<li>list item2</li>
<li>list item3</li>
</ol>
</li>
</ol>
</a>
Expected output:
<a>
<ol>
<li>
<p><s>test tag</s><br/></p>
<ol>
<p><li>list item1</li></p>
<p><li>list item1</li></p>
<p><li>list item1</li></p>
</ol>
</li>
</ol>
</a>
XSLT code:
<xsl:template match="ol">
<ol>
<xsl:apply-templates/>
</ol>
</xsl:template>
<xsl:template match="li[not(descendant::ol)]">
<li>
<p>
<xsl:apply-templates/>
</p>
</li>
</xsl:template>
<xsl:template match="li[descendant::ol]">
<li>
<p>
<xsl:apply-templates select="node()[parent::li][following-sibling::ol]"/>
</p>
</li>
<xsl:apply-templates select=""/>
</xsl:template>
<xsl:template match="node()[parent::li][following-sibling::ol]"/>
In the case where there is no descendant ol, your desired output has a p element outside each li, whereas your stylesheet is putting a p inside each ol. I don't know which you actually want, but I assume you know how to fix it.
In the case where there is a descendant ol, you have only given one possible example of what you want, and one example does not constitute a specification. However, for that example, the following would work:
<xsl:template match="li[ol]">
<li>
<p>
<xsl:apply-templates select="node()[not(self::ol)]"/>
</p>
<xsl:apply-templates select="ol"/>
</li>
</xsl:template>
If this doesn't solve the problem in the general case, that's because you haven't said what's required in the general case.

Grouping in XSLT 2.0 similar to br to p problems

In XSLT 1.0, a common question in forums was how to convert flat HTML into hierarchical XML, which many times boiled down to nesting text in between <br /> tags in <p> tags.
I have a similar problem, which I think I've partially solved using XSLT 2.0, but it's a new approach to me and I'd like to get a second opinion.
The XHTML source has <span class="pageStart"></span> scattered throughout. They can appear in several different parent nodes. I want to wrap all the nodes between one page start marker and the next in an <page> node. The solution I currently have is:
<xsl:template match="*[child::span[#class='pageStart']]">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:for-each-group select="node()"
group-starting-with="span[#class='pageStart']">
<page>
<xsl:apply-templates select="current-group()"/>
</page>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
There's at least one flaw with this -- the parent node of the marker gets a <page> as a child node when I don't want it. In other works, if there's a <div> that has a child page marker anywhere in it, an <page> node is created as an immediate child of <div> in addition to the locations I expect.
I had hoped that I could simply make the template rule be <xsl:template match="span[#class='pageStart']"> but current-group() seems to be empty no matter what I try. The common sense approach I tried was <xsl:for-each-group select="node()" group-starting-with="span[#class='pageStart']">.
Is there an easier way to solve this problem that I'm missing?
EDIT
Here's an example of the input:
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head></head>
<body>
<span class="pageStart"/>
<p>...</p>
<div>...</div>
<img />
<p></p>
<span class="pageStart"/>
<div>...</div>
<span class="pageStart"/>
<p>...</p>
<div>
<span class="pageStart"/>
<p>...</p>
<p>...</p>
<span class="pageStart"/>
<div>...</div>
<img/>
</div>
</body>
</html>
I assume the last two nested pages make this problem more difficult, so I'd be perfectly happy getting this as the output, or something close:
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head></head>
<body>
<page>
<span class="pageStart"/>
<p>...</p>
<div>...</div>
<img />
<p></p>
</page>
<page>
<span class="pageStart"/>
<div>...</div>
</page>
<page>
<span class="pageStart"/>
<p>...</p>
<div>
<page>
<span class="pageStart"/>
<p>...</p>
<p>...</p>
</page>
<page>
<span class="pageStart"/>
<div>...</div>
<img/>
</page>
</div>
</page>
</body>
</html>
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" 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="*[span/#class='pageStart']">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:for-each-group select="node()"
group-starting-with="span[#class='pageStart']">
<page>
<xsl:apply-templates select="current-group()"/>
</page>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<html>
<head></head>
<body>
<span class="pageStart"/>
<p>...</p>
<div>...</div>
<img />
<p></p>
<span class="pageStart"/>
<div>...</div>
<span class="pageStart"/>
<p>...</p>
<div>
<span class="pageStart"/>
<p>...</p>
<p>...</p>
<span class="pageStart"/>
<div>...</div>
<img/>
</div>
</body>
</html>
produces the wanted, correct result:
<html>
<head/>
<body>
<page>
<span class="pageStart"/>
<p>...</p>
<div>...</div>
<img/>
<p/>
</page>
<page>
<span class="pageStart"/>
<div>...</div>
</page>
<page>
<span class="pageStart"/>
<p>...</p>
<div>
<page>
<span class="pageStart"/>
<p>...</p>
<p>...</p>
</page>
<page>
<span class="pageStart"/>
<div>...</div>
<img/>
</page>
</div>
</page>
</body>
</html>

Resources