How to get CDATA value from XML in XSL - xslt

I need to transform the XML inside the CDATA of the XML using the XSLT.
Input:
<pre>
<![CDATA[<p><strong>Guidance</strong> about simplifying medication in <em>patients<em> with end-stage CHF who appear to be imminently dying.</p>]]>
</pre>
Output:
<ce:section-title>Pre</ce:section-title>
<ce:para><ce:bold>Guidance</ce:bold> about simplifying medication in <ce:italic>patients</ce:italic> with <ce:inter-ref xlink:type="simple" xlink:href="/formulary/en/drug-treatment-in-the-imminently-dying.html#heart-failure">end-stage CHF who appear to be imminently dying</ce:inter-ref>.</ce:para>
Could you please suggest me on this. Thanks in advance.

Which XSLT processor and which version of XSLT can you use? With the commercial versions of Saxon 9 you can use XSLT 3.0 and
<xsl:template match="pre">
<ce:section-title>Pre</ce:section-title>
<xsl:apply-templates select="parse-xml(.)"/>
</xsl:template>
<xsl:template match="p">
<ce:para>
<xsl:apply-templates/>
</ce:para>
</xsl:template>
<!-- now add similar templates here for transformation of strong, em etc -->

Related

XSLT Concatenate content of consecutive nodes

I’m new to XSLT and work on this once a year only. Here a really Basic XSLT question I can’t figure out.
I need to concatenate all the same nodes into a single text output separated by a space. Any help appreciated.
Sample of XML
<?xml version="1.0" encoding="UTF-8"?>
<root>
<properties>
<plus>Agile en conduite tout-terrain et dans la neige.</plus>
<plus>Habitacle logeable et commandes logiques.</plus>
<plus>Bonne visibilité.</plus>
<minus>Retrait du constructeur du marché canadien en 2014.</minus>
<minus>Roulement assez sec.</minus>
</properties>
</root>
Well, it's dead easy in XSLT 2.0: string-join(properties/*, ' ').
So I guess you're not using XSLT 2.0, or you wouldn't be asking. And if you're only working on it once a year, it's probably easier for you to put up with the limitations of 1.0 than to upgrade. The 1.0 solution looks something like
<xsl:for-each select="properties/*">
<xsl:if test="position() != 1"><xsl:text> </xsl:text></xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
I figured it out... Since my output was text Was able to get it with.
<xsl:for-each select="plus">
<xsl:value-of select="."/><xsl:text>
</xsl:text>

How to select and separate comma values in XSLT

My XML:
<geoCode>36.113,-114.925</geoCode>
I need a XSLT which converts the above XML to below XML format:
<geoCode>
<lati>36.113</lati>
<longi>-114.925</longi>
</geoCode>
You can use the following code in your template:
<xsl:template match="...">
<xsl:variable name="geo-code-split" select="tokenize(geoCode, ',')" />
<geoCode>
<lati><xsl:value-of select="$geo-code-split[1]" /></lati>
<longi><xsl:value-of select="$geo-code-split[2]" /></longi>
</geoCode>
</xsl:template>
P.S.: This solution uses XSLT 2.0. For XSLT 1.0, you can use the string-before() and string-after() functions.
<geoCode>
<lati>
<xsl:value-of select="substring-before(geoCode,',')"/>
</lati>
<longi>
<xsl:value-of select="substring-after(geoCode,',')"/>
</longi>
</geoCode>
This is with substring-before and substring-after.

Using an xsl param as argument to XPath function

