Extract strings between two special charecter($) using XSLT 1.0 - xslt-1.0

Input: "abcdef$ghi$jklmno$pqrst$ wx $yx$"
Expected output:
<tokens>
<token>$ghi$</token>
<token>$pqrst</token>
<token>$yx$</token>
</tokens>
As per sample input string, output must be like Expected output by using XSLT 1.0
Thanks in Advance

For each pair of dollar signs (1,2), (3,4), ... (2 × n - 1, 2 × n) you want a token element with the text between dollar sign number 2 × n - 1 and dollar sign number 2 n.
One of two cases will necessarily apply.
Case 1: there is some fixed maximum value of n, and it's relatively small (say, less than 5 or 10). In this case a simple sequence of xs:variable assignments with appropriate calls to substring-before and substring-after should suffice, and you're done. You'll end up with something like this:
Let s0 be the original string, s1 the substring after the first dollar sign in s0, t1 the substring before the first dollar sign in s1, and r1 the substring after the first dollar sign in s1.
Now let s2 be the substring after the first dollar sign in r1, t2 the substring before the first dollar sign in s2, and r2 the substring after the first dollar sign in s2.
Repeat as needed: for i in 3 to n, let si be the substring after the first dollar sign in ri - 1, ti the substring before the first dollar sign in si, and ri the substring after the first dollar sign in si.
Case 2: there is no maximum n. In this case the straightforward approach in XSLT 1.0 is to write a named template which (a) drops the substring before the first dollar sign, (b) constructs a token element containing a dollar sign, the string after the first dollar sign and before second dollar sign, and (c) calls itself recursively with the substring after the second dollar sign. Add appropriate boundary conditions for empty strings, strings with odd numbers of dollar signs, and other input errors, and you're done.
So: go read up on the string functions of XSLT 1.0 and solve your problem. Come back (and show your work) if you have further trouble.

Hope the below xsl helps you. It works. I tried it myself.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="input">
<xsl:text>abcdef$ghi$jklmno$pqrst$ wx$yx$</xsl:text>
</xsl:variable>
<tokens>
<xsl:text>
</xsl:text>
<xsl:analyze-string select="$input" regex="(\$[a-z]+\$)">
<xsl:matching-substring>
<token>
<xsl:value-of select="regex-group(1)"/>
</token>
<xsl:text>
</xsl:text>
</xsl:matching-substring>
</xsl:analyze-string>
</tokens>
</xsl:template>
</xsl:stylesheet>

Related

XSLT: variables and "empty" labels

