How to convert flat xml data to hierarchical data xml 2 - xslt

Following my question XSL change structure of ODT XML file, I cannot work out the solution given in this thread How to convert flat xml data to hierarchical data xml in my case.
I slightly changed the input xml (h instead of p for titles) :
<body>
<h class="head1">1: Heading level 1</h>
<p>some text here</p>
<p>some text here</p>
<h class="head2">1.1: Heading level 2</h>
<p>some text here</p>
<p>some text here</p>
<h class="head3">1.1.1: Heading level 3</h>
<p>some text here</p>
<p>some text here</p>
<h class="head1">2: Heading level 1</h>
<h class="head2">2.1: Heading level 2</h>
<p>some text here</p>
<p>some text here</p>
<h class="head3">2.1.1: Heading level 3</h>
<p>some text here</p>
<p>some text here</p>
<h class="head3">2.1.2: Heading level 3</h>
<p>some text here</p>
<p>some text here</p>
</body>
And i don't know how to adapt the following Xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf" version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:function name="mf:group" as="element(section)*">
<xsl:param name="entries" as="element(p)*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$entries"
group-starting-with="p[#class = concat('head',$level)]">
<xsl:variable name="P_ID" select="generate-id(.)"/>
<section name="{#class}">
<title>
<xsl:value-of select="."/>
</title>
<xsl:if test="following-sibling::p[1][not(#class)]">
<ps>
<xsl:apply-templates
select="following-sibling::p[not(#class)][generate-id(preceding-sibling::p[#class][1]) = $P_ID]"
/>
</ps>
</xsl:if>
<xsl:sequence select="mf:group(current-group() except ., ($level + 1))"/>
</section>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="body">
<xsl:copy>
<xsl:sequence select="mf:group(p[contains(#class,'head')], 1)"/>
</xsl:copy>
</xsl:template>
To get this result
<body>
<section name="head1">
<title>1: Heading level 1</title>
<ps>
<p>some text here</p>
<p>some text here</p>
</ps>
<section name="head2">
<title>1.1: Heading level 2</title>
<ps>
<p>some text here</p>
<p>some text here</p>
</ps>
<section name="head3">
<title>1.1.1: Heading level 3</title>
<ps>
<p>some text here</p>
<p>some text here</p>
</ps>
</section>
</section>
</section>
<section name="head1">
<title>2: Heading level 1</title>
<section name="head2">
<title>2.1: Heading level 2</title>
<ps>
<p>some text here</p>
<p>some text here</p>
</ps>
<section name="head3">
<title>2.1.1: Heading level 3</title>
<ps>
<p>some text here</p>
<p>some text here</p>
</ps>
</section>
<section name="head3">
<title>2.1.2: Heading level 3</title>
<ps>
<p>some text here</p>
<p>some text here</p>
</ps>
</section>
</section>
</section>
</body>

An adaption and simplification of my answer in the referenced previous question would be
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:function name="mf:group" as="element()*">
<xsl:param name="elements" as="element()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$elements" group-starting-with="h[#class = concat('head', $level)]">
<xsl:choose>
<xsl:when test="not(self::h[#class = concat('head', $level)])">
<xsl:where-populated>
<ps>
<xsl:apply-templates select="current-group()"/>
</ps>
</xsl:where-populated>
</xsl:when>
<xsl:otherwise>
<section name="{#class}">
<title>
<xsl:apply-templates select="node()"/>
</title>
<xsl:sequence select="mf:group(current-group() except ., ($level + 1))"/>
</section>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="body">
<xsl:copy>
<xsl:sequence select="mf:group(*, 1)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jxDjimV
Uses XSLT 3 xsl:mode to declare the identity transformation as the base transformation but you could spell that out as a template for an XSLT 2 processor. And the xsl:where-populated is also XSLT 3 but could be replaced with a more specific test in the xsl:when test" if needed.

Related

Find the nodes which are coming in continuety and then add a parent tag to them xslt1.0

