create HTML from a list of XML nodes using XSLT - xslt

I am a noob on XSLT.
I have a XML where t nodes are followed by other nodes, and then another t node might appear again followed by nodes again, and so on
<t />
<n1 />
<n2 />
..
<t/>
<n3 />
<n4 />
...
What I need to turn this XML into is a HTML where t nodes wraps all nodes following it up to the next t node
<div class='t'>
<div class='n1'/>
<div class='n2'/>
...
</div>
<div class='t'>
<div class='n3'/>
<div class='n4'/>
...
</div>
I am having a hard time implementing this.
Any ideas \ hints?
Thanks!

This is grouping adjacents. There are many solutions:
Whit this wellformed input:
<root>
<t />
<n1 />
<n2 />
<t/>
<n3 />
<n4 />
</root>
XSLT 1.0: traversing with following axis
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="node()[1]" mode="group"/>
<xsl:apply-templates select="t"/>
</xsl:copy>
</xsl:template>
<xsl:template match="t">
<div class="t">
<xsl:apply-templates select="following-sibling::node()[1]"
mode="group"/>
</div>
</xsl:template>
<xsl:template match="t" mode="group"/>
<xsl:template match="node()" mode="group">
<xsl:apply-templates select="."/>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="group"/>
</xsl:template>
<xsl:template match="*[starts-with(name(),'n')]">
<div class="{name()}"/>
</xsl:template>
</xsl:stylesheet>
XSLT 1.0: Keys
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:key name="kNodeByMark"
match="node()[../t][not(self::t)]"
use="generate-id((..|preceding-sibling::t[1])[last()])"/>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="key('kNodeByMark',generate-id())"/>
<xsl:for-each select="t">
<div class="t">
<xsl:apply-templates
select="key('kNodeByMark',generate-id())"/>
</div>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'n')]">
<div class="{name()}"/>
</xsl:template>
</xsl:stylesheet>
XSLT 2.0: for-each-group instruction
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="node()[../t[1] >> .]"/>
<xsl:for-each-group select="node()" group-starting-with="t">
<div class="t">
<xsl:apply-templates
select="current-group()[position()>1]"/>
</div>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'n')]">
<div class="{name()}"/>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<div class="t">
<div class="n1" />
<div class="n2" />
</div>
<div class="t">
<div class="n3" />
<div class="n4" />
</div>
</root>
EDIT: Traversing following axis refactored to look like the others solutions. Stripping identity rules.

See my note on your question, regarding "which XSLT version?". If grouping is supported in your target version, see other answers here, as that is easier to understand and will almost certainly perform better on any XSLT processor. If you aren't certain, I recommend going with a 1.0 solution like this one.
You can do it with the "XML fragment" exactly like you posted with most XSLT processors, but I added a "root" element to your XML, to reduce certain unknowns in answering your question.
In this solution below, I've tried to keep a direct correlation between the shape of the XSLT and the shape of the output you desire. In my opinion that makes it easier to maintain/understand, at least for smaller stylesheets.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
<xsl:for-each select="t">
<div class='t'>
<xsl:for-each select="following-sibling::*[count(preceding-sibling::t)=(count(current()/preceding-sibling::t) + 1) and not(self::t)]">
<div class='{name()}' />
</xsl:for-each>
</div>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The right-hand side of "following-sibling::*[count(preceding-sibling::t)=(count(current()/preceding-sibling::t) + 1) and not(self::t)]" could be simplified, I'm sure, using something like "current()::position()" (which isn't valid, fyi), but I'm rusty and couldn't remember some of the alias syntax.
This basically says: 1) Evaluate every T. 2) Select elements with the same quantity of T preceding them, as the index of the T we are currently evaluating.
Note that you've probably tried iterating through procedurally, and found you can't store the last value found in XSLT. Or you've found that you can, but only with nested templates. This same type of pivot you are performing has many XSLT neophytes hitting roadblocks, so don't feel bad.

Related

xslt fields grouped by their type

