XSLT 2.0 to 1.0 - xslt

Lots of help here to convert XSLT 1.0 to 2.0, but I need to go the other way!
OK, I have an XSL file working in XSLT 2.0: I need to convert this file to XSLT 1.0. Right now, I know the transformation is getting hung up on "result-document" but I'm not sure how to fix this. And, I'm not sure if my other info will translate to 1.0: will my value-of select= statements still work? help! (thanks)!
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:result-document href="output.xml" method="xml">
<playlist>
<xsl:for-each select="//dict[preceding-sibling::*[1]='Tracks']/dict">
<Track>
<Artist>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Artist']"/>
</Artist>
<Album>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Album']"/>
</Album>
<Songname>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Name']"/>
</Songname>
<TrackID>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Track ID']"/>
</TrackID>
<TagID>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Tag ID']"/>
</TagID>
</Track>
</xsl:for-each>
<xsl:for-each select="//dict[preceding-sibling::*[1]='Upload Information']">
<Description>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Playlist Description']"/>
</Description>
<Title>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Playlist Title']"/>
</Title>
<Username>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Username']"/>
</Username>
<Token>
<xsl:value-of select="child::*[preceding-sibling::*[1] = 'Token']"/>
</Token>
</xsl:for-each>
</playlist>
</xsl:result-document>
</xsl:template>

Well considering that result-document is new in XSLT 2.0 and that XSLT 1.0 without processor specific extension does not allow creating of multiple result documents at all it is in general not possible to translate stylesheets making use of result-document to XSLT 1.0.
However in your snippet the result-document instruction is inside the template matching the single / document node so you could simply remove it and use
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<playlist>
<xsl:for-each select="//dict[preceding-sibling::*[1]='Tracks']/dict">
<Track>
<Artist>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Artist']"/>
</Artist>
<Album>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Album']"/>
</Album>
<Songname>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Name']"/>
</Songname>
<TrackID>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Track ID']"/>
</TrackID>
<TagID>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Tag ID']"/>
</TagID>
</Track>
</xsl:for-each>
<xsl:for-each select="//dict[preceding-sibling::*[1]='Upload Information']">
<Description>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Playlist Description']"/>
</Description>
<Title>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Playlist Title']"/>
</Title>
<Username>
<xsl:value-of select="*[preceding-sibling::*[1][local-name()='key'] = 'Username']"/>
</Username>
<Token>
<xsl:value-of select="child::*[preceding-sibling::*[1] = 'Token']"/>
</Token>
</xsl:for-each>
</playlist>
</xsl:template>
Of course you would then need to ensure you run your XSLT processor with the right arguments to write its result to output.xml.
As for value-of, check whether any of the used expressions selects multiple nodes in your input XML documents. But you only need to check if the stylesheet had once version="2.0" and was run that way with an XSLT 2.0 processor. Your posted snippet has version="1.0", that way even an XSLT 2.0 processor would run it in backwards compatible mode where value-of semantics (output string value of first selected node) is used.
So the value-of only need adaption if with version="2.0" they output multiple values and you want to preserve that with version="1.0". You need to use for-each then e.g. by replacing
<xsl:value-of select="child::*[preceding-sibling::*[1] = 'Token']"/>
with
<xsl:for-each select="child::*[preceding-sibling::*[1] = 'Token']">
<xsl:if test="position() > 1"><xsl:text> </xsl:text></xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>

The only problem with your code with xslt-1.0 would be the <xsl:result-document href="output.xml" method="xml">. This is not supported for xslt-1.0
One possible solution could be to remove this (xsl:result-document) and redirect the "stdout" output to a file. This depends on the tools you are using.
Also some xlst processor support some extensions. For example wiht xsltproc you can replace
xsl:result-document with xsl:document.

Related

How can I write a whitespace-only text node in XML output of XSLT

