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

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>

Related

Keeping track of an iterator through a nested xsl:for-each

I want to take the following input:
<test>
<a>
<b />
<b />
</a>
<a>
<b />
<b />
</a>
</test>
And create the following output using XSLT 2.0:
<items>
<item num="1">
<item num="2"/>
<item num="3"/>
</item>
<item num="4">
<item num="5"/>
<item num="6"/>
</item>
</items>
I know this is wrong, but for a starting point, here's my current XSLT:
<xsl:template match="test">
<items>
<xsl:for-each select="a">
<item num="{position()}">
<xsl:for-each select="b">
<item num="{position()}"/>
</xsl:for-each>
</item>
</xsl:for-each>
</items>
</xsl:template>
This is clearly not the way to do it, because position() only considers elements in the same level. But how would I do this?
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="/*">
<items><xsl:apply-templates/></items>
</xsl:template>
<xsl:template match="a|b">
<xsl:variable name="vNum">
<xsl:number level="any" count="a|b"/>
</xsl:variable>
<item num="{$vNum}">
<xsl:apply-templates/>
</item>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<test>
<a>
<b />
<b />
</a>
<a>
<b />
<b />
</a>
</test>
produces the wanted, correct result:
<items>
<item num="1">
<item num="2"/>
<item num="3"/>
</item>
<item num="4">
<item num="5"/>
<item num="6"/>
</item>
</items>
In XSLT 2, using xsl:number is the right way, if your XSLT processor also supports XSLT 3, then using an accumulator https://www.w3.org/TR/xslt-30/#element-accumulator is an alternative:
<?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="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy" use-accumulators="item-count"/>
<xsl:accumulator name="item-count" as="xs:integer" initial-value="0">
<xsl:accumulator-rule match="test" select="0"/>
<xsl:accumulator-rule match="test/a | test/a/b" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="test">
<items>
<xsl:apply-templates/>
</items>
</xsl:template>
<xsl:template match="a | b">
<item num="{accumulator-before('item-count')}">
<xsl:apply-templates/>
</item>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/3NSSEvn
That would even work with streaming in Saxon EE where xsl:number is not supported:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy" use-accumulators="item-count" streamable="yes"/>
<xsl:accumulator name="item-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="test" select="0"/>
<xsl:accumulator-rule match="test/a | test/a/b" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="test">
<items>
<xsl:apply-templates/>
</items>
</xsl:template>
<xsl:template match="a | b">
<item num="{accumulator-before('item-count')}">
<xsl:apply-templates/>
</item>
</xsl:template>
</xsl:stylesheet>
I figured out one solution: use xsl:number with any level and counting both a and b:
<xsl:template match="test">
<items>
<xsl:for-each select="a">
<item>
<xsl:attribute name="num">
<xsl:number level="any" count="a|b"/>
</xsl:attribute>
<xsl:for-each select="b">
<item>
<xsl:attribute name="num">
<xsl:number level="any" count="a|b"/>
</xsl:attribute>
</item>
</xsl:for-each>
</item>
</xsl:for-each>
</items>
</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>

Getting the value of sibling nodes in XSLT

I have the following XML
<CN>12<CN>
<CT>XYXY</CT>
I need the result AS
<DIV>12 XYXY</DIV>
I'm USING the floowing XSLT but it's not working
<xsl:variable name="x"><xsl:value-of select="CN"/></xsl:variable>
<xsl:template match="CT">
<div class="chap-title"><span><xsl:value-of select="$x"/></span></div>
</xsl:template>
Input:
<input>
<CN>12</CN>
<CT>XYXY</CT>
</input>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"
method="xml"
encoding="UTF-8" />
<xsl:template match="/input">
<DIV>
<xsl:value-of select="CN"/>
<xsl:text> </xsl:text>
<xsl:value-of select="CT"/>
</DIV>
</xsl:template>
</xsl:stylesheet>

Grouping of similer nodes

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.

Resources