I have a xml like below:
<root>
<p>some text</p>
<ol>
<li>
link
</li>
</ol>
<li>tag with parent as root</li>
<li>another li tag with parent as root</li>
<li>another li tag with parent as root</li>
<p>some more text</p>
<li>some text in li.</li>
<li>another li tag with parent as root</li>
<p>some more text</p>
<li>some text in li.</li>
http://cowherd.com
</root>
Desired output: I want to add a parent to the all li tags which don't have any parent and also I want to add the li tags into a common parent if there are more than one li tags coming in continuety.
<root>
<p>some text</p>
<ol>
<li>
link
</li>
</ol>
<ul>
<li>tag with parent as root</li>
<li>another li tag with parent as root</li>
<li>another li tag with parent as root</li>
</ul>
<p>some more text</p>
<ul>
<li>some text in li.</li>
<li>another li tag with parent as root</li>
</ul>
<p>some more text</p>
<ul>
<li>some text in li.</li>
</ul>
</root>
I tried various things but doesn't work. Thanks in advance
<xsl:template match="li[not(parent::ol) and not(parent::ul)]">
<ul>
<xsl:copy-of select="."/>
</ul>
</xsl:template>
<xsl:template match="li[not(parent::ol) and not(parent::ul)]">
<xsl:for-each select="root/li">
<ul><xsl:value-of select="."/></ul>
</xsl:for-each>
</xsl:template>
<xsl:template match="li[not(parent::ol) and not(parent::ul)]">
<ul><xsl:apply-templates/></ul>
</xsl:template>
This is doable in XSLT 1 with a key, a rather convoluted one, but doing the job:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="li-group" match="*[not(self::ol | self::ul)]/li[preceding-sibling::*[1][self::li]]" use="generate-id(preceding-sibling::li[not(preceding-sibling::*[1][self::li])][1])"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(self::ol | self::ul)]/li[not(preceding-sibling::*[1][self::li])]">
<ul>
<xsl:copy-of select=". | key('li-group', generate-id())"/>
</ul>
</xsl:template>
<xsl:template match="*[not(self::ol | self::ul)]/li[preceding-sibling::*[1][self::li]]"/>
</xsl:stylesheet>
Other options are sibling recursion.

How do I add .01 to the end of a number using XSLT?

I need to add .01 to the entry in this line of code in my XSLT script <xsl:apply-templates select="descendant::xhtml:span[#property = 'atom:content-item-name']"/>. This number appears twice in my output. I only need the .01 on the second entry. How would I do that? I guess it would be in effect a counter because if there are 2 questions I'd need it to number as .02, etc.
This is the full XSLT script:
<?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"
xmlns:math="http://exslt.org/math"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:mml="http://www.w3.org/1998/Math/MathML"
xmlns="http://www.w3.org/1999/xhtml"
exclude-result-prefixes="xs math xd xhtml mml"
version="3.0">
<xsl:output encoding="UTF-8" include-content-type="no" indent="no" method="xhtml" omit-xml-declaration="yes"/>
<!-- attributes, commments, processing instructions, text: copy as is -->
<xsl:template match="#*|comment()|processing-instruction()|text()">
<xsl:copy-of select="."/>
</xsl:template>
<!-- elements: create a new element with the same name, but no namespace -->
<xsl:template match="*">
<xsl:param name="content-item-name"/>
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="content-item-name" select="$content-item-name"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<!-- process root -->
<xsl:template match="xhtml:html">
<xsl:text disable-output-escaping="yes"><!DOCTYPE html></xsl:text>
<xsl:sequence select="'
'"/>
<html>
<xsl:apply-templates select="#* | node()"/>
</html>
</xsl:template>
<!-- process item, pass content item name variable -->
<xsl:template match="xhtml:li[#property='ktp:question']">
<li>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="content-item-name" select="descendant::xhtml:span[#property='atom:content-item-name']/#data-value"/>
</xsl:apply-templates>
</li>
</xsl:template>
<!-- branching point for any interaction type-based updates -->
<xsl:template match="xhtml:li[#property='ktp:question']">
<li>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="interactionType" select="//xhtml:span[#property='ktp:interactionType']"/>
</xsl:apply-templates>
</li>
</xsl:template>
<!-- Change content -->
<xsl:template match="xhtml:ol[#class='ktp-question-set']">
<xsl:variable name="content-item-name" select="xhtml:span[#property='atom:content-item-name']/#data-value"/>
<xsl:variable name="feedbackPara"
select="xhtml:section[#property = 'ktp:stimulus']"/>
<ol property="ktp:questionSet" typeof="ktp:QuestionSet" class="ktp-question-set">
<li class="ktp-question-set-meta">
<section property="ktp:metadata" class="ktp-meta">
<xsl:apply-templates
select="descendant::xhtml:span[#property = 'atom:content-item-name']"/>
</section>
<section property="ktp:tags" class="ktp-meta">
<span class="ktp-meta" property="ktp:questionSetType">shared-stimulus</span>
</section>
</li>
<li class="ktp-stimulus" typeof="ktp:Stimulus" property="ktp:stimulus">
<xsl:apply-templates
select="descendant::xhtml:section[#property = 'ktp:stimulus']"/>
<p class="place-top atom-exclude">Stimulus End: Place content above this line</p>
</li>
<li class="ktp-question" typeof="ktp:Question" property="ktp:question">
<section class="ktp-question-meta">
<section property="ktp:metadata" class="ktp-meta">
<xsl:apply-templates
select="descendant::xhtml:span[#property = 'atom:content-item-name']"/>
</section>
<xsl:apply-templates
select="descendant::xhtml:section[#property = 'ktp:tags']"/>
</section>
<xsl:apply-templates
select="descendant::xhtml:section[#class = 'ktp-question-stem']"/>
<xsl:apply-templates
select="descendant::xhtml:ol[#class = 'ktp-answer-set']"/>
<xsl:apply-templates
select="descendant::xhtml:section[#property = 'ktp:explanation']"/>
</li>
</ol>
</xsl:template>
</xsl:stylesheet>
This is the input section where I need to pull the data-value from:
<section class="ktp-question-meta">
<section property="ktp:metadata" class="ktp-meta"><span property="atom:content-item-name" class="ktp-meta" data-value="lsac820402"></span></section>
<section property="ktp:tags" class="ktp-meta">
<span property="ktp:interactionType" class="ktp-meta">single-select</span>
<span property="ktp:questionType" class="ktp-meta">Assumption (Sufficient)</span>
<span property="ktp:difficulty" class="ktp-meta">★</span>
</section>
This is how I need that section to appear with the .01 added to that data-value:
<section class="ktp-question-meta">
<section property="ktp:metadata" class="ktp-meta">
<span property="atom:content-item-name" class="ktp-meta" data-value="lsac820402.01"></span>
</section>

