Odd XSL output in Symphony CMS - xslt

I'm in Symphony CMS trying to return an article image like so.
<img src="{$workspace}/uploads/{/data/news-articles/entry/image-thumbnail}"/>
The output looks like this
<img src="/workspace/uploads/%0A%09%09%09%09penuts_thumb.png%0A%09%09%09%09%0A%09%09%09">
If I just try to return the node value
<xsl:value-of select="image-thumbnail" />
Output looks correct
penuts_thumb.png
Any thoughts on why I'm getting all the excess characters?

Output looks correct
No, it only "looks correct", because the browser ignores white-space characters.
What happens is that the string "penuts_thumb.png" is surrounded by whitespace. When this whitespace is serialized as part of the src attribute value, it is encoded (normalized) -- this is why you see %0A (code for newline) anf %09 (code for tab).
This transformation helps to see exactly what is generated in each case:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:variable name="workspace" select="'/workspace'"/>
<xsl:template match="/">
<img src="{$workspace}/uploads/{/data/news-articles/entry/image-thumbnail}"/>
===========
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="entry">
"<xsl:value-of select="image-thumbnail"/>"
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<data>
<news-articles>
<entry>
<image-thumbnail>
penuts_thumb.png
</image-thumbnail>
</entry>
</news-articles>
</data>
produces this output:
<img src="/workspace/uploads/%0A penuts_thumb.png%0A ">
===========
"
penuts_thumb.png
"
As we can see (thanks to the quotes) in the second case the string "penuts_thumb.png" is also surrounded by a lot of whitespace characters.
Solution:
Use the normalize-space() function in this way:
<img src=
"{$workspace}/uploads/{normalize-space(/data/news-articles/entry/image-thumbnail)}"/>

Related

How to remove last N character using XSLT

I have following code
<xsl:value-of select=concat(substring(DBColumn, string-length(DBColumn)-3),concat('-',DBColumn))
It results me
230-Virginia-230.
I want it as 230-Virginia.
Originally in database it is as Virginia-230
Furthermore
ABC, 230-Virginia
How to trim whitespace in the same mentioned code so that it should look like as follow ABC,230-Virginia
It's not clear what exactly your question is.
To answer the question as stated in your title: you can remove the last N characters from a string using:
substring($string, 1, string-length($string) - $N)
Trying to illustrate with an input document that contains the data that you mentioned:
<input>
<DBColumn>Virginia-230</DBColumn>
<other>ABC </other> <!-- N.B. trailing space -->
</input>
This XSLT 3.0 stylesheet does some of the things that you mentioned in the "proposed value". I've also included the input value and the "old-value" with the value-of expression that you mentioned in your post.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
exclude-result-prefixes="#all">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/input">
<output>
<input-value><xsl:value-of select="DBColumn" /></input-value>
<old-output-value>
<xsl:value-of
select="concat(substring(DBColumn, string-length(DBColumn)-3),
concat('-', DBColumn))"/>
</old-output-value>
<proposed-value>
<xsl:value-of
select="normalize-space(other)
|| ',' ||
string-join(reverse(tokenize(DBColumn, '-')), '-')"
/>
</proposed-value>
</output>
</xsl:template>
</xsl:stylesheet>
which produces:
<output>
<input-value>Virginia-230</input-value>
<old-output-value>-230-Virginia-230</old-output-value>
<proposed-value>ABC,230-Virginia</proposed-value>
</output>
For an xsl:value-of() that I believe works in XSLT1.0 (but I won't guarantee), you could try:
<xsl:value-of
select="concat(other, ',',
substring-after(DBColumn, '-'),
'-',
substring-before(DBColumn, '-'))" />
which does not address the trailing space in other but at least suggests how to reverse the two values around the '-' char in DBColumn.
For suggestions on removing leading/trailing spaces on string, see: XSLT 1.0 to remove leading and trailing spaces

Transforming node contents to remove whitespace

