XSLT normalize-space not working - xslt

Here is my input xml
<?xml version="1.0" encoding="utf-8"?>
<content>
<body>
<p>This      is a Test</p>
<p>
Toronto,           ON - Text added here.
</p>
</body>
</content>
and here is my style sheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="xml" indent="no" />
<!-- Root / document element-->
<xsl:template match="/">
<xsl:apply-templates select="node()"/>
</xsl:template>
<xsl:template match="*/text()">
<xsl:value-of select="normalize-space()"/>
</xsl:template>
</xsl:stylesheet>
When i apply this transformation using the ASP.NET XslCompiledTransform's transform method and view the result in the browser, I still can see the spaces and the normalize-space does not seem to be working.
Can any one please let me know what i am doing wrong
Thanks a lot!

This transformation:
<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="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(text()[2])]/text()">
<xsl:value-of select="normalize-space()"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided source XML document:
<content>
<body>
<p>This is a Test</p>
<p>
Toronto, ON - Text added here.
</p>
</body>
</content>
produces the wanted, correct result:
<content><body><p>This is a Test</p>
<p>Toronto, ON - Text added here.</p>
</body>
</content>
Update:
In case the problem is caused by a space-like character(s), here is a solution that will replace the unwanted (up to 40 per text node) characters with spaces and then normalize-space() would do its work:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vAllowed" select=
"concat('ABCDEFGHIJKLMNOPQRSTUVWXUZ', 'abcdefghijklmnopqrstuvwxyz',
'0123456789.,-')"/>
<xsl:variable name="vSpaces" select="' '"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(text()[2])]/text()">
<xsl:value-of select=
"normalize-space(translate(., translate(.,$vAllowed,''), $vSpaces))"/>
</xsl:template>
</xsl:stylesheet>
When the transformation is applied on this source XML document:
<content>
<body>
<p>This \\\ is a ~~~ Test</p>
<p>
Toronto, ``` ON - Text added here.
</p>
</body>
the wanted, correct result is produced:
<content>
<body>
<p>This is a Test</p>
<p>Toronto, ON - Text added here.</p>
</body>
</content>

Dimitre's answer is of course great, but there is a typo. In $vAllowed he's missing a capital "Y."
Also for my own purposes, I needed a single quote or apostrophe to be an allowed character. I added it to the variable like so:
<xsl:variable name="vAllowed" select=
"concat('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz',
'0123456789.,-', '&apos;&apos;')"/>

Related

Creating text from character code in XSLT

I am transforming documents from one XML standard to another. One of the requirements is that a numbered list uses alphabetical instead of roman numbering. I can create the unicode from the list position but it seems impossible to insert those into the XML output.
Maybe I am simply not seeing the obvious solution here. In JavaScript I would be using the fromCharCode( ) function but I have not yet found a similar option in XSLT.
I am using XSL 2.0 but could also use 3.0 if needed to make things easier.
I created a simple XML plus XSL to show the problem I need to solve. The actual files are much too big to be sharing here.
Sample XML input document:
<doc>
<p>Some text</p>
<p>Some more text</p>
</doc>
Required output:
<doc>
<list>
<list-item>
<label>A</label>
<p>Some text</p>
</list-item>
<list-item>
<label>B</label>
<p>Some more text</p>
</list-item>
</list>
</doc>
I have tried this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="/doc">
<xsl:copy>
<list>
<xsl:apply-templates select="p"/>
</list>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<xsl:variable name="number" as="xs:integer" select="position()"/>
<list-item>
<label>
<xsl:value-of select="concat('&#',$number + 65,';')" disable-output-escaping="yes"/>
</label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
</xsl:stylesheet>
I have used disable-output-escaping on other generated items before (e.g. to put angular brackets into the XML output), but it seems that the standard character codes are not being handled in the same way. My output from the above XSL shows up like this:
doc>
<list>
<list-item>
<label>B</label>
<p>Some text</p>
</list-item>
<list-item>
<label>C</label>
<p>Some more text</p>
</list-item>
</list>
</doc>
Is there any way to do this elegantly (i.e. without me having to create a huge xsl:choose to call out every possible integer label value and then explicitly creating the alphabetical labels?
You can use codepoints-to-string() and have it convert the number into the letter.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/doc">
<xsl:copy>
<list>
<xsl:apply-templates select="p"/>
</list>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<list-item>
<label>
<xsl:value-of select="codepoints-to-string(position() + 64)"/>
</label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
</xsl:stylesheet>
Or you could use xsl:number with value="position()" and format="A"
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/doc">
<xsl:copy>
<list>
<xsl:apply-templates select="p"/>
</list>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<list-item>
<label>
<xsl:number format="A" value="position()"/>
</label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
</xsl:stylesheet>
A quick solution for values up to 26 (='Z') would be
<xsl:template match="p">
<xsl:variable name="alphabet" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:variable name="number" as="xs:integer" select="position()"/>
<list-item>
<label><xsl:value-of select="substring($alphabet,$number,1)"/></label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
The output is as desired.
This could be extended to 26*26 by using a div and an xsl:if.

How to generate HTML based on the output of template processing (instead of source document)

I need to do two things:
Remove duplicate notes from source XML
Generate HTML document with values from the source after the transformation in step 1 (duplicates has been removed)
I have a working solution for each of the two steps in separate XSLT-files but I cannot figure out how to combine the operations in one XSLT-file - so the output of step 1 is used as the input of step 2.
Source data (XML):
<products>
<product>
<price>200EUR</price>
<size>XL</size>
<skuid>453</skuid>
</product>
<product>
<price>200EUR</price>
<size>XL</size>
<skuid>453</skuid>
</product>
</products>
Step 1 file for taking only unique products based on id (XSLT)
<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:key name="skukey" match="product" use="skuid"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"product[not(generate-id() = generate-id(key('skukey', skuid)[1]))]"
/>
</xsl:stylesheet>
Step 2 file for generating HTML (XSLT)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="products/product">
<p>
<xsl:value-of select="price"/>
<xsl:value-of select="size"/>
</p>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Why not simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="skukey" match="product" use="skuid"/>
<xsl:template match="/products">
<html>
<body>
<xsl:for-each select="product[generate-id() = generate-id(key('skukey', skuid)[1])]">
<p>
<xsl:value-of select="price"/>
<xsl:value-of select="size"/>
</p>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
You probably want to insert some kind of separator between price and size.

xslt: trying to understand conditional inside value-of

I'm expecting only Hola to appear:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="helloworld.xslt"?>
<greetings>
<greeting id="1">
Hello World!
</greeting>
<greeting id="2">
Hola!
</greeting>
</greetings>
However, both greetings appear.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html"/>
<xsl:template match="greetings">
<xsl:apply-templates select="greeting"/>
</xsl:template>
<xsl:template match="greeting">
<html>
<body>
<h1>
<xsl:value-of select="#id[.>1]"/>
</h1>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
It is not so much the conditional that is causing a problem, but the fact your statement is selecting the attribute, and so the xsl:value-of will output the attribute (but only if the value is greater than 1)
What you need to do is move the conditional to your xsl:apply-templates, and then do <xsl:value-of select="." /> to get your "Hola" value
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="greetings">
<xsl:apply-templates select="greeting[#id > 1]"/>
</xsl:template>
<xsl:template match="greeting">
<html>
<body>
<h1>
<xsl:value-of select="."/>
</h1>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

How to group elements based on their siblings using XSLT

I am trying to group several elements based on a starting and ending attribute of their surrounding siblings.
Sample XML:
<list>
<item>One</item>
<item class="start">Two</item>
<item>Three</item>
<item class="end">Four</item>
<item>Five</item>
<item class="start">Six</item>
<item class="end">Seven</item>
<item>Eight</item>
</list>
Desired Result:
<body>
<p>One</p>
<div>
<p>Two</p>
<p>Three</p>
<p>Four</p>
</div>
<p>Five</p>
<div>
<p>Six</p>
<p>Seven</p>
</div>
<p>Eight</p>
</body>
I come close to the desired results with the following XSLT. However, the following-sibling match doesn't stop after it reaches the ending attribute. Also, the standard matching repeats the elements that were already output from the following-sibling match.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="2.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:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="list">
<body>
<xsl:apply-templates />
</body>
</xsl:template>
<xsl:template match="item">
<p>
<xsl:apply-templates />
</p>
</xsl:template>
<xsl:template match="item[#class='start']">
<div>
<p><xsl:apply-templates /></p>
<xsl:apply-templates select="following-sibling::*[not(preceding-sibling::*[1][#class='end'])]" />
</div>
</xsl:template>
</xsl:transform>
Since you're using XSLT 2.0, why don't you take advantage of it:
<xsl:stylesheet version="2.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:template match="/list">
<body>
<xsl:for-each-group select="item" group-starting-with="item[#class='start']">
<xsl:for-each-group select="current-group()" group-ending-with="item[#class='end']">
<xsl:choose>
<xsl:when test="count(current-group()) gt 1">
<div>
<xsl:apply-templates select="current-group()" />
</div>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:for-each-group>
</body>
</xsl:template>
<xsl:template match="item">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>

How to return text of node without child nodes text

I have xml like:
<item id="1">
<items>
<item id="2">Text2</item>
<item id="3">Text3</item>
</items>Text1
</item>
How to return text of <item id="1">('Text1')?
<xsl:value-of select="item/text()"/> returns nothing.
My XSLT is:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="item"/>
</body>
</html>
</xsl:template>
<xsl:template match="item">
<xsl:value-of select="text()"/>
</xsl:template>
</xsl:stylesheet>
I dont know what else to type to commit my edits
How to return text of <item id="1">('Text1')? <xsl:value-of
select="item/text()"/> returns nothing.
The item element has more than one text-node children and the first of them happens to be a all-whitespace one -- this is why you get "nothing".
One way to test if the string value of a node isn't all-whitespace is by using the normalize-space() function.
In a single Xpath expression, you want this:
/*/text()[normalize-space()][1]
Here is a complete transformation the result of which is the desired text node:
<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:copy-of select="text()[normalize-space()][1]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<item id="1">
<items>
<item id="2">Text2</item>
<item id="3">Text3</item>
</items>Text1
</item>
the wanted, correct result is produced:
Text1
This should generally work:
<xsl:apply-templates select="item/text()" />
Incorporated into your XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item_key" match="item" use="."/>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<html>
<body>
<ul>
<xsl:apply-templates select="item"/>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="item">
<li>
<xsl:apply-templates select="text()"/>
</li>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, the result is:
<html>
<body>
<ul>
<li>Text1
</li>
</ul>
</body>
</html>
Alternatively, this should work as well:
<xsl:copy-of select="item/text()" />