Flatten XML file with XML changing the labels according to their nesting position

I am new to XSL and I am trying to flatten an XML file with the following structure with the objective of using it in InDesign (the real XML structure is a lot more complex and actually follows the NLM schema but the example below should work to illustrate what I need):
Ex.
<section>
<p> just normal text here 1</p>
<section>
<p>just normal text for this section</p>
</section>
<p>just normal text here 2</p>
<section>
<p>just more text</p>
<section>
<p>this is the text in the deepest section </p>
</section>
<p>and even more text </p>
</section>
<p>just normal text here 3</p>
</section>
I have almost accomplished what I needed with the following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="section">
<xsl:variable name="sectionlevel">
<xsl:value-of select="count(ancestor::section)" />
</xsl:variable>
<xsl:element name="s{$sectionlevel}">
<xsl:apply-templates select="descendant::section" />
<xsl:copy-of select="*[local-name() != 'section']"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The output I get with this code is the one shown below which would be fine but the problem is that I need to keep the order of the elements. I only need to change the section element names and keep everything else the same:
<?xml version="1.0" encoding="utf-8"?>
<s0>
<s1>
<p> just normal text for this section</p>
</s1>
<s1>
<s2>
<p>this is the text in the deepest section </p>
</s2>
<p>just more text</p>
<p>and even more text </p>
</s1>
<s2>
<p>this is the text in the deepest section </p>
</s2>
<p> just normal text here 1</p>
<p>just normal text here 2</p>
<p>just normal text here 3</p>
</s0>
As you can see the elements in this example are moved to the end inside the section element. How should I code the XSL transformation so that if keeps all the original XML order and structure and just changes the section labels?
Is this what you expect?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- Identity to copy all elements -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="section" >
<xsl:element name="s{count(ancestor::section)}">
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This would output:
<?xml version="1.0" encoding="UTF-8"?>
<s0>
<p> just normal text here 1</p>
<s1>
<p>just normal text for this section</p>
</s1>
<p>just normal text here 2</p>
<s1>
<p>just more text</p>
<s2>
<p>this is the text in the deepest section </p>
</s2>
<p>and even more text </p>
</s1>
<p>just normal text here 3</p>
</s0>

