cdata-section-elements not working for dynamically created element - xslt

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.

Related

Strip all the text from a specific node and remove all tags from xml using xslt1

I'm trying to strip all tags from a xml doc and i need to strip all text from a specific node only. For more clearity see the below example:
<root>
<p>My 1st Semester Visual</p>
<p>
<b>Self Reflection</b>
</p>
<p>The activity</p>
<content-block>
<div class="imageWrapper" />
</content-block>
<p id="5fce699db97470099ea6c7e6"> </p>
<content-block>
<div class="carousel">
<div class="carouselHeader" />
<div class="carouselNavbar">
<div class="carouselNavbarThumbnails" />
</div>
</div>
My Space Unit Flyer
</content-block>
<div>
<br />
</div>
</root>
Result:
<root><text>My 1st Semester VisualSelf ReflectionThe activity
My Space Unit Flyer
</text><contentBlocks>2</contentBlocks></root>
Expected result: I also need to remove text that is inside the <content-block>.
<root><text>My 1st Semester VisualSelf ReflectionThe activity
</text><contentBlocks>2</contentBlocks></root>
My xslt:
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="no" omit-xml-declaration="yes"/>
<!-- Strip out white space -->
<xsl:strip-space elements="*"/>
<!-- Strip out all html tags, only leaving text contents -->
<xsl:template match="*">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="root">
<root>
<text>
<xsl:apply-templates/>
</text>
<contentBlocks>
<xsl:if test="//content-block">
<xsl:value-of select="count(//content-block)"/>
</xsl:if>
<xsl:if test="figure">
<xsl:value-of select="count(figure)"/>
</xsl:if>
</contentBlocks>
</root>
</xsl:template>
</xsl:transform>
Thanks in advance
<!-- Add this to your code. It suppresses content-block. -->
<xsl:template match="content-block"/>

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.

XSL for-each 2 elements warp in container

I have an xsl file, where I get content out of templates and need to warp each 2 elements in container, if there are 2 elements.
My code is like this:
<?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="html"/>
<xsl:template name="type0">
<div class="content"></div>
</xsl:template>
<xsl:template name="teaser>
<xsl:param name="pos" select="1" />
<xsl:call-template name="type0">
<xsl:with-param name="pos" select="$pos" />
</xsl:call-template>
</xsl:template>
<xsl:template match="frontpage">
<div class="container">
<xsl:call-template name="teaser />
</div>
</xsl:template>
The output needs to be like this (if I there are 5 elements created):
<div class="container">
<div class="content"></div>
<div class="content"></div>
</div>
<div class="container">
<div class="content"></div>
<div class="content"></div>
</div>
<div class="container">
<div class="content"></div>
</div>
Any idea how this is possible?
EDIT:
I've tryed to implement Thomas W.'s suggestion, but it fails with it can't compile stylesheet. Am I doing something work in this code:
<xsl:template match="type0" mode="generate-container-content">
<div class="content"></div>
</xsl:template>
<xsl:template name="teaser">
<xsl:param name="pos" select="1" />
<div class="container">
<xsl:template match="type0[position() mod 2 = 1]">
<xsl:apply-templates select=".|following-sibling::type0[1]" mode="generate-container-content">
<xsl:with-param name="pos" select="$pos" />
</xsl:apply-templates>
</xsl:template>
</div>
</xsl:template>
<xsl:template match="frontpage">
<xsl:call-template name="teaser" />
</xsl:template>
Say you have a list of <someElement> elements, then you can make use of modes and do the following (see comments for explanation):
<!-- Only generate a container for elements at uneven position
(1st, 3dr, 5th etc.) -->
<xsl:template match="someElement[position() mod 2 = 1]">
<div class="container">
<!-- Use a different mode for actually creating the content elements
We need to use the mode for distinguishing between templates that
match elements in the list for creating the container and ones that
match for creating content. -->
<xsl:apply-templates select=".|following-sibling::someElement[1]"
mode="generate-container-content"/>
</div>
</xsl:template>
<!-- Don't generate anything for any other elements that are not
matched by the template above (the even-positioned ones remain) -->
<xsl:template match="someElement"/>
<xsl:template match="someElement" mode="generate-container-content">
<div class="content">
Generate whatever the content looks like
</div>
</xsl:template>

XSLT: Wrap every 3rd div in a div

