how can I represent nested element of xml with xslt? - xslt

This is the xml data I need to generate xslt from.
<root>
<entry id="1">
<headword>go</headword>
<example>I <hw>go</hw> to school.</example>
</entry>
<entry id="2">
<headword>come</headword>
<example>I <verb>came</verb> back home.</example>
</entry>
I want to create an html like this:
<html>
<body>
<div class="entry" id="1">
<span class="headword">go</span>
<span class="example">I <span class="hw">go</span> to school.</span>
</div>
<div class="entry" id="2">
<span class="headword">comeo</span>
<span class="example">I <span class="hw">came</span> back home.</span>
</div>
</body>
</html>
This is my xslt:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="root/entry">
<div class="entry">
<span class="headword">
<xsl:value-of select="headword"/>
</span>
<span class="example">
<xsl:value-of select="example"/>
</span>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I don't know how to convert the value of the attribute "id," and the element "hw."

Please give this a try. I'm assuming the second class="hw" in your sample output was a typo and was supposed to be class="verb" since that's the only possibility that makes sense:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="root/entry" />
</body>
</html>
</xsl:template>
<xsl:template match="entry">
<div class="entry" id="{#id}">
<xsl:apply-templates select="*" mode="entryContents" />
</div>
</xsl:template>
<xsl:template match="*" mode="entryContents">
<span class="{local-name()}">
<xsl:apply-templates select="node()" mode="entryContents" />
</span>
</xsl:template>
</xsl:stylesheet>

Related

How does parent tag Attributes in the "apply templete" when it is in "group by"