I've been trying to figure out a way to use a param/variable as an argument to a function.
At the very least, I'd like to be able to use basic string parameters as arguments as follows:
<xsl:param name="stringValue" default="'abcdef'"/>
<xsl:value-of select="substring(string($stringValue),1,3)"/>
The above code generates no output.
I feel like I'm missing a simple way of doing this. I'm happy to use exslt or some other extension if an xslt 1.0 processor does not allow this.
Edit:
I am using XSL 1.0 and transforming using Nokogiri, which supports XPATH 1.0 . Here is a more complete snippet of what I am trying to do:
I want to pass column numbers as parameters using nokogiri as follows
document = Nokogiri::XML(File.read('table.xml'))
template = Nokogiri::XSLT(File.read('extractTableData.xsl'))
transformed_document = template.transform(document,
["tableName","'Problems'", #Table Heading
"tablePath","'Table'", #Takes an absolute XPATH String
"nameColumnIndex","2", #column number
"valueColumnIndex","3"]) #column number
File.open('FormattedOutput.xml', 'w').write(transformed_document)
My xsl then wants to access every TD[valueColumnIndex] and and retrieve the first 3 characters at that position, which is why I am using a substring function. So I want to do something like:
<xsl:value-of select="substring(string(TD[$valueColumnIndex]),1,3)"/>
Since I was unable to do that, I tried to extract TD[$valueColumnIndex] to another param valueCode and then do substring(string(valueCode),1,3)
That did not work either (which is to say, no text was output, whereas <xsl:value-of select="$valueCode"/> gave me the expected output).
As a result, i decided to understand how to use parameters better, I would just use a hard coded string, as mentioned in my earlier question.
Things I have tried:
using single quotes around abcdef (and not) while
using string() around the param name (and not)
Based on the comments below, it seems I am handicapped in my ability to understand the error because Nokogiri does not report an error for these situations. I am in the process of installing xsltproc right now and seeing if I receive any errors.
Finally, here is my entire xsl. I use a separate template forLoop because of the valueCode param I am creating. The lines of interest are the last 5 or so. I cannot include the xml as there are data use issues involved.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:dyn="http://exslt.org/dynamic"
exclude-result-prefixes="ext dyn">
<xsl:param name="tableName" />
<xsl:param name="tablePath" />
<xsl:param name= "nameColumnIndex" />
<xsl:param name= "valueColumnIndex"/>
<xsl:template match="/">
<xsl:param name="tableRowPath">
<xsl:value-of select="$tablePath"/><xsl:text>/TR</xsl:text>
</xsl:param>
<!-- Problems -->
<section>
<name>
<xsl:value-of select="$tableName" />
</name>
<!-- <xsl:for-each select="concat($tablePath,'/TR')"> -->
<xsl:for-each select="dyn:evaluate($tableRowPath)">
<!-- Encode record section -->
<xsl:call-template name="forLoop"/>
</xsl:for-each>
</section>
</xsl:template>
<xsl:template name="forLoop">
<xsl:param name="valueCode">
<xsl:value-of select="./TD[number($valueColumnIndex)][text()]"/>
</xsl:param>
<xsl:param name="RandomString" select="'Try123'"/>
<section>
<name>
<xsl:value-of select="./TD[number($nameColumnIndex)]"/>
</name>
<code>
<short>
<xsl:value-of select="substring(string($valueCode),1,3)"/>
</short>
<long>
<xsl:value-of select="$valueCode"/>
</long>
</code>
</section>
</xsl:template>
</xsl:stylesheet>
Use it this way:
<xsl:param name="stringValue" select="'abcdef'"/>
<xsl:value-of select="substring($stringValue,1,3)"/>

Matching different node handles to get a node value with XSLT