I have some data that i put in (image, title, text). I ahve 3 different elements in my xslt for each of them:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:udt="DotNetNuke/UserDefinedTable" exclude-result-prefixes="udt">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<!--
This prefix is used to generate module specific query strings
Each querystring or form value that starts with udt_{ModuleId}_param
will be added as parameter starting with param
-->
<xsl:variable name="prefix_param">udt_<xsl:value-of select="//udt:Context/udt:ModuleId" />_param</xsl:variable>
<xsl:template match="udt:Data" mode="list">
<xsl:value-of select="udt:Image" disable-output-escaping="yes" />
<xsl:value-of select="udt:Title" disable-output-escaping="yes" />
<xsl:value-of select="udt:Text" disable-output-escaping="yes" />
</xsl:template>
<xsl:template match="/udt:UserDefinedTable">
<xsl:variable name="currentData" select="udt:Data" />
<xsl:if test="$currentData">
<xsl:apply-templates select="$currentData" mode="list">
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template name="EditLink">
<xsl:if test="udt:EditLink">
<a href="{udt:EditLink}">
<img border="0" alt="edit" src="{//udt:Context/udt:ApplicationPath}/images/edit.gif" />
</a>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I want to fill these elements each into its own div group, so my end result would be something like this:
<div id="images">
<!--all images here-->
</div>
<div id="title">
<!--all titles here-->
</div>
<div id="text">
<!--all texts here-->
</div>
Can this be achieved by any kind of grouping or whats the right aproach?
The concept to use xsl:value-of for your 3 element types is wrong,
as this instruction copies only the content of these elements,
without XML markup.
Assuming that the only goal is to reorder (group) your elements,
and they are direct descendants of the current element (udt:Data),
the task can be done the following way:
<xsl:template match="udt:Data">
<xsl:copy>
<div id="images">
<xsl:copy-of select="udt:Image"/>
</div>
<div id="title">
<xsl:copy-of select="udt:Title"/>
</div>
<div id="text">
<xsl:copy-of select="udt:Text"/>
</div>
</xsl:copy>
</xsl:template>
Of course, this is only a template, not the whole script.
Note that e.g. if these elements were located also at "deeper" descendant levels,
all the above XPath expressions should be prececed with descendant::.
And remember about including in your script all namespaces, the script refers to.
They should be declared in stylesheet tag.

cdata-section-elements not working for dynamically created element

I am trying to define some dynamically created elements as cdata sections, but it's not working for some reason:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="no" indent="yes" method="xml"
cdata-section-elements="DESCRIPTION2"
/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/RSS/ITEM/TEST">
<DESCRIPTION2>
<div class="container">
<xsl:if test="NAME != ''">
<div class="test">
<xsl:value-of select="NAME"/>
</div>
</xsl:if>
</div>
</DESCRIPTION2>
</xsl:template>
</xsl:stylesheet>
Test XML:
<?xml version="1.0" encoding="UTF-8"?>
<RSS>
<ITEM>
<CODE>41,000</CODE>
<TEST>
<NAME><p>HTML code</p></NAME>
</TEST>
</ITEM>
</RSS>
Live test.
Sure I can add manually (<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>), but I would like to know why it's not working If I define it as cdata-section-elements.
CDATA serialization happens for text nodes inside of the nominated elements, if you put in elements there it doesn't happen. Note that, assuming an XSLT 3 processor with XPath 3.1 support, you can use the serialize function to serialize the content you build as html and then output it as a text node:
<xsl:template match="/RSS/ITEM/TEST">
<xsl:variable name="html">
<div class="container">
<xsl:if test="NAME != ''">
<div class="test">
<xsl:value-of select="NAME"/>
</div>
</xsl:if>
</div>
</xsl:variable>
<DESCRIPTION2>
<xsl:value-of select="serialize($html, map { 'method' : 'html' })"/>
</DESCRIPTION2>
</xsl:template>
http://xsltfiddle.liberty-development.net/948Fn5i/1 then gives the result as a CDATA section
<DESCRIPTION2><![CDATA[<div class="container">
<div class="test">Peter</div>
</div>]]></DESCRIPTION2>
Your content is well-formed XHTML, so it doesn't need to apply CDATA when serializing the content.
If you escaped the markup and constructed a string, it would serialize as CDATA:
<xsl:template match="/RSS/ITEM/TEST">
<DESCRIPTION2>
<div class="container">
<xsl:if test="NAME != ''">
<div class="test">
<xsl:value-of select="NAME"/>
</div>
</xsl:if>
</div>
</DESCRIPTION2>
</xsl:template>
Produces:
<DESCRIPTION2><![CDATA[
<div class="container">
<div class="test">
Peter
</div>
</div>
]]></DESCRIPTION2>
But why would you want to generate a string when you could have well-formed markup? It makes it a pain for everyone downstream.

