XSLT numbering - grouping? - xslt

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.

Related

XSL grouping and wrapping of tags

Need some help with one xsl transformation.
I got an XML with the following format
<p class="list">
First Vid
</p>
<p class="indent">test</p>
<notes><p>TEST ME NOTE</p></notes>
<p class="list">
Second Vid
</p>
This needs to be converted to something like
<ul>
<li class="list">
<p>
First Vid
</p>
<p class="indent">test</p>
<notes>
<p>TEST ME NOTE</p>
</notes>
</li>
<li class="list">
Second Vid
</li>
</ul>
what i did is
<xsl:template match="p">
<li class="list">
<p>
<xsl:apply-templates/>
</p>
</li>
</xsl:template>
But that created li for all p elements and i lost the notes tag.
How can i wrap those nodes between the class list to the first li?
You could use group-starting-with, for example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<output>
<ul>
<xsl:for-each-group select="input/*" group-starting-with="p[#class='list']">
<li class="list">
<xsl:copy-of select="current-group()"/>
</li>
</xsl:for-each-group>
</ul>
</output>
</xsl:template>
</xsl:stylesheet>
See it working here: https://xsltfiddle.liberty-development.net/aiyneL

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>

XSLT Concatenate text

I have a large number of html files like the following:
<html>
<head>
<title>t</title>
</head>
<body>
<div class="a">
<div class="b" type="t1">
b11<div class="x">x</div>
b12<div class="y">y</div>b13
</div>
<div class="c">c</div>
</div>
<div class="b" type="t2" region="r">b21
<div class="x">x</div>b22
<div class="y">y</div>
b23
</div>
</body>
</html>
At present the text for div class="b" is fragmented at the beginning, middle and end of the node.
I want to consolidate the text for div class="b" so that it appears at the beginning.
The file I want to obtain is like the following:
<html>
<head>
<title>t</title>
</head>
<body>
<div class="a">
<div class="b" type="t1">b11 b12 b13
<div class="x">x</div>
<div class="y">y</div>
</div>
<div class="c">c</div>
</div>
<div class="b" type="t2" region="r">b21 b22 b23
<div class="x">x</div>
<div class="y">y</div>
</div>
</body>
</html>
I run the following bash script a.sh:
xsltproc a.xslt a.html > b.html
where a.xslt is the following:
<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()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//div[#class='b']">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
<xsl:for-each select="text()">
<xsl:if test="position() > 1"><xsl:text> </xsl:text></xsl:if>
<xsl:value-of select="normalize-space(.)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Unfortunately my output is not what I want:
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<title>t</title>
</head>
<body>
<div class="a">
<div class="b" type="t1">b11
<div class="x">x</div>
b12
<div class="y">y</div>
b13</div>
b11 b12 b13
<div class="c">c</div>
</div>
<div class="b" region="r" type="t2">b21
<div class="x">x</div>
b22
<div class="y">y</div>
b23</div>
<p>b21 b22 b23</p>
</body>
</html>
Do you have any advice on how to proceed please?
Would this work for you?
<xsl:template match="div[#class='b']">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each select="text()">
<xsl:if test="position() > 1">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:value-of select="normalize-space(.)"/>
</xsl:for-each>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>

how can I represent nested element of xml with 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>

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