Grouping in XSLT 2.0 similar to br to p problems - xslt-2.0

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>

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>

Generate footer page number for each p/#outputclass="kingbreak"

Input Dita file
If any topic has p/#outputclass="kingbreak", we want to create footer page number with horizontal, I tried with xsl:number/ but it is picking disorder page number, Please help
<?xml version="1.0" encoding="utf-8"?>
<dit xmlns:dita-ot="http://dita-ot.sourceforge.net/ns/201007/dita-ot">
<topic>
<title/>
<body>
<p>content here</p>
<p>content here <ph/>content here</p>
<p outputclass="kingbreak"/>
</body>
</topic>
<topic>
<title>SELF title</title>
<body>
<p>body content</p>
</body>
<topic>
<title>content here</title>
<body>
<p>
<b>content here</b>
</p>
</body>
<topic>
<title>content here</title>
<body>
<section>
<p>contenet here</p>
<p outputclass="kingbreak"/>
</section>
</body>
</topic>
<topic>
<title>content here</title>
<body>
<section>
<p>content here</p>
<p outputclass="kingbreak"/>
</section>
</body>
</topic>
<topic>
<title>title content here</title>
<body>
<p>content here</p>
<p outputclass="kingbreak"/>
</body>
</topic>
<topic>
<title>content here</title>
<body>
<section>
<p>content here</p>
<p outputclass="kingbreak"/>
</section>
</body>
</topic>
</topic>
</topic>
</dit>
XSLT
I tried with below xslt, it is generating random page number
<?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"
version="2.0">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[contains(#outputclass, 'kingbreak')]">
<xsl:number/>
<hr style="border-top:1.5pt solid #000000;" />
</xsl:template>
<xsl:stylesheet/>
Please help on this, thanks in advance
Try whether <xsl:number level="any" count="p[contains(#outputclass, 'kingbreak')]"/> gives you the wanted sequence of numbers.

XSLT current-groups() issue

I've been having a bit of trouble with an HTML file that I'm trying to translate. Basically, the relevant part of the source structure as it currently stands is this:
<h2 />
<h3 />
<table />
<table />
<h3 />
<table />
<table />
<h3 />
<table />
<h3 />
<h3 />
<table />
<table />
<h2 />
<h3 />
...
and so on. Each of the contents of these are being translated in different ways, but the problem I'm currently having is in grouping them correctly. Essentially, I want it to end up like the following:
<category>
<h2 />
<container>
<h3 />
<table />
<table />
</container>
<container>
<h3 />
<table />
<table />
</container>
<container>
<h3 />
<table />
</container>
<container>
<h3 />
</container>
<container>
<h3 />
<table />
<table />
</container>
</category>
<category>
<h2 />
<container>
<h3 />
...
to achieve this, I've been using the following code:
<xsl:for-each-group select="node()"group-starting-with="xh:h2">
<category>
<xsl:apply-templates select="xh:h2"/>
<xsl:for-each-group select="current-group()"
group-starting-with="xh:h3">
<container>
<xsl:apply-templates select="current-group()[node()]"/>
</container>
</xsl:for-each-group>
</category>
</xsl:for-each-group>
However, the output I get from this is as follows:
<category>
<h2 />
<container>
<h3 />
<table />
<table />
<h3 />
<table />
<table />
<h3 />
<table />
<h3 />
<h3 />
<table />
<table />
</container>
</category>
<category>
<h2 />
<container>
<h3 />
...
The first for-loop function is working as expected, however the second does not appear to be. If I use <xsl:copy-of> to output the first element in the <current-group> in the second for-loop, it shows the <h2> element, where that element should not even be in the group.
If anyone can point out where I'm going wrong, or offer a better solution, it would be greatly appreciated.
I think you want to change
<xsl:for-each-group select="node()" group-starting-with="xh:h2">
<category>
<xsl:apply-templates select="xh:h2"/>
<xsl:for-each-group select="current-group()"
group-starting-with="xh:h3">
to
<xsl:for-each-group select="*" group-starting-with="xh:h2">
<category>
<xsl:apply-templates select="."/>
<xsl:for-each-group select="current-group() except ."
group-starting-with="xh:h3">
That way the inner for-each-group processes the h3 and table elements but no the h2 element starting the outer group.
If you need more help then consider to post small but complete samples with namespaces present allowing us to reproduce the problem with the undesired output.
I think you've simplified the problem and in doing so have introduced some red herrings.
The xsl:apply-templates select="h2" surely does nothing, because none of the nodes selected in the outer grouping has an h2 child.
In every group selected by the outer for-each-group, except the first, the first node in the group will be an h2 element, by definition. Your inner for-each-group will partition the sequence of nodes starting with an h2 into: first, a group that starts with the h2 (because every node becomes part of some group), and then a sequence of groups each of which starts with an h3. You need to split out the first (non-h3) group and treat it differently, because you don't want to generate a container element in this case. So you need an xsl:choose in the inner for-each-group, typically with the condition xsl:when test="self::h2" to detect that you're processing the special first group.
Having said all that I can't see why you aren't getting a container element for each h3 element. I think this must be caused by something that you haven't shown us (perhaps a namespace issue?)

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.

Render a Form from an XSLT file

I've generated the following XSLT file, and have created a Form that will post to an ASP.Net MVC action called Home/ProcessRequest:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<body>
<xsl:value-of select="Employee/Name"/>
<br />
<xsl:value-of select="Employee/ID"/>
<form method="post" action="/Home/ProcessRequest?id=42">
<input id="Action" name="Action" type="radio" value="Approved"></input> Approved <br />
<input id="Action" name="Action" type="radio" value="Rejected"></input> Rejected <br />
<input type="submit" value="Submit"></input>
</form>
</body>
</html>
Here is my XML File:
<Employee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Russ</Name>
<ID>42</ID>
</Employee>
This works fine the way it is, but I need to change the id parameter in my from from a hard coded integer, to use the ID element from my XML file. Does anyone know how to do this?
This should do it:
<form method="post" action="/Home/ProcessRequest?id={Employee/ID}">
{} is shorthand for using XPath inside attributes.

Resources