XSLT - Is it possible to output template matches into one span tag

The following is an XSLT Transform that outputs HTML. Is it possible to output both Target A and Target B into one span?
xsl
<xsl:template match="targetA | targetB">
<span> Is it possible to output both targets in one span</span>
</xsl:template>
xml
<doc>
<targetA>
Target A Content
</targetA>
<targetB>
Target B Content
</targetB>
</doc>
expecting
<span>
Target A and Target B
</span>
This is similar to a previous question (Output once if both xml tags occur at the same time), so you can do this by having a template like this....
<xsl:template match="targetA | targetB[not(../targetA)]">
<span>
<xsl:value-of select="../targetA" />
<xsl:value-of select="../targetB" />
</span>
</xsl:template>
(You would probably use an xsl:text if you wanted a space between then).
However, you would also need a template to match, and ignore targetB to avoid that being output again. (Assuming you are using the identity template, for example).
Try this XSLT...
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="targetA | targetB[not(../targetA)]">
<span>
<xsl:value-of select="../targetA" />
<xsl:value-of select="../targetB" />
</span>
</xsl:template>
<xsl:template match="targetB" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note, this approach would fail if you have more that one targetA or targetB under the same parent.

XSLT: find first element above selected element

I want to get the first heading (h1) before a table in a docx.
I can get all headings with:
<xsl:template match="w:p[w:pPr/w:pStyle[#w:val='berschrift1']]">
<p>
<context>
<xsl:value-of select="." />
</context>
</p>
</xsl:template>
and I can also get all tables
<xsl:template match="w:tbl">
<p>
<table>
<xsl:value-of select="." />
</table>
</p>
</xsl:template>
But unfortunetly the processor does not accept
<xsl:template match="w:tbl/preceding-sibling::w:p[w:pPr/w:pStyle[#w:val='berschrift1']]">
<p>
<table>
<xsl:value-of select="." />
</table>
</p>
</xsl:template>
Here is a reduced XML file extracted from a docx: http://pastebin.com/KbUyzRVv
I want something like that as a result:
<context>Let’s get it on</context> <- my heading
<table>data</table>
<context>Let’s get it on</context> <- my heading
<table>data</table>
<context>We’re in the middle of something</context> <- my heading
<table>data</table>
Thanks to Daniel Haley I was able to find a solution for that problem. I'll post it here, so it is independend of the pastebin I postet below.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:v="urn:schemas-microsoft-com:vml" exclude-result-prefixes="xsl w v">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="w:tbl">
<context>
<xsl:value-of select="(preceding-sibling::w:p[w:pPr/w:pStyle[#w:val = 'berschrift1']])[last()]"/>
</context>
<table>
<xsl:value-of select="."/>
</table>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Hard to answer without a Minimal, Complete, and Verifiable example, but try this:
<xsl:template match="w:tbl">
<p>
<table>
<xsl:value-of select="(preceding::w:p[w:pPr/w:pStyle[#w:val='berschrift1']])[last()]"/>
</table>
</p>
</xsl:template>
Assuming you can use XSLT 2.0 (and most people can, nowadays), I find a useful technique here is to have a global variable that selects all the relevant nodes:
<xsl:variable name="special"
select="//w:tbl/preceding-sibling::w:p[w:pPr/w:pStyle[#w:val='berschrift1']][1]"/>
and then use this variable in a template rule:
<xsl:template match="w:p[. intersect $special]"/>
In XSLT 3.0 you can reduce this to
<xsl:template match="$special"/>