I have an XML datafile containing among other things a string of arbitrarily many comma separated values. I want those values to be displayed in a web browser as a list with one value per line. So I wrote an XSLT template that takes this string, displays the first value followed by a linebreak tag (<br/>), properly name-spaced, and resources with the remainder of the string. In effect, the commas are being replaced by HTML <br/> tags.
Now, when I store the result of calling that template in a xsl:variable, and display that through xsl:value-of, then the HTML tags disappear: what is shown is the string minus the commas.
When I display the result directly by having the xsl:call-template in place of the xsl:value-of, all is fine, and the values appear in a list.
So, what's going on?
Is this behavior an implementation artifact, or is it standard XSLT?
Use xsl:copy-of instead of xsl:value-of if you want to output nodes (like your br elements), xsl:value-of creates a simple text node with the string value(s) selected.
Here is an example that shows the difference between xsl:value-of and xsl:copy-of, you will note that it is not the use of the variable with newly created br elements that makes the difference, it is simply the use of xsl:value-of that creates a text() node with the string conversion of the selection:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes" version="5" doctype-system="about:legacy-doctype"/>
<xsl:variable name="var">Phrase 1.<br/>Phrase 2.<br/>Phrase 3.</xsl:variable>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<section>
<h1>Example 1: value-of</h1>
<xsl:value-of select="$var"/>
</section>
<section>
<h1>Example 2: copy-of</h1>
<xsl:copy-of select="$var"/>
</section>
<xsl:apply-templates select="//p"/>
<xsl:apply-templates select="//p" mode="copy-of"/>
</body>
</html>
</xsl:template>
<xsl:template match="p">
<section>
<h1>Example 1: value-of</h1>
<xsl:value-of select="."/>
</section>
</xsl:template>
<xsl:template match="p" mode="copy-of">
<section>
<h1>Example 1: copy-of</h1>
<xsl:copy-of select="."/>
</section>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/gWmuiJy/1
Output is
Example 1: value-of
Phrase 1.Phrase 2.Phrase 3.
Example 2: copy-of
Phrase 1.
Phrase 2.
Phrase 3.
Example 1: value-of
Line 1.Line 2.Line 3.
Example 1: copy-of
Line 1.
Line 2.
Line 3.
It seems that you hit the boundaries of the RTF ("Result tree fragment"):
When you use an XML fragment to initialize a variable or a parameter, then the variable or parameter is of the
"result tree fragment" datatype. This is an XSLT 1.0 specific datatype [just like node-set, but slightly different].
A result tree fragment is equivalent to a node-set that contains just the root node.
You cannot apply operators like "/", "//" or predicate on a result tree fragments. They are only applicable for node-set datatypes.
[...]
a) In XSLT 1.0
The resolution of this is to convert the result tree fragment into a node-set. I am not aware of any oracle specific xpath extension functions that can do this trick for you.
You could use EXSLT to achieve this.
b) Use XSLT 2.0
You can code your transformations in XSLT 2.0. XSLT 2.0 deprecates ResultTreeFragments i.e. if you are modeling an XSLT 2.0 transformation, and you create a variable or a parameter that holds a tree fragment, it is implicitly a node sequence.
So without using an XSLT version greater than 1, you're out of luck. So better use XSLT-2.0 or 3.0 to solve this problem.
Is this behavior an implementation artifact, or is it standard XSLT?
It is standard for XSLT-1.0, but not for XSLT-2.0+.

escaping an ampersand in xsl variable

I'm trying to encode certain text variables into a QR code using qrserver.com. (specifically, a gift message on a packing slip)
Here's the snippet of code relevant to the encoding:
<!-- gift message -->
<xsl:for-each select="Customer/Order/Item">
<xsl:for-each select="Option[Name='Gift Message (optional)']">
<xsl:variable name="test" select="translate(Description,'','')" />
<xsl:value-of select="concat('https://api.qrserver.com/v1/create-qr-code/?data=', $test,'&size=100x100&charset-source=UTF-8')" />
</xsl:for-each>
</xsl:for-each>
Occasionally the text will contain an ampersand, like "Merry Christmas & Happy New Year", so the url that is passed is
https://api.qrserver.c-o-m/v1/create-qr-code/?data=Merry Christmas & Happy New Year&size=100x100
and everything after the ampersand gets truncated. The result is a QR code that encodes "Merry Christmas" but no Happy New Year. I tried using the translate function to replace the ampersand with "and", but translate(Description,'&','and') only works on the first letter, so it replaces the ampersand with "a". This is in xsl 1.0, so I don't have the option of the replace function. Does anyone out there know how I can deal with these ampersands? Thanks in advance!

XSLT decimal-format causes exception

I am trying to use the xslt:decimal-format element, but I get the same error message whether I use my own code or the example code provided by w3schools.com. This is the w3 sample code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:decimal-format name="euro" decimal-separator="," grouping-separator="."/>
<xsl:template match="/">
<xsl:value-of select="format-number(26825.8, '#,###.00', 'euro')"/>
</xsl:template>
</xsl:stylesheet>
And this is the XsltException it produces when I run it in Visual Studio 2010:
"Format '#,###.00' cannot have zero digit symbol after digit symbol after decimal point."
What's wrong on my side that causes this error?
You have changed the decimal format, called "euro" so that a valid number looks like this "1.232,99" (one thousand, two hundred and thirty two, point nine-nine in words). This does not match the format you have requested which is "#,###.00".
Change your format-number pattern to "#.###,00"

setting flag in XSLT to detect the first time through a loop

