XSLT <xsl: value-of> generates extra line-breaks - xslt

In XSLT, using , it generates a line break before the rendered value and another one after it.
Here comes an example:
<xsl:when test="name(.) = 'Item'">
"<xsl:value-of select="./Item/Data[last()]/text()"/>"
</xsl:when>
And the rendered result is:
"
09/07/2012
"
As you can see, it puts two line breaks before and after the result value, while the desired result is:
"09/07/2012"
The original input is :
Here comes the original input, sorry for that.
<Item>
<Item>
<Data>105</Data>
<Data>09/07/2012</Data>
</Item>
</Item>
I'm executing this XSLT within an Oracle Server Bus
Any help will be appreciated.

The extra space is could also be coming from the selected text. Use normalize-space() to remove this.
<xsl:value-of select="normalize-space(./Item/Data[last()]/text())"/>
Edit Overnuts is correct in using <xsl:text> around the quotes, otherwise the Xslt processor will preserve the newline before the opening / after the closing quotes. However, I still can't see why a newline could get in between the quotes and your xsl:value-of?
I've tried the following
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/xml" xml:space="default">
<xsl:apply-templates select="*" />
</xsl:template>
<xsl:template match="*" xml:space="default">
<xsl:choose>
<xsl:when test="name(.) = 'Item'">
<xsl:text>"</xsl:text>
<xsl:value-of select="normalize-space(./Item/Data[last()]/text())"/>
<xsl:text>"</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When run with this XML:
<xml>
<Item>
<Item>
<Data>105</Data>
<Data>09/07/2012</Data>
</Item>
</Item>
</xml>
Produces "09/07/2012"

I think you can try:
everything on a single line (quick and dirty):
<xsl:when test="name(.) = 'Item'">"<xsl:value-of select="./Item/Data[last()]/text()"/>"</xsl:when>
or use tags like this (best practice):
<xsl:when test="name(.) = 'Item'">
<xsl:text>"</xsl:text>
<xsl:value-of select="./Item/Data[last()]/text()"/>
<xsl:text>"</xsl:text>
</xsl:when>

Possibly the original XML soure contains these newlines (indentation), try something like:
<xsl:value-of select="concat('~', normalize-space(./Item/Data[last()]/text()), '~')"/>

Perhaps an implementation specific bug?
Using xsltproc all the above work as expected, although the expected results of bare newline+whitespace+quote+date+quote+newline+space is for the /external/ white-space to be copied as well. All the other examples produce the same 13 bytes, including a trailing newline.
Using libxml 20706, libxslt 10124 and libexslt 813
xsltproc was compiled against libxml 20701, libxslt 10124 and libexslt 813
libxslt 10124 was compiled against libxml 20701
libexslt 813 was compiled against libxml 20701

Related

XSLT check values in for each and set value of element

I am not expert in XSLT. I have requirement to check all "code" values, set statuscode as per below rules.
If all code values are 200, then set statuscode to 200 and reasonphrase to success
If all code values are either 200 or 204, then set status code to 200 and reasonphrase is concatnatiopn of all reason which have code other than 200.
If atleast one code contains value other than 200 and 204, then set statuscode to 503 and reasonphrase is concatnatiopn of all reason which have code other than 200.
I have tried couple of ways which are to store all code values in one variable and execeute contains function with above conditions as well as create code variables and store values and then check string length. But i did not get any success.
I am looking for some more generic way if possible as this requirement is part of complex xslt and below is just an example of requirement.
Once i get logic for below code, i should be able to fit logic in complex xslt.
I also tried to search in answers but could not get any solution which fits this requirement.
I am looking for solution in xslt 1.0 as other part of xslt is written in xslt 1.0
Input -
<root>
<Node1>
<code>200</code>
<reason>Success</reason>
</Node1>
<Node1>
<code>200</code>
<reason>Success</reason>
</Node1>
<Node1>
<code>204</code>
<reason>Business Error</reason>
</Node1>
<Node1>
<code>500</code>
<reason>Tech Error</reason>
</Node1>
<Node1>
<code>200</code>
<reason>Success</reason>
</Node1>
</root>
Output-
<root>
<output>
<statuscode></statuscode>
<reasonphrase></reasonphrase>
</output>
</root>
Thank you.
Try this as your starting point:
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="*"/>
<xsl:template match="/root">
<xsl:copy>
<output>
<xsl:choose>
<xsl:when test="not(Node1[code!=200])">
<!-- all code values are 200 -->
<statuscode>200</statuscode>
<reasonphrase>success</reasonphrase>
</xsl:when>
<xsl:when test="not(Node1[code!=200 and code!=204])">
<!-- all code values are either 200 or 204 -->
<statuscode>200</statuscode>
<reasonphrase>
<xsl:for-each select="Node1[code!=200]">
<xsl:value-of select="code"/>
<xsl:if test="position()!=last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</reasonphrase>
</xsl:when>
<xsl:otherwise>
<statuscode>503</statuscode>
<reasonphrase>
<xsl:for-each select="Node1[code!=200]">
<xsl:value-of select="code"/>
<xsl:if test="position()!=last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</reasonphrase>
</xsl:otherwise>
</xsl:choose>
</output>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that this could be streamlined to eliminate repeating code - but the point here is to show how to test for the given conditions.