Positional grouping: xslt conversion of html

I have the following html file and i want to run a transformation so that all the h1,h2,h3 tags will converted into corresponding divs. h2 will always be a nested div of h1 and if there are 2 h2 tags then it should have there own divs. same way h3 will always be a nested div of h2.
<body>
<p> this is a text</p>
click here
<h3>this is heading 3</h3>
<p>text for heading 3</p>
<h1>
heading 1
</h1>
this is a text for heading 1
This is a link
<h2>
this is heading 2
</h2>
this is a text for heading 2
<h2>
this is heading 2 again
</h2>
this is a text for heading 2 again
</body>
"
The output of above should be like :
<body>
<p> this is a text</p>
click here
<div>
<heading>this is heading 3</heading>
<p>text for heading 3</p>
<div>
<div>
<heading>
heading 1
</heading>
this is a text for heading 1
This is a link
<div>
<heading>
this is heading 2
</heading>
this is a text for heading 2
</div>
<div>
<heading>
this is heading 2 again
</heading>
this is a text for heading 2 again
</div>
</div>
</body>
Any help will be appreciated. Currenlty i have done this in asp.net but want to convert this into xslt.
Here is an XSLT 2.0 stylesheet:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:param name="max-level" as="xs:integer"/>
<xsl:choose>
<xsl:when test="$level le $max-level">
<xsl:for-each-group select="$nodes" group-starting-with="*[local-name() eq concat('h', $level)]">
<xsl:choose>
<xsl:when test="self::*[local-name() eq concat('h', $level)]">
<div>
<xsl:apply-templates select="."/>
<xsl:sequence select="mf:group(current-group() except ., $level + 1, $max-level)"/>
</div>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$nodes"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[h1]">
<xsl:copy>
<xsl:sequence select="mf:group(node(), 1, 3)"/>
</xsl:copy>
</xsl:template>
<xsl:template match="h1 | h2 | h3">
<heading>
<xsl:apply-templates/>
</heading>
</xsl:template>
</xsl:stylesheet>
When applied with Saxon 9.3 to the input
<body>
<h1>
heading 1
</h1>
this is a text for heading 1
This is a link
<h2>
this is heading 2
</h2>
this is a text for heading 2
<h2>
this is heading 2 again
</h2>
this is a text for heading 2 again
</body>
I get the following output
<body>
<div>
<heading>
heading 1
</heading>
this is a text for heading 1
This is a link
<div>
<heading>
this is heading 2
</heading>
this is a text for heading 2
</div>
<div>
<heading>
this is heading 2 again
</heading>
this is a text for heading 2 again
</div>
</div>
</body>
I haven't tested the XSLT with any other more complex input so test yourself and report back if you encounter any problems.

XSLT wrapping node text around child node value

So I have an xml file containing
...
<chapter>
<para>This line has a quote <quote id="one"/>. Here is some more text.</para>
<para>This also has a quote <quote id="two"/>. Here is some more text.</para>
</chapter>
<references>
<source id="one">
<author>Author 1</author>
<title>Title 1</title>
<year>2001</year>
</source>
<source id="two">
<author>Author 2</author>
<title>Title 2</title>
<year>2002</year>
</source>
</references>
...
I would like to output an xhtml
...
<p>This line has a quote <a href="#one>[1]</a>. Here is some more text.</p>
<p>This also has a quote <a href="#two>[2]</a>. Here is some more text.</p>
<h3>References</h3>
<ol>
<li><a name="one">Author 1, Title 1, 2001</a></li>
<li><a name="two">Author 2, Title 2, 2002</a></li>
</ol>
...
So what I want is a quote inside text with a link to an item in the references list.
I would also like for references to be ordered as they appear in text.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="quote">
<a href="#{#id}">
<xsl:text>[</xsl:text>
<xsl:number count="quote" level="any" />
<xsl:text>]</xsl:text>
</a>
</xsl:template>
<xsl:template match="references">
<h3>References</h3>
<ol>
<xsl:apply-templates/>
</ol>
</xsl:template>
<xsl:template match="source">
<li>
<a name="{#id}">
<xsl:apply-templates/>
</a>
</li>
</xsl:template>
<xsl:template match="author|title">
<xsl:value-of select="."/>
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template match="year">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>