We have a messaging pipeline which include XML-to-XML transforms.
For a source document like this (which may also be in one line without formatting):
<doc>
<a>Foo</a>
<b>Bar1</b>
<b>Bar2</b>
<b>Bar3</b>
<c>Baz</c>
</doc>
I need the XML output of the transform to be (note the line breaks):
<x>Bar1
Bar2
Bar3</x>
But the output I'm getting is:
<x>Bar1Bar2Bar3</x>
The stylesheet looks 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 omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<x>
<xsl:for-each select="//b">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:text>
</xsl:text> <!-- something wrong here? -->
</xsl:if>
</xsl:for-each>
</x>
</xsl:template>
</xsl:stylesheet>
If I add a non-whitespace character to the text node then I end up with the new-line being preserved. So, if I modify the xsl:text node to (note the added hyphen):
<xsl:text>-
</xsl:text>
then I get the output:
<x>Bar1-
Bar2-
Bar3</x>
How can I generate the desired output?
Note that we're limited to XSLT 1.0.
Update
I've done some more testing. Below is full code to reproduce the issue. Interestingly, this code reproduces the issue when run under .Net Framework 4.5 and .Net Core 2.1, but it gives the desired output when run under Mono.
using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Xsl;
namespace xslt
{
class Program
{
static void Main(string[] args)
{
var doc = new XmlDocument();
doc.LoadXml(#"<doc><a>Foo</a><b>Bar1</b><b>Bar2</b><b>Bar3</b><c>Baz</c></doc>");
var xsl = new XmlDocument();
xsl.LoadXml(#"<?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' method='xml' version='1.0' />
<xsl:template match='/'>
<x>
<xsl:for-each select='//b'>
<xsl:value-of select='.' />
<xsl:if test='position() != last()'>
<xsl:text>
</xsl:text> <!-- something wrong here? -->
</xsl:if>
</xsl:for-each>
</x>
</xsl:template>
</xsl:stylesheet>");
var xslt = new XslCompiledTransform();
xslt.Load(xsl);
using (var stream = new MemoryStream())
{
xslt.Transform(doc, null, stream);
Console.WriteLine(Encoding.UTF8.GetString(stream.ToArray()));
}
}
}
}
How can I preserve whitespace-only text node in XML output of XSLT
If you really want to preserve the text() nodes between the b elements, you can match them with the XPath expression
text()[preceding::*[1][self::b]][following::*[1][self::b]]
and copy their whole content with an xsl:copy-of. The whole set of templates could look like this:
<xsl:template match="/doc">
<x>
<xsl:apply-templates select="node()|#*" />
</x>
</xsl:template>
<xsl:template match="b">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="text()" />
<xsl:template match="text()[preceding::*[1][self::b]][following::*[1][self::b]]">
<xsl:copy-of select="." />
</xsl:template>
This copies also the whitespaces in between and not only the newlines, so the output looks like
<x>Bar1-
Bar2
Bar3</x>
I was able to get this working by adding a script block to the stylesheet to build the newline-separated value.
I'm still interested to know if it's possible with pure XSL.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<x>
<xsl:value-of select='userCSharp:JoinLines(//b)' />
</x>
</xsl:template>
<msxsl:script language="C#" implements-prefix="userCSharp">
<![CDATA[
public string JoinLines(XPathNodeIterator nodes)
{
var builder = new StringBuilder();
while (nodes.MoveNext())
{
builder.AppendLine(nodes.Current.Value);
}
return builder.ToString().Trim();
}
]]>
</msxsl:script>
</xsl:stylesheet>

Otherwise-statement in XSLT-choose is not applied/working

I have an XML-document with a type-node whose value is either "1" or "2":
<MyDoc>
<foo>
<bar>
<type>2</type>
</bar>
</foo>
</MyDoc>
I want to set a variable typeBool depending on the value of the type-node, if it is "1" it should be set to false, if it's "2" to true.
With the XSLT-choose-Element it should be possible to test for the current value and set typeBool according to the outcome.
I'm trying to do this with the following construct in XSLT 2.0, but I'm puzzled that the "otherwise"-path is not applied and I get an error that typeBool is not created:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="type" select="/MyDoc/foo/bar/type/text()"/>
<xsl:choose>
<xsl:when test="$type = '2'">
<xsl:variable name="typeBool">true</xsl:variable>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="typeBool">false</xsl:variable>
</xsl:otherwise>
</xsl:choose>
<h1><b><xsl:value-of select="$typeBool"/></b></h1>
</xsl:transform>
This is the transformation error I get:
error during xslt transformation:
Source location: line 0, col 0 Description:
No variable with name typeBool exists
As you currently present your problem, an xsl:choose is not needed and it unnecessarily complicates your XSLT code. Your actual problem might be more intricate though.
You can write a template that matches the element you are interested in (for instance, type elements) and then simply select the value of a comparison that will evaluate to either true or false.
XSLT Stylesheet
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="type">
<h1>
<b>
<xsl:value-of select=". = '2'"/>
</b>
</h1>
</xsl:template>
<xsl:template match="text()"/>
</xsl:transform>
HTML Output
<h1><b>true</b></h1>
Try it online here.
With choose-element
The choose-clause has to be defined inside of the variable-declaration:
<xsl:variable name="type">
<xsl:value-of select="/MyDoc/foo/bar/type/text()"/>
</xsl:variable>
<xsl:variable name="typeBool">
<xsl:choose>
<xsl:when test="$type = '2'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:variable>
The conditional also looks cleaner this way.
With XSLT 2.0
#MichaelKay pointed out that in XSLT 2.0 a xpath-conditional can be used, which is even simpler:
<xsl:variable name="type">
<xsl:value-of select="/MyDoc/foo/bar/type/text()"/>
</xsl:variable>
<h1>
<b>
<xsl:value-of select="select="if($type=2) then 'true' else 'false'"/>
</b>
</h1>

Multiply nodes value using XSLT

I need to get a value which is coming from two different nodes in the same XML file. For instance, my xml:
<asset>
<curr_wanted>EUR</curr_wanted>
<curr>USD</curr>
<value>50</value>
</asset>
<exchangeRates>
<USD>
<USD>1</USD>
<EUR>0.73</EUR>
</USD>
</exchangeRates>
and I want to get equivalent of 50 Dollars in Euro.
I tried :
<xsl:value-of select="(Asset/value * /exchangeRates[node() = curr]/curr_wanted)"/>
But it didn't work. Also I have to use XSLT 1.0. How can I get that value in Euro?
I did not test it very much but for input like
<?xml version="1.0" encoding="UTF-8"?>
<root>
<asset>
<curr_wanted>EUR</curr_wanted>
<curr>USD</curr>
<value>50</value>
</asset>
<asset>
<curr_wanted>EUR</curr_wanted>
<curr>USD</curr>
<value>25</value>
</asset>
<exchangeRates>
<USD>
<USD>1</USD>
<EUR>0.73</EUR>
</USD>
</exchangeRates>
</root>
something like following could work
for $asset in /root/asset, $rate in /root/exchangeRates
return $asset/value*$rate/*[name() = $asset/curr]/*[name() = $asset/curr_wanted]
But it will work only in xpath 2.0 and it also depends on the whole input xml (like if there might exist more asset elements, more exchangeRates elements, etc.).
Edit: In xslt 1.0 you could use xsl:variable to store some information and prevent them from context changes during xpath evaluation. Look for example at following template
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="text" />
<!-- Store "exchangeRates" in a global variable-->
<xsl:variable name="rates" select="/root/exchangeRates" />
<xsl:template match="/root">
<xsl:apply-templates select="asset" />
</xsl:template>
<xsl:template match="asset">
<!-- Store necessary values into local variables -->
<xsl:variable name="currentValue" select="value" />
<xsl:variable name="currentCurrency" select="curr" />
<xsl:variable name="wantedCurrency" select="curr_wanted" />
<xsl:variable name="rate" select="$rates/*[name() = $currentCurrency]/*[name() = $wantedCurrency]" />
<!-- Some text to visualize results -->
<xsl:value-of select="$currentValue" />
<xsl:text> </xsl:text>
<xsl:value-of select="$currentCurrency" />
<xsl:text> = </xsl:text>
<!-- using variable to prevent context changes during xpath evaluation -->
<xsl:value-of select="$currentValue * $rate" />
<!-- Some text to visualize results -->
<xsl:text> </xsl:text>
<xsl:value-of select="$wantedCurrency" />
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
which produces following output for input xml above.
50 USD = 36.5 EUR
25 USD = 18.25 EUR

XSLT: Replace string with Abbreviations

I would like to know how to replace the string with the abbreviations.
My XML looks like below
<concept reltype="CONTAINS" name="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" type="NUM">
<code meaning="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" value="18074-5" schema="LN" />
<measurement value="5.7585187646">
<code value="cm" schema="UCUM" />
</measurement>
<content>
<concept reltype="HAS ACQ CONTEXT" name="Image Mode" type="CODE">
<code meaning="Image Mode" value="G-0373" schema="SRT" />
<code meaning="2D mode" value="G-03A2" schema="SRT" />
</concept>
</content>
</concept>
and I am selecting some value from the xml like,
<xsl:value-of select="concept/measurement/code/#value"/>
Now what I want is, I have to replace cm with centimeter. I have so many words like this. I would like to have a xml for abbreviations and replace from them.
I saw one similar example here.
Using a Map in XSL for expanding abbreviations
But it replaces node text, but I have text as attribute. Also, it would be better for me If I can find and replace when I select text using xsl:valueof select instead of having a separate xsl:template. Please help. I am new to xslt.
I have created XSLT v "1.1". For abbreviations I have created XML file as you have mentioned:
Abbreviation.xml:
<Abbreviations>
<Abbreviation>
<Short>cm</Short>
<Full>centimeter</Full>
</Abbreviation>
<Abbreviation>
<Short>m</Short>
<Full>meter</Full>
</Abbreviation>
</Abbreviations>
XSLT:
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" />
<xsl:param name="AbbreviationDoc" select="document('Abbreviation.xml')"/>
<xsl:template match="/">
<xsl:call-template name="Convert">
<xsl:with-param name="present" select="concept/measurement/code/#value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="Convert">
<xsl:param name="present"/>
<xsl:choose>
<xsl:when test="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]">
<xsl:value-of select="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]/Full"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$present"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
INPUT:
as you have given <xsl:value-of select="concept/measurement/code/#value"/>
OUTPUT:
centimeter
You just need to enhance this Abbreviation.xml to keep short and full value of abbreviation and call 'Convert' template with passing current value to get desired output.
Here a little shorter version:
- with abbreviations in xslt file
- make use of apply-templates with mode to make usage shorter.
But with xslt 1.0 node-set extension is required.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="abbreviations_txt">
<abbreviation abbrev="cm" >centimeter</abbreviation>
<abbreviation abbrev="m" >meter</abbreviation>
</xsl:variable>
<xsl:variable name="abbreviations" select="exsl:node-set($abbreviations_txt)" />
<xsl:template match="/">
<xsl:apply-templates select="concept/measurement/code/#value" mode="abbrev_to_text"/>
</xsl:template>
<xsl:template match="* | #*" mode="abbrev_to_text">
<xsl:variable name="abbrev" select="." />
<xsl:variable name="long_text" select="$abbreviations//abbreviation[#abbrev = $abbrev]/text()" />
<xsl:value-of select="$long_text"/>
<xsl:if test="not ($long_text)">
<xsl:value-of select="$abbrev"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Setting disable-output-escaping="yes" for every xsl:text tag in the xml

say I have the following xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<display>
<xsl:for-each select="logline_t">
<xsl:text disable-output-escaping="yes"><</xsl:text> <xsl:value-of select="./line_1" <xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text> <xsl:value-of select="./line_2" <xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text> <xsl:value-of select="./line_3" <xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:for-each>
</display>
</xsl:template>
</xsl:stylesheet>
Is there a way to set disable-output-escaping="yes" to all of the xsl:text that appear in the document?
I know there is an option to put
< xsl:output method="text"/ >
and every time something like
& lt;
appears, a < will appear, but the thing is that sometimes in the values of line_1, line_2 or line_3, there is a "$lt;" that I don't want changed (this is, I only need whatever is between to be changed)
This is what I'm trying to accomplish. I have this xml:
<readlog_l>
<logline_t>
<hora>16:01:09</hora>
<texto>Call-ID: 663903<hola>396#127.0.0.1</texto>
</logline_t>
</readlog_l>
And this translation:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<display>
<screen name="<xsl:value-of select="name(.)"/>">
<xsl:for-each select="logline_t">
< field name="<xsl:for-each select="*"><xsl:value-of select="."/></xsl:for-each>" value="" type="label"/>
</xsl:for-each>
</screen>
</display>
</xsl:template>
</xsl:stylesheet>
I want this to be the output:
<?xml version="1.0"?>
<display>
<screen name="readlog_l">
<field name="16:01:09 Call-ID: 663903<hola>396#127.0.0.1 " value="" type="label">
</screen>
</display>
Note that I need the "<" inside the field name not to be escaped, this is why I can't use output method text.
Also, note that this is an example and the translations are much bigger, so this is why I'm trying to find out how not to write disable-output-escaping for every '<' or '>' I need.
Thanks!
Thanks for clarifying the question. In this case, I'm fairly sure there's no need to disable output escaping. XSLT was designed to accomplish what you're doing:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<display>
<screen name="{name(.)}">
<xsl:for-each select="logline_t">
<xsl:variable name="nameContent">
<xsl:for-each select="*">
<xsl:if test="position() > 1"><xsl:text> </xsl:text></xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:variable>
<field name="{$nameContent}" value="" type="label" />
</xsl:for-each>
</screen>
</display>
</xsl:template>
</xsl:stylesheet>
I'm a bit unclear on this point:
Note that I need the "<" inside the field name not to be escaped, this is why I can't use output method text.
Which < are you referring to? Is it the < and > around "hola"? If you left those unescaped you would wind up with invalid XML. It also looks like the name attribute in your sample output have a lot of values that aren't in the input XML. Where did those come from?
Given your expected output you don't need d-o-e at all for this. Here is a possible solution that doesn't use d-o-e, and is based on templates rather than for-each:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/*">
<display>
<screen name="{name(.)}">
<xsl:apply-templates select="logline_t"/>
</screen>
</display>
</xsl:template>
<xsl:template match="logline_t">
<field value="" type="label">
<xsl:attribute name="name">
<xsl:apply-templates select="*" mode="fieldvalue"/>
</xsl:attribute>
</field>
</xsl:template>
<xsl:template match="*[last()]" mode="fieldvalue">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="*" mode="fieldvalue">
<xsl:value-of select="." />
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
If you want to set d-o-e on everything, that suggests you are trying to generate markup "by hand". I don't think that's a particularly good idea (in fact, I think it's a lousy idea), but if it's what you want to do, I would suggest using the text output method instead of the xml output method. That way, no escaping of special characters takes place, and therefore it doesn't need to be disabled.