If the contents of a citations node is something like the following:
<p>
WAJWAJADS:
</p>
<p>
asdf
</p>
<p>
ALSOAS:
</p>
<p>
lorem ipsum...<br />
lorem<br />
blah blah <i>
adfas & dasdsaafs
</i>, April 2011.<br />
lorem lorem dear lord the whitespace
</p>
Is there any way to transform this to properly formatted HTML with XSLT?
normalize-space() just concats everything together. The best I've managed to do is normalize-space() on all p descendants within a for-each loop and wrap them in a p element. However, then any inner tags are still lost.
Is there a better way to parse this WYSIWYG generated trainwreck? Unfortunately I have no control over the generated XML.
I've modified a little the answer by Martin Honnen:
<xsl:template match="text()">
<xsl:value-of select="normalize-space(.)"/>
<xsl:if test="substring(., string-length(.)) = ' ' and substring(., string-length(.) - 1, string-length(.)) != ' '">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:template>
it tests if the last character is a space and the last 2 characters are not both spaces, if true, it inserts a space.
You first need to have a well-formed XML with a root.
Assuming you have that, you can apply an identity transform to copy the source tree to the result, strip spaces between the tags, optionally generate output in HTML (without the XML declaration) and indented, and use normalize-space() only in the text nodes.
Try this stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes" method="html"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>
</xsl:stylesheet>
The result applied to the data you provided will be:
<p>WAJWAJADS:</p>
<p>asdf</p>
<p>ALSOAS:</p>
<p>lorem ipsum...<br>lorem<br>blah blah<i>adfas & dasdsaafs</i>, April 2011.<br>lorem lorem dear lord the whitespace
</p>
You can see the result applied to your example in this XSLT Fiddle
UPDATE 1: to add an extra space around each text node (and avoid concatenation when the string value of the node is calculated) you can replace the last template with:
<xsl:template match="text()">
<xsl:value-of select="concat(' ',normalize-space(.),' ')"/>
</xsl:template>
Result:
<html>
<p> WAJWAJADS: </p>
<p> asdf </p>
<p> ALSOAS: </p>
<p> lorem ipsum... <br> lorem <br> blah blah <i> adfas & dasdsaafs </i> , April 2011. <br> lorem lorem dear lord the whitespace
</p>
</html>
See: http://xsltransform.net/3NzcBsE/1
UPDATE 2: to add a space or newline after each copied element. Place this <xsl:text>
</xsl:text> (for a newline) or this <xsl:text> </xsl:text> (for a space) after the </xsl:copy> in the first template:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
<xsl:text>
</xsl:text>
</xsl:template>
Result:
<html>
<p>WAJWAJADS:</p>
<p>asdf</p>
<p>ALSOAS:</p>
<p>lorem ipsum...<br>
lorem<br>
blah blah<i>adfas & dasdsaafs</i>
, April 2011.<br>
lorem lorem dear lord the whitespace
</p>
</html>
See: http://xsltransform.net/3NzcBsE/2
Use the identity transformation template plus a template for text nodes doing the normalize-space:
<xsl:template match="text()"><xsl:value-of select="normalize-space()"/></xsl:template>
This question would have been a lot easier to understand if the example contained real text instead of gibberish. "No additional whitespace between node start/end and text." is not an accurate enough description of the expected result.
I am going to take a guess here and assume you actually want to perform a "run of spaces to one space" operation on all the text nodes. This could be done as follows:
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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" priority="1">
<xsl:variable name="temp" select="normalize-space(concat('x', ., 'x'))" />
<xsl:value-of select="substring($temp, 2, string-length($temp) - 2)"/>
</xsl:template>
</xsl:stylesheet>
When applied to the following test input:
<chapter>
<p>
This question would have
been a lot <b> easier </b> to understand
if the example contained
<i> real </i> text instead of
gibberish.
</p>
<p>
Here is an example of preserving zero spaces
between text nodes:<br/>(continued) on a new line.
</p>
<p>
Here is another example of
preserving zero spaces within a text
node: <i>some text in italic</i> followed
by normal text.
</p>
</chapter>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<chapter>
<p> This question would have been a lot <b> easier </b> to understand if the example contained <i> real </i> text instead of gibberish. </p>
<p> Here is an example of preserving zero spaces between text nodes:<br/>(continued) on a new line. </p>
<p> Here is another example of preserving zero spaces within a text node: <i>some text in italic</i> followed by normal text. </p>
</chapter>
--
Note that there will be no difference between the input and output when rendered in HTML.