Here is the Sample XML which I am using
XML.xml
<root>
<main>
<docum year="2022" month="2022.01">1</docum>
<docum year="2021" month="2021.12">2</docum>
<docum year="2020" month="2020.11">3</docum>
<docum year="2020" month="2020.12">4</docum>
<docum year="2022" month="2022.12">5</docum>
</main>
</root>
Need a navigator HTML which is toc.htm and 1.htm, 2.htm, 3.htm
toc.htm
<div>
<div>2022_2022.01</div>
<div>2022_2022.12</div>
<div>2022_2020.11</div>
</div>
1.htm
<div>
<h1>2022</h1>
<h2>2022.01</h2>
1
</div>
<div>
<h1>2022</h1>
<h2>2022.12</h2>
5
</div>
2.htm
<div>
<h1>2021</h1>
<h2>2021.12</h2>
2
</div>
3.htm
<div>
<h1>2020</h1>
<h2>2020.11</h2>
3
</div>
<div>
<h1>2020</h1>
<h2>2020.11</h2>
4
</div>
this is my XSL which I am trying but not sure about how we get parent div attributes value in the apply template in the current group().
XSL.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:template match="root">
<xsl:apply-templates select="main" mode="toc" />
<xsl:apply-templates select="main" mode="chapter" />
</xsl:template>
<xsl:template match="root/main" mode="toc">
<xsl:result-document href="toc.htm" method="html">
<html>
<body>
<xsl:for-each select="docum">
<xsl:element name="div">
<xsl:attribute name="class"></xsl:attribute>
<xsl:attribute name="id">
<xsl:value-of select="#year" />
</xsl:attribute>
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="lower-case(concat('id_', generate-id(), '.htm'))" />
</xsl:attribute>
<xsl:value-of select="#year" /> _<xsl:value-of select="#month" />
</xsl:element>
</xsl:element>
</xsl:for-each>
</body>
</html>
</xsl:result-document>
</xsl:template>
<xsl:template match="root/main" mode="chapter">
<xsl:for-each-group select="docum" group-by="#month">
<xsl:result-document href="{lower-case(concat('id_', generate-id()))}.htm" method="HTML">
<html>
<body>
<h1><xsl:value-of select="#year" /></h1>
<h2><xsl:value-of select="#month" /></h2>
<xsl:apply-templates select="current-group()" />
</body>
</html>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I think you want something along the following lines:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:template match="root/main">
<xsl:result-document href="toc.html">
<html>
<head>
<title>TOC</title>
</head>
<body>
<xsl:for-each-group select="docum" group-by="#year">
<div id="g-{current-grouping-key()}">
{let $sorted-months := sort(current-group()/#month, (), function($m) { $m }) return $sorted-months[1] || ('-' || $sorted-months[last()])[$sorted-months[2]]}
</div>
<xsl:result-document href="year-{current-grouping-key()}.html">
<html>
<head>
<title>{current-grouping-key()}</title>
</head>
<body>
<xsl:apply-templates select="current-group()"/>
</body>
</html>
</xsl:result-document>
</xsl:for-each-group>
</body>
</html>
</xsl:result-document>
</xsl:template>
<xsl:template match="docum">
<div>
<h1>{#year}</h1>
<h2>{#month}</h2>
{.}
</div>
</xsl:template>
</xsl:stylesheet>
This creates toc.html and for each year a year-yyyy.html which I think then has the contents you want, i.e. each month belonging to that year.

xsl:for-each-group not working as per exception

I have to transform XML tag out of p as separate div but i am unable to do it:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<p>
This is <bold>First</bold> paragraph
<boxed-text>
<sec>
<title>Boxed title</title>
<p>
<list list-type="bullet">
<list-item>
<p>List first para</p>
</list-item>
</list>
</p>
</sec>
</boxed-text>
This is <bold>Second</bold> paragraph
</p>
</root>
XSLT:
<?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:output method="xhtml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<html>
<head>
<title>Title</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="p">
<xsl:choose>
<xsl:when test="descendant::boxed-text">
<xsl:for-each-group select="node()" group-starting-with="node()">
<xsl:choose>
<xsl:when test="self::boxed-text">
<div class="boxed-text">
<xsl:apply-templates select="current-group()"/>
</div>
</xsl:when>
<xsl:otherwise>
<p class="indent">
<xsl:apply-templates select="current-group()"/>
</p>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:when>
<xsl:otherwise>
<p class="indent">
<xsl:apply-templates/>
</p>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="boxed-text">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="p[table-wrap | list]">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="list">
<xsl:choose>
<xsl:when test="#list-type[. = 'bullet']">
<ol style="list-style-type:disc;">
<xsl:apply-templates/>
</ol>
</xsl:when>
<xsl:otherwise>
<ol style="list-style-type:none;">
<xsl:apply-templates/>
</ol>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="list-item">
<li>
<xsl:apply-templates/>
</li>
</xsl:template>
</xsl:stylesheet>
Current Output:
<?xml version="1.0" encoding="UTF-8"?><html>
<head>
<title>Title</title>
</head>
<body>
<p class="indent">
This is
</p>
<p class="indent">First</p>
<p class="indent"> paragraph
</p>
<div class="boxed-text">Boxed title
<ol style="list-style-type:disc;">
<li>
<p class="indent">List first para</p>
</li>
</ol>
</div>
<p class="indent">
This is
</p>
<p class="indent">Second</p>
<p class="indent"> paragraph
</p>
</body>
</html>
Excepted output:
<?xml version="1.0" encoding="UTF-8"?><html>
<head>
<title>Title</title>
</head>
<body>
<p class="indent">This is <b>First</b> paragraph</p>
<div class="boxed-text">Boxed title
<ol style="list-style-type:disc;">
<li>
<p class="indent">List first para</p>
</li>
</ol>
</div>
<p class="indent">This is <b>Second</b> paragraph</p>
</body>
</html>
It seems this is rather a job for group-adjacent="boolean(self::boxed-text)":
<xsl:template match="p[descendant::boxed-text]">
<xsl:for-each-group select="node()" group-adjacent="boolean(self::boxed-text)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<p class="indent">
<xsl:apply-templates select="current-group()"/>
</p>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
Complete adaption is
<?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:output method="html" indent="no" html-version="5"/>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="p">
<p class="indent">
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="p[descendant::boxed-text]">
<xsl:for-each-group select="node()" group-adjacent="boolean(self::boxed-text)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<p class="indent">
<xsl:apply-templates select="current-group()"/>
</p>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="boxed-text">
<div class="boxed-text">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="p[table-wrap | list]">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="list">
<ol style="list-style-type:none;">
<xsl:apply-templates/>
</ol>
</xsl:template>
<xsl:template match="list[#list-type[. = 'bullet']]">
<ol style="list-style-type:disc;">
<xsl:apply-templates/>
</ol>
</xsl:template>
<xsl:template match="list-item">
<li>
<xsl:apply-templates/>
</li>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6rewNyg

XPath: Appending text only to the last text() occurrence (regardless of how deeply nested) while copying all XML.

Using XSLT on an XML file, my goal is to copy all XML while appending a snippet of text ("(PDF)" in this example) to only the last bit of text present in a particular tag regardless of how deeply nested that next is. I've managed to take care of most of the edge cases and gotten it close, but there's still one instance that's giving me trouble. I'm sure there's also a way to do this more efficiently so any tips are appreciated.
XML
<links>
This is a PDF file
<a href="something.PDF">
<span>
<b>This</b> is a PDF file
</span>
</a>
<a href="something.pdF">
<div>
<span>
This is a PDF file
</span>
</div>
</a>
<a href="something.pdf">
<div class="something">
<span>
This is a <i>PDF</i> file
</span>
</div>
</a>
<a href="something.pDf">
<div class="something">
<div>
<div>
Test Text
<div>
This is a <i>PDF</i>
</div>
</div>
</div>
</div>
</a>
</links>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="xml"/>
<xsl:strip-space elements="*" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:param name="pdf-append" select="'(PDF)'"/>
<xsl:template match="a['.pdf' = substring(translate(#href,'PDF','pdf'), string-length(#href) - 3)]/text()">
<xsl:value-of select="concat(current(),' ', $pdf-append)"/>
</xsl:template>
<xsl:template match="a['.pdf' = substring(translate(#href,'PDF','pdf'), string-length(#href) - 3)]//node()[last()]/text()[last()]">
<xsl:value-of select="concat(current(),' ', $pdf-append)"/>
</xsl:template>
</xsl:stylesheet>
Current Result
<links>
This is a PDF file (PDF)
<a href="something.PDF">
<span>
<b>This</b> is a PDF file
(PDF)</span>
</a>
<a href="something.pdF">
<div>
<span>
This is a PDF file
(PDF)</span>
</div>
</a>
<a href="something.pdf">
<div class="something">
<span>
This is a <i>PDF</i> file
(PDF)</span>
</div>
</a>
<a href="something.pDf">
<div class="something">
<div>
<div>
Test Text
(PDF)<div>
This is a (PDF)<i>PDF (PDF)</i>
</div>
</div>
</div>
</div>
</a>
</links>
The last <a> is the problem: ideally "(PDF)" would only appear within the the last <i> tag (e.g. This is a <i>PDF (PDF)</i>). So the questions is: how can I fix that last instance?
Thanks.
How about:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pdf-append" select="' (PDF)'"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="."/>
<xsl:variable name="a" select="ancestor::a" />
<xsl:variable name="href" select="$a/#href" />
<xsl:variable name="extension" select="substring(translate($href,'PDF','pdf'), string-length($href) - 3)" />
<xsl:variable name="last-text" select="$a/descendant::text()[last()]" />
<xsl:if test="$extension='.pdf' and generate-id()=generate-id($last-text) ">
<xsl:value-of select="$pdf-append"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

How can i output this html with xsl( I want to exit an element)

I have this xsl bellow :
<xsl:template match="/" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
<div id="vtab">
<ul>
<xsl:call-template name="dvt_1.body"/>
</ul>
</div>
</xsl:template>
<xsl:template name="dvt_1.body">
<xsl:for-each select="/dsQueryResponse/Rows/Row[generate-id(.)=generate-id(key('TitleKey', #Title))]">
<li>
<xsl:value-of select="#Title"/>
</li>
<xsl:variable name="thisClassification" select="#Title" />
<div>
<xsl:for-each select="../Row[#Title = $thisClassification]">
<xsl:value-of select="#Model"/>
</xsl:for-each>
</div>
</xsl:for-each>
</xsl:template>
I would like have this output:
<div id="vtab">
<ul>
<li>HTC</li>
<li>Nokia</li>
</ul>
<div>HTC Sensation 345-HTC Ev</div>
<div>Nokia</div>
</div>
But now this is what i'm getting
<div id="vtab">
<ul>
<li>HTC</li>
<div>HTC Sensation 345HTC Evo</div>
<li>Nokia</li>
<div>Nokia</div>
</ul>
</div>
How can i exit the UL element and start the DIV element.
Thanks alot
It is not optimal, but consider making two loops something like the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<div id="vtab">
<xsl:call-template name="LOOP_1"/>
<xsl:call-template name="LOOP_2"/>
</div>
</xsl:template>
<xsl:template name="LOOP_1">
<ul>
<xsl:for-each select="/dsQueryResponse/Rows/Row[generate-id(.)=generate-id(key('TitleKey', #Title))]">
<li>
<xsl:value-of select="#Title"/>
</li>
<xsl:variable name="thisClassification" select="#Title"/>
<div>
<xsl:for-each select="../Row[#Title = $thisClassification]">
<xsl:value-of select="#Model"/>
</xsl:for-each>
</div>
</xsl:for-each>
</ul>
</xsl:template>
<xsl:template name="LOOP_2">
<div>
<xsl:for-each select="/dsQueryResponse/Rows/Row[generate-id(.)=generate-id(key('TitleKey', #Title))]">
<xsl:variable name="thisClassification" select="#Title"/>
<div>
<xsl:for-each select="../Row[#Title = $thisClassification]">
<xsl:value-of select="#Model"/>
</xsl:for-each>
</div>
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
I am not sure of your requirements, but it may be more efficient to review the desired output, and output nested lists and format it using CSS rather than creating separate loops

XSLT numbering - grouping?

I'm trying to do the following:
<body>
<div>
<p> text<note/>texttext<note/>text </p>
<p> text<note/>text </p>
</div>
<div>
text<note/>texttext<note/>text
</div>
</body>
should result in
<body>
<div>
<p> text<note n="1"/>texttext<note n="2"/>text </p>
<p> text<note n="3"/>text </p>
</div>
<div>
text<note n="1"/>texttext<note n="2"/>text
</div>
</body>
As you can see, I want to number all notes under div regardless of the parent node. So notes can be structured under div in any way.
However I can't figure out a solution by using xsl:number. Any help would be appreciated.
edit: Big thanks to DRCB for his solution. I've adapted it so that it can be also used for complex nesting by using an identity template.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="div//note">
<note>
<xsl:attribute name="n">
<xsl:value-of select="count(preceding::note) - count(preceding::div//note) + 1"/>
</xsl:attribute>
<xsl:value-of select="."/>
</note>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
tranforms:
<body>
<any>
<div>
<p>
<p> text<note/>texttext<note/>text </p>
</p>
<p> text<note/>text </p>
</div>
</any>
<div> text<note/>texttext<note/>text </div>
</body>
to:
<body>
<any>
<div>
<p>
<p> text<note n="1"/>texttext<note n="2"/>text </p>
</p>
<p> text<note n="3"/>text </p>
</div>
</any>
<div> text<note n="1"/>texttext<note n="2"/>text </div>
</body>
I believe there might be a better solution however this works for me.
A solution using xsl:number:
<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="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="note">
<xsl:variable name="vNum">
<xsl:number level="any" count="note" from="/*/div"/>
</xsl:variable>
<note n="{$vNum}">
<xsl:apply-templates/>
</note>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<body>
<div>
<p> text<note/>texttext<note/>text </p>
<p> text<note/>text </p>
</div>
<div>
text<note/>texttext<note/>text
</div>
</body>
the wanted, correct result is produced:
<body>
<div>
<p> text<note n="1"/>texttext<note n="2"/>text </p>
<p> text<note n="3"/>text </p>
</div>
<div>
text<note n="1"/>texttext<note n="2"/>text
</div>
</body>
Explanation: Appropriate use of the from attribute of xsl:number.
I have found following quick workaround:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by http://www.w3schools.com/xsl/tryxslt.asp?xmlfile=cdcatalog&xsltfile=cdcatalogp -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="div">
[div <xsl:apply-templates/>]
</xsl:template>
<xsl:template match="note">
[note n=<xsl:value-of select="count(preceding::note) - count(preceding::div//note) + 1"/>]
</xsl:template>
</xsl:stylesheet>
It works however only with "plain" div structure without a complex nesting.
You can test it here: http://www.w3schools.com/xsl/tryxslt.asp?xmlfile=cdcatalog&xsltfile=cdcatalog with your source xml.