XSLT get nth node using dynamic number

I need to add CSS classes to each item output by an XSLT template. The class values need to include the position of the node, which is fine, but the position needs to be written in words (classOne, classTwo etc) rather than a digits (class1, class2 etc).
The code I have almost works. It outputs the position correctly as a number, but when I use that position to return the written version of the number it just picks the first one every time, so I always get a class of 'classOne'. If I hard-code the number it works fine.
<xsl:param name="currentPage"/>
<xsl:variable name="numbers" select="my.library:Split('One,Two,Three,Four,Five,Six,Seven,Eight',',')"/>
<xsl:template match="/">
<xsl:apply-templates select="$currentPage/*[starts-with(name(), 'largeImage')]" mode="large" />
</xsl:template>
<xsl:template match="*" mode="large">
<xsl:variable name="index" select="substring(name(), 11)"/>
<div class="class{$numbers/*[$index]}">item</div>
</xsl:template>
Can anyone see how I can get it to convert the $index value into the written equivalent?
Use <div class="class{$numbers/*[position() = $index]}">item</div>. If that does not work then you need to show details of what kind of data your my.library:Split function returns.
No string-splitting and extension functions are necessary to carry out the required processing:
I. XSLT 1.0 Solution
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:nums>
<num>one</num>
<num>two</num>
<num>three</num>
<num>four</num>
<num>five</num>
<num>six</num>
<num>seven</num>
<num>eight</num>
<num>nine</num>
<num>ten</num>
</my:nums>
<xsl:variable name="vNums" select="document('')/*/my:nums/*"/>
<xsl:template match="*[starts-with(name(), 'largeImage')]">
<xsl:variable name="vIndex">
<xsl:number count="*[starts-with(name(), 'largeImage')]"
level="any"/>
</xsl:variable>
<div class="class{$vNums[position() = $vIndex]}">item</div>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (no source XML document was provided!):
<t>
<largeImageA/>
<largeImageB/>
<largeImageC/>
<largeImageD/>
<largeImageE/>
<largeImageF/>
<largeImageG/>
<largeImageH/>
<largeImageI/>
<largeImageJ/>
</t>
produces the wanted, correct result:
<div class="classone">item</div>
<div class="classtwo">item</div>
<div class="classthree">item</div>
<div class="classfour">item</div>
<div class="classfive">item</div>
<div class="classsix">item</div>
<div class="classseven">item</div>
<div class="classeight">item</div>
<div class="classnine">item</div>
<div class="classten">item</div>
II. XSLT 2.0 Solution
<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="*[starts-with(name(), 'largeImage')]">
<xsl:variable name="vIndex">
<xsl:number count="*[starts-with(name(), 'largeImage')]"
level="any" format="w"/>
</xsl:variable>
<div class="class{$vIndex}">item</div>
</xsl:template>
</xsl:stylesheet>
when applied on the same XML document (above), the same correct result is produced:
<div class="classone">item</div>
<div class="classtwo">item</div>
<div class="classthree">item</div>
<div class="classfour">item</div>
<div class="classfive">item</div>
<div class="classsix">item</div>
<div class="classseven">item</div>
<div class="classeight">item</div>
<div class="classnine">item</div>
<div class="classten">item</div>
Somewhat more verbose, but you could try something like:
<xsl:template name="int2str">
<xsl:param name="val" required="yes"/>
<xsl:choose>
<xsl:when test="$val= 1">One</xsl:when>
<xsl:when test="$val= 2">Two</xsl:when>
<xsl:when test="$val= 3">Three</xsl:when>
<xsl:when test="$val= 4">Four</xsl:when>
<xsl:when test="$val= 5">Five</xsl:when>
<xsl:when test="$val= 6">Six</xsl:when>
<xsl:when test="$val= 7">Seven</xsl:when>
<xsl:when test="$val= 8">Eight</xsl:when>
</xsl:choose>
</xsl:template>
you can call it with:
<xsl:call-template name="int2str">
<xsl:with-param name="val" select="$index"/>
</xsl:call-template>