New to xslt. Using it in umbraco, so it's version 1.0.
Trying to insert separators between links in an inline footer list, so will put them before each link except the first one. This is a simple programming problem, where you set a flag to 0 outside the loop, then once in, you test if the flag is 1 and if it's not, you skip adding a separator before the link. As the last bit in the first pass through the loop, you set the flag to 1.
How can I accomplish this in xslt? I'm a bit flummoxed by the syntax and reading that once a variable value is set you can't change it. Anyone have a simple exmaple?
Here is a simple example:
<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:param name="pBase" select="'http://myBase.com/'"/>
<xsl:template match="num">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is produced:
01, 02, 03, 04, 05, 06, 07, 08, 09, 10
and it is displayed in the browser as:
01, 02, 03, 04, 05, 06, 07, 08, 09, 10
Explanation:
XSLT is a functional language -- among other things this means that variables, once defined, cannot be changed.
The standard XPath position() function can be used to check if the position of the current node has a specific value.
The builtin XSLT templates are used and the one that matches an element has this body: <xsl:apply-templates/> . This is an abbreviation for: <xsl:apply-templates select="child::node()"/> and applies templates to all node children of the current node -- in document order.
Because we have specified <xsl:strip-space elements="*"/>, the whitespace-only children of any element has been stripped during the parsing of the XML document. This leaves only non-white-space children nodes of the top element nums and they are all num elements.
The value of the position() function inside a template is the position of the current node in the node-list formed when the <xsl:apply-templates> instruction is executed. This means that the value of the position() function inside the template matching num is 1 when the first num element is processed, 2 when the 2nd num element is processed, ..., 10 when the 10th num element is processed.
The generated output for all num elements where the value of position() is greater than 1 starts with the string ", " -- exactly as per the requirements of this question. For the first num element the string ", " isn't generated, because the value of position() in this case is 1.
There is no such thing as time in XSLT. You can't detect the "first time through a loop" because there is no concept of time, therefore no first time, in fact, it's not a loop in the traditional programming sense at all. All the items in the input list are conceptually processed in parallel. That's why you can't set variables for use in later iterations - there is no such thing as a "later iteration".
What you can do is to detect that you are processing the first item in the input sequence (or the first item in the input sequence after sorting, if you use xsl:sort). You do that with the test position() = 1.

XSLT Difference between value-of select and variable select

I need to flip the elements of two nodes. Originally the variable were set with the following command:
<xsl:variable name="matchesLeft" select="$questionObject/descendant::simpleMatchSet[position()=1]/simpleAssociableChoice"/>
<xsl:variable name="matchesRight" select="$questionObject/descendant::simpleMatchSet[position()=2]/simpleAssociableChoice"/>
I now want to flip the variable with the following code:
<xsl:variable name="matchesRight">
<xsl:choose>
<xsl:when test="$flippedQuestions='true'">
<xsl:value-of select="$questionObject/descendant::simpleMatchSet[position()=2]/simpleAssociableChoice"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$questionObject/descendant::simpleMatchSet[position()=1]/simpleAssociableChoice"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
But it only get the value from the first element and not all elements in the node. How can I achive this?
The problem is that xsl:variable/#select gives you a node-set, but xsl:value-of turns a node-set into its string value. You want the node-set. In XSLT 1.0, an xsl:variable with content will always give you a result-tree-fragment; but in the select attribute you're confined to using XPath 1.0 which has no conditional expression.
The best solution of course is to move to XSLT 2.0 which solves all these problems. The number of valid reasons for staying with 1.0 is reducing all the time. If you do have to stay with 1.0, there are convoluted workarounds in XPath 1.0 for the absence of a conditional expression, such as the one shown by Dimitre.
Use:
<xsl:variable name="matchesRight" select=
"$questionObject/descendant::simpleMatchSet
[1+($flippedQuestions='true')]
/simpleAssociableChoice"/>
Explanation:
In XPath whenever a boolean value $someBVal is passed to a numeric operator such as +, the boolean is converted to a number (either 0 or 1) using number($someBVal).
By definition:
number(false()) = 0
and
number(true()) = 1
Thus
1+($flippedQuestions='true')
evaluates to 1 if the string value of flippedQuestions isn't the string "true" and the same expression evaluates to 2 if the string value of flippedQuestions is the string "true" .