XSLT tokenize - capturing the separators

here is a piece of code in XSL which tokenizes a text into fragments separated by interpunction and similar characters. I'd like to ask if there is a possibility to somehow capture the strings by which the text was tokenized, for example the comma or dot etc.
<xsl:stylesheet version="2.0" exclude-result-prefixes="xs xdt err fn" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:err="http://www.w3.org/2005/xqt-errors" xmlns:xdt="http://www.w3.org/2005/xpath-datatypes">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="GENERUJ">
<TEXT>
<xsl:variable name="text">
<xsl:value-of select="normalize-space(unparsed-text(#filename, 'UTF-8'))" disable-output-escaping="yes"/>
</xsl:variable>
<xsl:for-each select="tokenize($text, '(\s+("|\(|\[|\{))|(("|,|;|:|\s\-|\)|\]|\})\s+)|((\.|\?|!|;)"?\s*)' )">
<xsl:choose>
<xsl:when test="string-length(.)>0">
<FRAGMENT>
<CONTENT>
<xsl:value-of select="."/>
</CONTENT>
<LENGTH>
<xsl:value-of select="string-length(.)"/>
</LENGTH>
</FRAGMENT>
</xsl:when>
<xsl:otherwise>
<FRAGMENT_COUNT>
<xsl:value-of select="last()-1"/>
</FRAGMENT_COUNT>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</TEXT>
</xsl:template>
As you see the constructed tags CONTENTS, LENGTH, I'd like to add one called SEPARATOR if you know what I mean. I couldnt find any answer to this on the internet and I'm just a beginner with xsl transformations so I'm looking for a quick solution. Thank you in advance.
The tokenize() function doesn't allow you to discover what the separators were. If you need to know, you will need to use xsl:analyze-string instead. If you use the same regex as for tokenize(), this passes the "tokens" to the xsl:non-matching-substring instruction and the "separators" to the xsl:matching-substring instruction.

Check for CR or LF in XSLT

I have a input XML like this :
<in_xml>
<company>
<project>
ProjNo1
ProjNo2
ProjNo3
</project>
</company>
</in_xml>
A simple XSLT is applied to this source, which writes another XML with the value of Project Tag.
The Project tag in input xml has three lines , it could be one or more line(s). I am looking at way for the XSLT to read only the first line, in case there are more than one and write the first line in the output xml.
The current XSLT is very simple as it just reads the Project tag and spits out the value, hence the code is not attached.
Regards.
I have added the answer to the question, see below #Maestro's answer.
If you are in the happy circumstances of being able to apply XSLT 2.0, the following may help:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="//project/text()">
<xsl:value-of select="tokenize(normalize-space(.),' ')[1]" />
</xsl:template>
</xsl:stylesheet>
Explanation: first normalize-space() to replace all whitespace strings by a single blank (and cut off leading and trailing whitespace), then split into words, then take the first one.
In XSLT 1.0 you could use
<xsl:value-of select="substring-before(normalize-space(.), ' ')"/>
instead. Less flexible if the second word has to be selected, but for the first word it works OK.
EDIT
you asked how to retrieve a first line in XSLT 1.0 - problem here is the leading whitespace which may contain a LF so you cannot just substring-before the first LF.
The below can probably be improved upon, but it works fine:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="//project/text()">
<xsl:variable name="afterLeadingWS"
select="substring-after(., substring-before(.,substring-before(normalize-space(.), ' ')))"/>
<xsl:choose>
<xsl:when test="contains($afterLeadingWS, '
')">
<xsl:value-of select="substring-before($afterLeadingWS, '
')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$afterLeadingWS"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Explanation: first get first word as before, then determine the whitespace before that first word, then get everything after that leading whitespace, then get the first line, which is the string before a LF character. It may just happen that there is no LF except maybe in the leading whitespace, hence the choose function.
first up thanks to #Maestro for taking time out and helping me, appreciate your help.
Here is the code that I used to get the first line from a paragraph of text that has a CR:
<xsl:variable name="projNumber" select="ProjectNumber" />
<xsl:variable name="crlf" select="'
'" />
<xsl:choose>
<xsl:when test="contains($projNumber,$crlf)">
<xsl:value-of select="substring-before($projNumber,$crlf)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$projNumber"/>
</xsl:otherwise>
</xsl:choose>
This can be written as a function but I don't know how to do it, maybe someone can guide but there you go. A better approach that a colleague of mine suggested is to escape the CR and directly use it in substring function this would avoid all those variables in the first place.
Thanks again.
Here's the code again, now with a function getFirstLine.
Note the addtional namespace that is needed.
Also note that this does require XSLT 2.0 (xsl:function is not available in 1.0).
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://temp.com/functions">
<xsl:output method="text"/>
<xsl:template match="//project/text()">
<xsl:value-of select="f:getFirstLine(.)"/>
</xsl:template>
<xsl:function name="f:getFirstLine">
<xsl:param name="input"/>
<xsl:variable name="afterLeadingWS" select="substring-after($input, substring-before($input,substring-before(normalize-space($input), ' ')))"/>
<xsl:choose>
<xsl:when test="contains($afterLeadingWS, '
')">
<xsl:value-of select="substring-before($afterLeadingWS, '
')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$afterLeadingWS"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
</xsl:stylesheet>

How to apply an XSLT transformation that includes spaces to an XML doc using tDOM?

I have some XML of the form:
<definitions devices="myDevice">
<reg offset="0x0000" mnem="someRegister">
<field mnem="someField" msb="31" lsb="24 />
...
</reg>
...
</definitions>
I want the XML to be the definitive reference and use XSLT to transform it to HTML for documentation, .h for building (and maybe other forms too).
The HTML version is working fine and produces a table per register, with a row per field:
... (header boilerplate removed)
<xsl:for-each select="definitions/reg">
<table>
<tr>
<th><xsl:value-of select="#offset"/></th>
<th><xsl:value-of select="#mnem"/></th>
</tr>
<xsl:for-each select="field">
<tr>
<td><xsl:value-of select="#msb"/>..<xsl:value-of select="#lsb"/></td>
<td><xsl:value-of select="#mnem"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
Converting to a .h isn't going so well. I'm completely failing to generate the required spaces in the output:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
#define <xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I'd hope for that to produce the output:
#define SOMEREGISTER 0x0000
But I actually get:
#define SOMEREGISTER0x0000
I don't understand why I get the space after the '#define', but not the one after the transformed mnemonic. I've tried a simpler solution with just an inline space, with the same results.
I'm too new to this (XSLT) to know whether I'm a) doing it wrong or b) finding a limitation in tDOM.
Testing with this:
# I could have read these from a file I suppose...
set in {<definitions devices="myDevice">
<reg offset="0x0000" mnem="someRegister">
<field mnem="someField" msb="31" lsb="24" />
</reg>
</definitions>}
set ss {<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
<xsl:text>#define </xsl:text>
<xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text xml:space="preserve"> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>}
# Interesting code starts here
package require tdom
set indoc [dom parse $in]
set xslt [dom parse -keepEmpties $ss]
set outdoc [$indoc xslt $xslt]
puts [$outdoc asText]
I find that this works. The issue is that the tDOM parser doesn't handle the xml:space attribute correctly; without the magical -keepEmpties option, all the empty strings are stripped from the stylesheet and that leads to a wrong XSLT stylesheet being applied. But with the option, it appears to do the right thing.
Note that the XSLT engine itself is doing the right thing. It's the XML parser/DOM builder. (I think it's a bug; I'll look up where to report it.)
Per:
http://www.ibm.com/developerworks/xml/library/x-tipwhitesp/index.html
Try using the preserve space directive:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
<xsl:text xml:space="preserve">#define </xsl:text>
<xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text xml:space="preserve"> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You don't have an output method specified in your second stylesheet, so the default is gonna be XML. I'd advice you to use output method "text", then use <xsl:text> elements for any literal output. Check this example:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each select="definitions/reg"><xsl:text>#define </xsl:text><xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/><xsl:text> </xsl:text><xsl:value-of select="#offset"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EDIT: by the way, that
at the end is a character code. It's simply the decimal value of the ASCII code for a line feed. This makes sure you start a new line for the next reg entry. If you need the Windows/DOS convention (carriage return + line feed), use
instead.