XSLT available is 1.0.
I'm working on a dual-language site in an XML-based CMS (Symphony CMS), and need to replace the English version of a category name with the French version.
This is my source XML.
<data>
<our-news-categories-for-list-fr>
<entry id="118">
<title-fr handle="technology">Technologie</title-fr>
</entry>
<entry id="117">
<title-fr handle="healthcare">Santé</title-fr>
</entry>
</our-news-categories-for-list-fr>
<our-news-article-fr>
<entry id="100">
<categories>
<item id="117" handle="healthcare" section-handle="our-news-categories" section-name="Our News categories">Healthcare</item>
<item id="118" handle="technology" section-handle="our-news-categories" section-name="Our News categories">Technology</item>
</categories>
<main-text-fr mode="formatted"><p>Blah blah</p></main-text-fr>
</entry>
</our-news-article-fr>
</data>
This is part of the XSLT that I currently have for the French version.
<xsl:template match="data">
<xsl:apply-templates select="our-news-article-fr/entry"/>
</xsl:template>
<xsl:template match="our-news-article-fr/entry">
<xsl:if test="categories/item">
<p class="category">In:</p>
<ul class="category">
<xsl:for-each select="categories/item">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:if>
</xsl:template match>
The problem: the visible text of the anchor (<xsl:value-of select="."/>) gives the English version of the category title.
The handles of the following nodes match (all handles are in English), and so I'm thinking I should be able to match one from the other.
/data/our-news-categories-for-list-fr/entry/title-fr/#handle (value of title-fr node is French translation of category title)
/data/our-news-article-fr/entry/categories/item/#handle
I'm new to XSLT and am struggling to find how to do this.
Many thanks.
Add <xsl:key name="k1" match="our-news-categories-for-list-fr/entry" use="#id"/> as a child of your XSLT stylesheet element. Then use e.g. <li><xsl:value-of select="key('k1', #id)/title-fr"/></li>.
../our-news-categories-for-list-fr/entry/title-fr/text() instead of #handle should do it
Your problem is that you are in
<our-news-article-fr>
and need to reference
<our-news-categories-for-list-fr>
so I do a parent .. to walk up the tree and then down the entry nodes
Within the xsl:for-each repetition instruction, the context is our-news-article-fr/entry/categories/item. If you use . you select the current context, that's why you are receiving the english version there.
Another approach (not saying the simplest and the best one) is simply specify an XPath expression which locates the correct node. You can use the ancestor:: axis to go out from the current context to data and then use your test node. The needed predicate must match against the current context using current() function:
<xsl:value-of select="
ancestor::data[1]/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]
"/>
If data is the root of your document you can obviously use an absolute location path:
/
data/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]

How to comment in XSLT and not HTML

I'm writing XSL and I want to make comments throughout the code that will be stripped when it's processed, like PHP, however I'm not sure how.
I'm aware of the comment object, but it prints out an HTML comment when processed. :\
<xsl:comment>comment</xsl:comment>
You use standard XML comments:
<!-- Comment -->
These are not processed by the XSLT transformer.
Just make sure that you put your <!-- comments --> AFTER the opening XML declaration (if you use one, which you really don't need):
BREAKS:
<!-- a comment -->
<?xml version="1.0"?>
WORKS:
<?xml version="1.0"?>
<!-- a comment -->
I scratched my head on this same issue for a bit while debugging someone else's XSLT... seems obvious, but easily overlooked.
Note that white space on either side of the comments can end up in the output stream, depending on your XSLT processor and its settings for handling white-space. If this is an issue for your output, make sure the comment is bracketed by xslt tags.
EG
<xsl:for-each select="someTag">
<xsl:text>"</xsl:text>
<!-- output the id -->
<xsl:value-of select="#id"/>
<xsl:text>"</xsl:text>
</xsl:for-each>
Will output " someTagID" (the indent tab/spaces in front of the comment tag are output).
To remove, either unindent it flush with left margin, or bracket it like
<xsl:text>"</xsl:text><!-- output the id --><xsl:value-of select="#id"/>
This is the way to do it in order to create a comment node that won't be displayed in html
<xsl:comment>
<!-- Content:template -->
</xsl:comment>
Sure. Read http://www.w3.org/TR/xslt#built-in-rule and then it should be apparent why this simple stylesheet will (well, should) do what you want:
<?xml version="1.0"?>
<xsl:stylesheet xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="comment()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()|#*"/>
</xsl:stylesheet>
Try :
<xsl:template match="/">
<xsl:for-each select="//comment()">
<SRC_COMMENT>
<xsl:value-of select="."/>
</SRC_COMMENT>
</xsl:for-each>
</xsl:template>
or use a <xsl:comment ...> instruction for a more literal duplication of the source document content in place of my <SRC_COMMENT> tag.