whitespace URL in XSLT

I have a xslt showing no whitespace as characters.
In this case show only %.
URL:
http://localhost:8888/tire/details/Bridgestone/ECOPIA%EP001S/Bridgestone,ECOPIA%EP001S,195--65%R15%91H,TL,ECO,0
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.w3.org/1999/xhtml" version="1.0">
<xsl:param name="extractorHost" />
<xsl:template match="/">
<links>
<xsl:apply-templates />
</links>
</xsl:template>
<xsl:template match="//x:form/x:a[#class='arrow-link forward']">
<xsl:variable name="url" select="translate(#href, ' ', '%20')"/>
<link href="{concat($extractorHost, $url)}" />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
The correct URL should be:
http://localhost:8888/tire/details/Bridgestone/ECOPIA%20EP001S/Bridgestone,ECOPIA%20EP001S,195--65%20R15%2091H,TL,ECO,0
Is it wrong XSLT formed?. Thanks.
The XPath translate function doesn't work the way you think it does. That is, it is not a replace-string function.
It maps individual characters from one list to the corresponding characters in the other list.
So this,
translate(#href, ' ', '%20')
means, translate a space into %. The 20 part of the third argument is ignored.
Take a look here: XSLT string replace
You can use already existing templates that will let you use "replace" function.

With XSLT, how can I process normally, but hold some nodes until the end and then output them all at once (e.g. footnotes)?

I have an XSLT application which reads the internal format of Microsoft Word 2007/2010 zipped XML and translates it into HTML5 with XSLT. I am investigating how to add the ability to optionally read OpenOffice documents instead of MSWord.
Microsoft stores XML for footnote text separately from the XML of the document text, which happens to suit me because I want the footnotes in a block at the end of the output HTML page.
However, unfortunately for me, OpenOffice puts each footnote right next to its reference, inline with the text of the document. Here is a simple paragraph example:
<text:p text:style-name="Standard">The real breakthrough in aerial mapping
during World War II was trimetrogon
<text:note text:id="ftn0" text:note-class="footnote">
<text:note-citation>1</text:note-citation>
<text:note-body>
<text:p text:style-name="Footnote">Three separate cameras took three
photographs at once, a direct downward and an oblique on each side.</text:p>
</text:note-body>
</text:note>
photography, but the camera was large and heavy, so there were problems finding
the right aircraft to carry it.
</text:p>
My question is, can XSLT process the XML as normal, but hold each of the text:note items until the end of the document text, and then emit them all at one time?
You're thinking of your logic as being driven by the order of things in the input, but in XSLT you need to be driven by the order of things in the output. When you get to the point where you want to output the footnotes, go find the footnote text wherever it might be in the input. Admittedly that doesn't always play too well with the apply-templates recursive descent processing model, which is explicitly input-driven; but nevertheless, that's the way you have to do it.
Don't think of it as "holding" the text:note items, instead simply ignore them in the main pass and then gather them at the end with a //text:note and process them there, e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:text="whateveritshouldbe">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- normal mode - replace text:note element by [reference] -->
<xsl:template match="text:note">
<xsl:value-of select="concat('[', text:note-citation, ']')" />
</xsl:template>
<xsl:template match="/">
<document>
<xsl:apply-templates select="*" />
<footnotes>
<xsl:apply-templates select="//text:note" mode="footnotes"/>
</footnotes>
</document>
</xsl:template>
<!-- special "footnotes" mode to de-activate the usual text:node template -->
<xsl:template match="#*|node()" mode="footnotes">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="footnotes" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You could use <xsl:apply-templates mode="..."/>. I'm not sure on the exact syntax and your use case, but maybe the example below will give you a clue on how to approach your problem.
Basic idea is to process your nodes twice. First iteration would be pretty much the same as now, and the second iteration only looks for footnotes and only outputs those. You differentiate those iteration by setting "mode" parameter.
Maybe this example will give you a clue how to approach your problem. Note that I used different tags that in your code, so the example would be simpler.
XSLT sheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="doc">
<xml>
<!-- First iteration - skip footnotes -->
<doc>
<xsl:apply-templates select="text" />
</doc>
<!-- Second iteration, extract all footnotes.
'mode' = footnotes -->
<footnotes>
<xsl:apply-templates select="text" mode="footnotes" />
</footnotes>
</xml>
</xsl:template>
<!-- Note: no mode attribute -->
<xsl:template match="text">
<text>
<xsl:for-each select="p">
<p>
<xsl:value-of select="text()" />
</p>
</xsl:for-each>
</text>
</xsl:template>
<!-- Note: mode = footnotes -->
<xsl:template match="text" mode="footnotes">
<xsl:for-each select=".//footnote">
<footnote>
<xsl:value-of select="text()" />
</footnote>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<text>
<p>
some text
<footnote>footnote1</footnote>
</p>
<p>
other text
<footnote>footnote2</footnote>
</p>
</text>
<text>
<p>
some text2
<footnote>footnote3</footnote>
</p>
<p>
other text2
<footnote>footnote4</footnote>
</p>
</text>
</doc>
Output XML:
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<!-- Output from first iteration -->
<doc>
<text>
<p>some text</p>
<p>other text</p>
</text>
<text>
<p>some text2</p>
<p>other text2</p>
</text>
</doc>
<!-- Output from second iteration -->
<footnotes>
<footnote>footnote1</footnote>
<footnote>footnote2</footnote>
<footnote>footnote3</footnote>
<footnote>footnote4</footnote>
</footnotes>
</xml>

Output element in comments

I need to display HTML-element in comments (for example)
<!-- <img src="path" width="100px" height="100px"/> -->
I use this approach
<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="no" encoding="windows-1251"/>
<xsl:template match="myNode">
...
<xsl:comment><xsl:apply-templates select="image" /></xsl:comment>
...
</xsl:template>
<xsl:template match="image">
<img src="{#src}" width="{#width}px" height="{#height}px" />
</xsl:template>
</xsl:stylesheet>
As a result:
<!---->
that is the code in the element xsl:comment ignored.
How do I display an item in the comments?
It might be possible to replace
<xsl:comment><xsl:apply-templates select="image" /></xsl:comment>
with
<xsl:text disable-output-escaping="yes"><!--</xsl:text>
<xsl:apply-templates select="image" />
<xsl:text disable-output-escaping="yes">--></xsl:text>
Haven't tried though.
<xsl:comment><xsl:apply-templates select="image" /></xsl:comment>
As a result:
<!---->
that is the code in the element
xsl:comment ignored
The XSLT 1.0 Spec says:
It is an error if instantiating the
content of xsl:comment creates nodes
other than text nodes. An XSLT
processor may signal the error; if it
does not signal the error, it must
recover by ignoring the offending
nodes together with their content.
How do I display an item in the
comments?
It depends what is meant for "display": in a browser:
<-- <xsl:apply-templates select="image" /> -->
may be useful, provided the result of <xsl:apply-templates/> aboveis just simple text (not markup).
If to "display" means to provide the result as text, then DOE, if allowed by the XSLT processor, may give us the wanted result:
<--
Some text -->
Finally, if it is required that what should be inside the "comment" should be markup and it should be displayed as markup, then this is rather challenging. In this case one has to use:
<xsl:output method="text"/>
and should present every XML lexical item with its desired serialization (i.e. escaped).
This is how the XPath Visualizer constructs its output.
Here is a small transformation that demonstrates the first two approaches:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<-- Hello, World -->
<xsl:text disable-output-escaping="yes"><--</xsl:text>
Hello,world! --<xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:template>
</xsl:stylesheet>
this transformation, when applied on any XML document (not used), produces:
<-- Hello, World -->
<--
Hello,world! -->
Both "comments" may be viewed as comments in a browser, while only the second is presented as comment in free text.
The third approach (most probably what you want) is illustrated below:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<-- <xsl:apply-templates select="image"/> -->
</xsl:template>
<xsl:template match="image">
<img src="<xsl:value-of select="#src"/>"
width="<xsl:value-of select="#width"/>px"
height="<xsl:value-of select="#height"/>px"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<image src="http://example.com/yyy.jpg" width="200" height="300"/>
the wanted result is produced:
<--
<img src="http://example.com/yyy.jpg"
width="200px"
height="300px"/>
-->
viewed in a browser as:
<--
<img src="http://example.com/yyy.jpg"
width="200px"
height="300px"/>
-->
From http://www.w3.org/TR/xslt#section-Creating-Comments:
The xsl:comment element is instantiated to create a comment node in the result tree. The content of the xsl:comment element is a template for the string-value of the comment node.
For example, this
<xsl:comment>This file is
automatically generated. Do not
edit!</xsl:comment>
would create the comment
<!--This file is automatically
generated. Do not edit!-->
It is an error if instantiating the
content of xsl:comment creates nodes
other than text nodes. An XSLT
processor may signal the error; if it
does not signal the error, it must
recover by ignoring the offending
nodes together with their content.
It is an error if the result of
instantiating the content of the
xsl:comment contains the string -- or
ends with -. An XSLT processor may
signal the error; if it does not
signal the error, it must recover by
inserting a space after any occurrence
of - that is followed by another - or
that ends the comment.
So, in order to do what you want you need to use DOE mechanism.
As example, this stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:output method="html" indent="no" encoding="windows-1251"/>
<xsl:template match="img">
<img src="{.}"/>
</xsl:template>
<xsl:template match="root">
<xsl:variable name="vResult">
<xsl:apply-templates/>
</xsl:variable>
<html>
<xsl:copy-of select="$vResult"/>
<xsl:comment>
<xsl:apply-templates select="msxsl:node-set($vResult)"
mode="encode"/>
</xsl:comment>
</html>
</xsl:template>
<xsl:template match="*" mode="encode">
<xsl:value-of select="concat('<',name())"
disable-output-escaping="yes"/>
<xsl:apply-templates select="#*" mode="encode"/>
<xsl:text>></xsl:text>
<xsl:apply-templates mode="encode"/>
<xsl:value-of select="concat('<',name(),'>')"
disable-output-escaping="yes"/>
</xsl:template>
<xsl:template match="*[not(node())]" mode="encode">
<xsl:value-of select="concat('<',name())"
disable-output-escaping="yes"/>
<xsl:apply-templates select="#*" mode="encode"/>
<xsl:text>/></xsl:text>
</xsl:template>
<xsl:template match="#*" mode="encode">
<xsl:value-of select="concat(' ',name(),'="',.,'"')"/>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
<img>http://example.org/image1.jpg</img>
<img>http://example.org/image2.jpg</img>
<img>http://example.org/image3.jpg</img>
</root>
Output:
<html>
<img src="http://example.org/image1.jpg">
<img src="http://example.org/image2.jpg">
<img src="http://example.org/image3.jpg">
<!--<img src="http://example.org/image1.jpg"/>
<img src="http://example.org/image2.jpg"/>
<img src="http://example.org/image3.jpg"/>-->
</html>
Note: node-set extension function for two pass transformation. disable-output-escaping attribute for xsl:value-of instruction.
As said before by Dimitri you can't use the xsl:comment instruction.
If your purpose is simply to comment a fragment of tree, the simplest way is to put the comments markers as text (unescaped) like this :
<xsl:text disable-output-escaping="yes"><!--</xsl:text><xsl:apply-templates select="image" /><xsl:text disable-output-escaping="yes">--></xsl:text>
instead of :
<xsl:comment><xsl:apply-templates select="image" /></xsl:comment>
and you will obtain exactly this
<!-- <img src="path" width="100px" height="100px"/> -->
used with msxml and saxon