How to remove empty lines in XSLT to CSV output

I have XML data which I have transformed using XSLT to output csv formatted text. My XSLT contains a <xsl:if test> clause to filter the result from the larger dataset of the input XML. This works but every line that is filtered out of the input is displayed as an empty line in the output.
I have tried <xsl:strip-space elements="*" /> and <xsl:template match="text()" /> but neither remove the empty lines.
A sample of my XML:
<?xml version="1.0" encoding="UTF-8"?>
<session_list xmlns="http://www.networkstreaming.com/namespaces/API" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<session lsid="fef672741e025ffda1acb3041f09252d">
<session_type>support</session_type>
<lseq>2899</lseq>
<start_time timestamp="1290027608">2010-11-17T16:00:08-05:00</start_time>
<end_time timestamp="1290027616">2010-11-17T16:00:16-05:00</end_time>
<duration>00:00:08</duration>
<public_site id="1">Default</public_site>
<external_key></external_key>
<session_chat_view_url>https://mysite.com/session_download.ns?lsid=l%3Dfef672741e025ffda1acb3041f09252d%3Bh%3D9bd6081f0b7fee08dcc32a58ef4cb54c7a0e233d%3Bt%3Dsd%3Bm%3Dchat&dl_action=chat&view=1&sessionType=sd</session_chat_view_url>
<session_chat_download_url>https://mysite.com/session_download.ns?lsid=l%3Dfef672741e025ffda1acb3041f09252d%3Bh%3D9bd6081f0b7fee08dcc32a58ef4cb54c7a0e233d%3Bt%3Dsd%3Bm%3Dchat&dl_action=chat&sessionType=sd</session_chat_download_url>
<file_transfer_count>0</file_transfer_count>
<primary_customer gsnumber="3">Smith, John</primary_customer>
<customer_list>
<customer gsnumber="3">
<username>Smith, John</username>
<public_ip>xxx.xxx.xxx.xxx</public_ip>
<private_ip>xxx.xxx.xxx.xxx</private_ip>
<hostname>DESKTOP</hostname>
<os>Windows 7 Enterprise x64 Edition (Build 7600)</os>
<primary_cust>1</primary_cust>
<info>
<name></name>
<company></company>
<company_code></company_code>
<issue></issue>
<details></details>
</info>
</customer>
</customer_list>
...etc...
</session>
</session_list>
My XSLT:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:bg="http://www.networkstreaming.com/namespaces/API" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:text>Session ID,</xsl:text>
<xsl:text>Start Time,</xsl:text>
<xsl:text>Duration,</xsl:text>
<xsl:text>Public Site,</xsl:text>
<xsl:text>Remedy Ticket Number,</xsl:text>
<xsl:text>Customer's Name,</xsl:text>
<xsl:text>Customer's Operating System,</xsl:text>
<xsl:text>Representative's Name</xsl:text>
<xsl:text>
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="bg:session_list">
<xsl:apply-templates select="bg:session"/>
</xsl:template>
<xsl:template match="bg:session">
<xsl:if test="contains(bg:customer_list/bg:customer/bg:os,'Click-To-Chat')">
<xsl:value-of select="bg:lseq"/>,<xsl:text/>
<xsl:value-of select="bg:start_time"/>,<xsl:text/>
<xsl:value-of select="bg:duration"/>,<xsl:text/>
<xsl:value-of select="bg:public_site"/>,<xsl:text/>
<xsl:value-of select="bg:external_key"/>,<xsl:text/>
<xsl:value-of select="bg:primary_customer"/>,<xsl:text/>
<xsl:value-of select="bg:customer_list/bg:customer/bg:os"/>,<xsl:text/>
<xsl:value-of select="bg:primary_rep"/>
</xsl:if>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
A sample of the output:
Session ID,Start Time,Duration,Public Site,Remedy Ticket Number,Customer's Name,Customer's Operating System,Representative's Name
6488,2011-03-14T09:17:13-04:00,00:00:06,Default,,User One,Windows® (x86) Click-To-Chat,Rep1
6489,2011-03-14T09:44:50-04:00,00:39:58,Default,,Nate,Windows® (x86) Click-To-Chat,Rep1
6494,2011-03-14T10:25:23-04:00,00:03:29,Default,,User TEST,Windows® (x86) Click-To-Chat,Rep1
6498,2011-03-14T11:01:36-04:00,,Default,,User Two,Windows® (x86) Click-To-Chat,Diane
Each of the lines between the printed lines is data that was filtered out because it didn't pass the <xsl:if test="contains(bg:customer_list/bg:customer/bg:os,'Click-To-Chat')"> but the empty lines are still printed. This output doesn't look very good when opened in excel.
Does anyone have any ideas how to remove them?
Thanks!
Have you tried putting the <xsl:text>
</xsl:text> line inside the xsl:if? This is the bit that puts a newline, so at the moment you are writing one even if you don't write the line.