I'm looking to get some XSLT (for Umbraco CMS) coded properly and I'm getting kind of stumped. What I'm trying to do is:
Start from a certain node, put each child node into a div; for every 3 children, wrap in a parent div.
Instead of my mess of for-each,choose and when statements, I have tried implementing a apply-template structure but I just can't seem to get the hang of it; so here's my mess of XSLT right now (I'm sure this is bad practice & terrible for performance, but I really don't know what to do at the moment):
<div class="row four">
<h2>Smart Phones see all smart phones →</h2>
<div class="row three"> <!-- This div should be created again for every 3 divs -->
<xsl:for-each select="umbraco.library:GetXmlNodeById('1063')/descendant::*[#isDoc and string(showInMainNavigation) = '1']">
<xsl:choose>
<xsl:when test="position() < 3">
<div class="col">
<a href="{umbraco.library:NiceUrl(./#id)}">
<img class="phonePreviewImg" src="{./previewImage}" style="max-width:117px; max-height:179px;" />
<h4 class="phoneTitle"><xsl:value-of select="./#nodeName" />/h4>
<p class="phonePrice">$<xsl:value-of select="./price" /></p
</a>
</div>
</xsl:when>
<xsl:when test="position() = 3"> <!-- set this div to include class of `omega` -->
<div class="col omega">
<a href="{umbraco.library:NiceUrl(./#id)}">
<img class="phonePreviewImg" src="{./previewImage}" style="max-width:117px; max-height:179px;" />
<h4 class="phoneTitle"><xsl:value-of select="./#nodeName" />/h4>
<p class="phonePrice">$<xsl:value-of select="./price" /></p
</a>
</div>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</div> <!-- End Row Three -->
</div> <!-- End Row Four -->
Obviously this code does not produce the "wrap every three". Can anyone shed some light on what I need to do to accomplish this?
UPDATE - improved the answer
I cannot think of an elegant solution using templates, but this clunky one with a loop works:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="render">
<xsl:param name="node"/>
<xsl:param name="last"/>
<div>
<xsl:if test="$last">
<xsl:attribute name="class">
<xsl:text>omega</xsl:text>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="$node"/>
</div>
</xsl:template>
<xsl:template match="/*">
<root>
<xsl:variable name="nodes" select="*[not(#skip)]"/>
<xsl:for-each select="$nodes">
<xsl:if test="(position() mod 3)=1">
<xsl:variable name="position" select="position()"/>
<div>
<xsl:call-template name="render">
<xsl:with-param name="node" select="."/>
<xsl:with-param name="last" select="false()"/>
</xsl:call-template>
<xsl:if test="$nodes[$position+1]">
<xsl:call-template name="render">
<xsl:with-param name="node" select="$nodes[$position+1]"/>
<xsl:with-param name="last" select="false()"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="$nodes[$position+2]">
<xsl:call-template name="render">
<xsl:with-param name="node" select="$nodes[$position+2]"/>
<xsl:with-param name="last" select="true()"/>
</xsl:call-template>
</xsl:if>
</div>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
applied to:
<root>
<node>1</node>
<node skip="1">to be skipped</node>
<node>2</node>
<node>3</node>
<node skip="1">to be skipped</node>
<node skip="1">to be skipped</node>
<node>4</node>
<node skip="1">to be skipped</node>
<node>5</node>
<node>6</node>
<node>7</node>
<node skip="1">to be skipped</node>
</root>
produces:
<root>
<div>
<div>1</div>
<div>2</div>
<div class="omega">3</div>
</div>
<div>
<div>4</div>
<div>5</div>
<div class="omega">6</div>
</div>
<div>
<div>7</div>
</div>
</root>
You need to replace the select used to set the $nodes variable the XPath selecting the nodes you want, and the render template with the code needed to generate the result you need for each node.
As simple and short as this:
<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="num[position() mod 3 = 1]">
<div>
<xsl:apply-templates mode="inGroup"
select=".|following-sibling::*[not(position() >2)]"/>
</div>
</xsl:template>
<xsl:template match="num" mode="inGroup">
<p><xsl:apply-templates mode="inGroup"/></p>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is produced:
<div>
<p>01</p>
<p>02</p>
<p>03</p>
</div>
<div>
<p>04</p>
<p>05</p>
<p>06</p>
</div>
<div>
<p>07</p>
<p>08</p>
<p>09</p>
</div>
<div>
<p>10</p>
</div>
Here's an elegant solution using templates.
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:param name="pNumInGroup" select="3" />
<xsl:template match="/*">
<html>
<xsl:apply-templates select="*[position() mod $pNumInGroup = 1]" />
</html>
</xsl:template>
<xsl:template match="node">
<div>
<xsl:for-each
select=".|following-sibling::*[not(position() > $pNumInGroup - 1)]">
<div>
<xsl:apply-templates />
</div>
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
...is applied to the sample XML provided by #MiMo:
<root>
<node>1</node>
<node>2</node>
<node>3</node>
<node>4</node>
<node>5</node>
<node>6</node>
<node>7</node>
</root>
...the correct result is produced:
<html>
<div>
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<div>
<div>4</div>
<div>5</div>
<div>6</div>
</div>
<div>
<div>7</div>
</div>
</html>
If the parameter value is changed to 5:
<xsl:param name="pNumInGroup" select="5" />
...the correct result is still produced:
<html>
<div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
<div>
<div>6</div>
<div>7</div>
</div>
</html>
Explanation:
We define a pNumInGroup parameter at the top of the document (with a default value of 3). This is useful, as it allows the XSLT to be used more flexibly (i.e., if you need a different number of <div> elements per group, simply pass them as a parameter).
The first template matches the root node, recreates it, and tells the XSLT processor to apply templates to the first element of each grouping (here's a refresher in modular arithmetic in case you need it).
The second template matches the <node> elements that are selected by the previous template. For each, a new <div> element is created and populated with the remaining items appropriate to that wrapped group.
NOTE: I generally stay away from <xsl:for-each> unless I really need it; in the case of the last template, I don't really need it (I could just as easily have specified the wrapping/secondary <div> logic with another template). However, for the sake of "crispness" and not over-templating the XSLT, I chose this route.

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>