How do I use a regular expression in XSLT 1.0? - regex

I am using XSLT 1.0.
My input information may contain these values
<!--case 1-->
<attribute>123-00</attribute>
<!--case 2-->
<attribute>Abc-01</attribute>
<!--case 3-->
<attribute>--</attribute>
<!--case 4-->
<attribute>Z2-p01</attribute>
I want to find out those string that match the criteria:
if string has at least 1 alphabet AND has at least 1 number,
then
do X processing
else
do Y processing
In example above, for case 1,2,4 I should be able to do X processing. For case 3, I should be able to do Y processing.
I aim to use a regular expression (in XSLT 1.0).
For all the cases, the attribute can take any value of any length.
I tried use of match, but the processor returned an error.
I tried use of translate function, but not sure if used the right way.
I am thinking about.
if String matches [a-zA-Z0-9]*
then do X processing
else
do y processing.
How do I implement that using XSLT 1.0 syntax?

This solution really works in XSLT 1.0 (and is simpler, because it doesn't and needn't use the double-translate method.):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="vUpper" select=
"'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:variable name="vLower" select=
"'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="vAlpha" select="concat($vUpper, $vLower)"/>
<xsl:variable name="vDigits" select=
"'0123456789'"/>
<xsl:template match="attribute">
<xsl:choose>
<xsl:when test=
"string-length() != string-length(translate(.,$vAlpha,''))
and
string-length() != string-length(translate(.,$vDigits,''))">
Processing X
</xsl:when>
<xsl:otherwise>
Processing Y
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML fragment -- made a well-formed XML document:
<t>
<!--case 1-->
<attribute>123-00</attribute>
<!--case 2-->
<attribute>Abc-01</attribute>
<!--case 3-->
<attribute>--</attribute>
<!--case 4-->
<attribute>Z2-p01</attribute>
</t>
the wanted, correct result is produced:
Processing Y
Processing X
Processing Y
Processing X
Do Note: Any attempt to use with a true XSLT 1.0 processor code like this (borrowed from another answer to this question) will fail with error:
<xsl:template match=
"attribute[
translate(.,
translate(.,
concat($upper, $lower),
''),
'')
and
translate(., translate(., $digit, ''), '')]
">
because in XSLT 1.0 it is forbidden for a match pattern to contain a variable reference.

If you found this question because you're looking for a way to use regular expressions in XSLT 1.0, and you're writing an application using Microsoft's XSLT processor, you can solve this problem by using an inline C# script.
I've written out an example and a few tips in this thread, where someone was seeking out similar functionality. It's super simple, though it may or may not be appropriate for your purposes.

XSLT does not support regular expressions, but you can fake it.
The following stylesheet prints an X processing message for all attribute elements having a string value containing at least one number and at least one letter (and Y processing for those that do not):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:variable name="digit" select="'0123456789'"/>
<xsl:template match="attribute">
<xsl:choose>
<xsl:when test="
translate(., translate(., concat($upper, $lower), ''), '') and
translate(., translate(., $digit, ''), '')">
<xsl:message>X processing</xsl:message>
</xsl:when>
<xsl:otherwise>
<xsl:message>Y processing</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Note: You said this:
In example above, for case 1,2,4 I should be able to do X processing.
for case 3, I should be able to do Y processing.
But that conflicts with your requirement, because case 1 does not contain a letter. If, on the other hand, you really want to match the equivalent of [a-zA-Z0-9], then use this:
translate(., translate(., concat($upper, $lower, $digit), ''), '')
...which matches any attribute having at least one letter or number.
See the following question for more information on using translate in this way:
How to write xslt if element contains letters?

Related

Difficulty formulating conditions for XSL choose or if

I am trying to use XSL choose condition. here I am trying to acheive is, when client sends below xml to Dp System. My XSLT should look for values matching Pv(a) or Pv(b) or Pv(c), if any of these are mactched then send to the backend url which is mentioned in the xsl
else
invoke another rule which is called "Do not call rule" (which is nothing but, picks the local file called error.xml
Thanks for the help
Input xml
<DownloadProfileChannels>
<DownloadProfileChannel>
<IntervalLength>60</IntervalLength>
<PulseMultiplier>0.025</PulseMultiplier>
<Category>Pv(a)</Category> <!-- for every Pv(a) or Pv(b) or Pv(c) -->
<TimeDataEnd>2014-02-20T08:00:00Z</TimeDataEnd>
<MedianValues>
<MedianValue>
<ChannelValue>9112</ChannelValue>
<ProfileStatuses i:nil="true" />
</MedianValue>
<MedianValue>
<ChannelValue>9096</ChannelValue>
<ProfileStatuses i:nil="true" />
</MedianValue>
<MedianValue>
<ChannelValue>9188</ChannelValue>
<ProfileStatuses i:nil="true" />
</MedianValue>
</MedianValue>
</MedianValues>
</DownloadProfileChannel>
</DownloadProfileChannels>
My XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<xsl:message dp:priority="debug"> Entered the XSL File </xsl:message>
</xsl:message>
<xsl:choose>
<xsl:when test="contains($Quantity,'Pv(a) or Pv(b)')">
<xsl:variable name="destURL"
select="http://backendurl.com"/>
<dp:set-variable name="'var://service/routing-url'"
value="$destURL"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="destURL"
select="local:///clienterror.xml"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This line:
<xsl:when test="contains($Quantity,'Pv(a) or Pv(b)')">
Is checking if $Quantity contains the literal string 'Pv(a) or Pv(b)'. You need to separate these out into two check like so:
<xsl:when test="contains($Quantity,'Pv(a)') or contains($Quantity,'Pv(b)')">
Firstly, as #Lego Stormtroopr says, you need to separate out the two conditions. But also, I don't think you really want a "contains" test here, you want an "=" test. The contains function would match Pv(a)(b)(c) - anything that has Pv(a) as a substring, whereas I think you want to match the whole node. So it becomes
<xsl:when test="$Quantity = 'Pv(a)' or $Quantity ='Pv(b)'">
which, if you are using XSLT 2.0, can be further abbreviated to
<xsl:when test="$Quantity = ('Pv(a)', 'Pv(b)')">
Alternatively, in XSLT 2.0 you can use regular expression matching:
<xsl:when test="matches($Quantity, 'Pv([ab])')">

is there any operation such as trim in xslt?

i wrote a xslt code which converts a xml file to a html file which contains lot of tables, one of the column contains messages(very long messages), but that line starts with either of the two words "Verification Passed" or "Verification failed"
My requirement is to make the entire table row red if verification failed and make entire table row green if verification passed
<xsl:choose>
<xsl:when test="contains(#message,'Verification failed:')"><td bgcolor="#FF0000"> <xsl:value-of select="#Message"/></td></xsl:when>
<xsl:when test="contains(#message,'Verification passed:')"><td bgcolor="#00FF00"><xsl:value-of select="#Message"/></td></xsl:when>
<xsl:otherwise><td> <xsl:value-of select="#Message"/></td></xsl:otherwise>
</xsl:choose>
Unfortunately you don't say what you expect your "trim()" function to do. But from your description of the requirement, I would guess that normalize-space() is close enough:
starts-with(normalize-space(message), 'Verification passed'))
The XPath normalize-space() function differs from the Java trim() method in that (a) it replaces internal sequences of whitespace characters by a single space, and (b) it has a slightly different definition of whitespace.
is there any operation such as trim in xslt?
I. XSLT 1.0
No, and it is rather difficult to perform "trim" in XSLT 1.0.
Here is the trim function/template from FXSL:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="trim.xsl"/>
<!-- to be applied on trim.xml -->
<xsl:output method="text"/>
<xsl:template match="/">
'<xsl:call-template name="trim">
<xsl:with-param name="pStr" select="string(/*)"/>
</xsl:call-template>'
</xsl:template>
</xsl:stylesheet>
When this transformation is performed (you have to download at least a few other stylesheet modules, which comprise the complete import tree) on this XML document:
<someText>
This is some text
</someText>
the wanted, correct result is produced:
'This is some text'
II In XSLT 2.0 / XPath 2.0
Still a little bit tricky, but very short:
if (string(.))
then replace(., '^\s*(.+?)\s*$', '$1')
else ()
Here is the complete, corresponding transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
"<xsl:sequence select=
"if (string(.))
then replace(., '^\s*(.+?)\s*$', '$1')
else ()
"/>"
</xsl:template>
</xsl:stylesheet>
and when applied on the same XML document (above), the same correct result is produced:
"This is some text"
using XSLT1 with registerLangFunctions
Today, ~10 years after (complex) XSLT2 standard released, many projects yet use (faster) XSLT1. Perhaps the problem is not only "simple vs complex", but XSLT1 is a fact for Perl, PHP, Python, PostgreSQL, and many other communities.
So, a solution for Perl, PHP and Python: use your main language to do trim and another usual functions that not exists into XSLT1.
Here an example of PHP:
https://en.wikibooks.org/wiki/PHP_Programming/XSL/registerPHPFunctions
<xsl:variable name="Colour">
<xsl:choose>
<xsl:when test="contains(#Message,'Verification failed:')">background-color:red; </xsl:when>
<xsl:when test="contains(#Message,'Verification passed:')">background-color:green</xsl:when>
<xsl:otherwise> </xsl:otherwise>
</xsl:choose>
</xsl:variable>
<tr style="{$Colour}">
<td> <xsl:value-of select="#Time"/></td>
<td>Line <xsl:value-of select="#Line"/></td>
<td> <xsl:value-of select="#Type"/></td>
<td> <xsl:value-of select="#Message"/></td>
</tr>

in xslt 1.0 if value contains number inside string value

I have a complex adress string, or rather different possible formats, which I need to split into road name, house number, floor, position (left,right,middel door) or door/room number
I have managed to do all apart from the last bit, with the pseudo "contains":
<xsl:choose>
<xsl:when test="contains(#addressFarLeft, ANY_NUMERIC_VALUE_ANYWHERE)">
<door>NUMERICVALUE</door>
</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
Im pretty sure I cant just use some form of contains, but what then?
Value is set dynamically, but here are a few possible values:
<xsl:variable name="addressFarLeftValue">.th.</xsl:variable> =>
no numeric value, do nothing
<xsl:variable name="addressFarLeftValue">.1.</xsl:variable> =>
produce: <door>1</door>
<xsl:variable name="addressFarLeftValue">, . tv </xsl:variable> =>
no numeric value, do nothing
<xsl:variable name="addressFarLeftValue">,th, 4.</xsl:variable> =>
produce: <door>1</door>
Any suggestions?
If you want to test whether a string contains a numeric value, one approach could be to use the 'translate' function to remove all numeric digits from the string, and if the resultant string doesn't match the initial string, you know it must have contained a number. If the string doesn't change, then it didn't.
<xsl:choose>
<xsl:when test="translate($addressFarLeft, '1234567890', '') != $addressFarLeft">
<door>1</door>
</xsl:when>
<xsl:otherwise/>
So, <xsl:variable name="addressFarLeftValue">.1.</xsl:variable> outputs <door>1</door>, but <xsl:variable name="addressFarLeftValue">.th.</xsl:variable> doesn't output anything.
IF you wanted to extract the actual number, then assuming there was only one occurence of a number in the string, you could do this...
<xsl:value-of
select="translate(
#addressFarLeft,
translate(#addressFarLeft, '1234567890', ''),
'')" />
So, <xsl:variable name="addressFarLeftValue">,th, 42.</xsl:variable> outputs 42
If you had multiple numbers present, such as ,th, 42, ab, 1 this approach would fail though.
The wanted numeric value can be obtained using the "double-translate" method, first shown by Michael Kay:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="addressFarLeftValue1" select="',th, 4.'"/>
<xsl:variable name="addressFarLeftValue2" select="', . tv'"/>
<xsl:variable name="vDoorNumber1" select=
"translate($addressFarLeftValue1,
translate($addressFarLeftValue1, '0123456789', ''),
'')"/>
<xsl:variable name="vDoorNumber2" select=
"translate($addressFarLeftValue2,
translate($addressFarLeftValue2, '0123456789', ''),
'')"/>
<xsl:template match="/">
"<xsl:value-of select="$vDoorNumber2"/>"
==========
"<xsl:value-of select="$vDoorNumber1"/>"
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to any XML document (not used), the wanted, correct result is produced:
""
==========
"4"

Formatting string (Removing leading zeros)

I am newbie to xslt. My requirement is to transform xml file into text file as per the business specifications. I am facing an issue with one of the string formatting issue. Please help me out if you have any idea.
Here is the part of input xml data:
"0001295"
Expected result to print into text file:
1295
My main issue is to remove leading Zeros. Please share if you have any logic/function.
Just use this simple expression:
number(.)
Here is a complete example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="t">
<xsl:value-of select="number(.)"/>
</xsl:template>
</xsl:stylesheet>
When applied on this XML document:
<t>0001295</t>
the wanted, correct result is produced:
1295
II. Use format-number()
format-number(., '#')
There are a couple of ways you can do this. If the value is entirely numeric (for example not a CSV line or part of a product code such as ASN0012345) you can convert from a string to a number and back to a string again :
string(number($value)).
Otherwise just replace the 0's at the start :
replace( $value, '^0*', '' )
The '^' is required (standard regexp syntax) or a value of 001201 will be replaced with 121 (all zero's removed).
Hope that helps.
Dave
Here is one way you could do it in XSLT 1.0.
First, find the first non-zero element, by removing all the zero elements currently in the value
<xsl:variable name="first" select="substring(translate(., '0', ''), 1, 1)" />
Then, you can find the substring-before this first character, and then use substring-after to get the non-zero part after this
<xsl:value-of select="substring-after(., substring-before(., $first))" />
Or, to combine the two statements into one
<xsl:value-of select="substring-after(., substring-before(., substring(translate(., '0', ''), 1, 1)))" />
So, given the following input
<a>00012095Kb</a>
Then using the following XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/a">
<xsl:value-of select="substring-after(., substring-before(., substring(translate(., '0', ''), 1, 1)))" />
</xsl:template>
</xsl:stylesheet>
The following will be output
12095Kb
As a simple alternative in XSLT 2.0 that can be used with numeric or alpha-numeric input, with or without leading zeros, you might try:
replace( $value, '^0*(..*)', '$1' )
This works because ^0* is greedy and (..*) captures the rest of the input after the last leading zero. $1 refers to the captured group.
Note that an input containing only zeros will output 0.
XSLT 2.0
Remove leading zeros from STRING
<xsl:value-of select="replace( $value, '^0+', '')"/>
You could use a recursive template that will remove the leading zeros:
<xsl:template name="remove-leading-zeros">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="starts-with($text,'0')">
<xsl:call-template name="remove-leading-zeros">
<xsl:with-param name="text"
select="substring-after($text,'0')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Invoke it like this:
<xsl:call-template name="remove-leading-zeros">
<xsl:with-param name="text" select="/path/to/node/with/leading/zeros"/>
</xsl:call-template>
</xsl:template>
<xsl:value-of select="number(.) * 1"/>
works for me
All XSLT1 parser, like the popular libXML2's module for XSLT, have the registered functions facility... So, we can suppose to use it. Suppose also that the language that call XSLT, is PHP: see this wikibook about registerPHPFunctions.
The build-in PHP function ltrim can be used in
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://php.net/xsl">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="test">
show <xsl:value-of select="fn:function('ltrim',string(.),'0')" />",
</xsl:template>
</xsl:stylesheet>
Now imagine a little bit more complex problem, to ltrim a string with more than 1 number, ex. hello 002 and 021, bye.
The solution is the same: use registerPHPFunctions, except to change the build-in function to a user defined one,
function ltrim0_Multi($s) {
return preg_replace('/(^0+|(?<= )0+)(?=[1-9])/','',$s);
}
converts the example into hello 2 and 21, bye.

XSLT 1.0 - conditional node assignment

using pure XSLT 1.0, how can I conditionally assign the node. I am trying something like this but it's not working.
<xsl:variable name="topcall" select="//topcall"/>
<xsl:variable name="focusedcall" select="//focusedcall" />
<xsl:variable name="firstcall" select="$topcall | $focusedcall"/>
For variable firstcall, I am doing the conditional node selection. if there is a topcall then assign it to firstcall, othersie assign firstcall to the focusedcall.
This should work:
<xsl:variable name="firstcall" select="$topcall[$topcall] |
$focusedcall[not($topcall)]" />
In other words, select $topcall if $topcall nodeset is non-empty; $focusedcall if $topcall nodeset is empty.
Re-Update regarding "it can be 5-6 nodes":
Given that there may be 5-6 alternatives, i.e. 3-4 more besides $topcall and $focusedcall...
The easiest solution is to use <xsl:choose>:
<xsl:variable name="firstcall">
<xsl:choose>
<xsl:when test="$topcall"> <xsl:copy-of select="$topcall" /></xsl:when>
<xsl:when test="$focusedcall"><xsl:copy-of select="$focusedcall" /></xsl:when>
<xsl:when test="$thiscall"> <xsl:copy-of select="$thiscall" /></xsl:when>
<xsl:otherwise> <xsl:copy-of select="$thatcall" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
However, in XSLT 1.0, this will convert the output of the chosen result to a result tree fragment (RTF: basically, a frozen XML subtree). After that, you won't be able to use any significant XPath expressions on $firstcall to select things from it. If you need to do XPath selections on $firstcall later, e.g. select="$firstcall[1]", you then have a few options...
Put those selections into the <xsl:when> or <xsl:otherwise> so that they happen before the data gets converted to an RTF. Or,
Consider the node-set() extension, which converts an RTF to a nodeset, so you can do normal XPath selections from it. This extension is available in most XSLT processors but not all. Or,
Consider using XSLT 2.0, where RTFs are not an issue at all. In fact, in XPath 2.0 you can put normal if/then/else conditionals inside the XPath expression if you want to.
Implement it in XPath 1.0, using nested predicates like
:
select="$topcall[$topcall] |
($focusedcall[$focusedcall] | $thiscall[not($focusedcall)])[not($topcall)]"
and keep on nesting as deep as necessary. In other words, here I took the XPath expression for 2 alternatives above, and replaced $focusedcall with
($focusedcall[$focusedcall] | $thiscall[not($focusedcall)])
The next iteration, you would replace $thiscall with
($thiscall[$thiscall] | $thatcall[not($thiscall)])
etc.
Of course this becomes hard to read, and error-prone, so I would not choose this option unless the others aren't feasible.
Does <xsl:variable name="firstcall" select="($topcall | $focusedcall)[1]"/> do what you want? That is usually the way to take the first node in document order of different types of nodes.
I. XSLT 1.0 Solution This short (30 lines), simple and parameterized transformation works with any number of node types/names:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pRatedCalls">
<call type="topcall"/>
<call type="focusedcall"/>
<call type="normalcall"/>
</xsl:param>
<xsl:variable name="vRatedCalls" select=
"document('')/*/xsl:param[#name='pRatedCalls']/*"/>
<xsl:variable name="vDoc" select="/"/>
<xsl:variable name="vpresentCallNames">
<xsl:for-each select="$vRatedCalls">
<xsl:value-of select=
"name($vDoc//*[name()=current()/#type][1])"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select=
"//*[name()
=
substring-before(normalize-space($vpresentCallNames),' ')]"/>
</xsl:template>
</xsl:stylesheet>
When applied to this XML document (do note the document order doesn't coincide with the specified priorities in the pRatedCalls parameter):
<t>
<normalcall/>
<focusedcall/>
<topcall/>
</t>
produces exactly the wanted, correct result:
<topcall/>
when the same transformation is applied to the following XML document:
<t>
<normalcall/>
<focusedcall/>
</t>
again the wanted and correct result is produced:
<focusedcall/>
Explanation:
The names of the nodes that are to be searched for (as many as needed and in order of priority) are specified by the global (typically externally specified) parameter named $pRatedCalls.
Within the body of the variable $vpresentCallNames we generate a space-separated list of names of elements that are both specified as a value of the type attribute of a call elementin the$pRatedCalls` parameter and also are names of elements in the XML document.
Finally, we determine the first such name in this space-separated list and select all elements in the document, that have this name.
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pRatedCalls" select=
"'topcall', 'focusedcall', 'normalcall'"/>
<xsl:template match="/">
<xsl:sequence select=
"//*
[name()=$pRatedCalls
[. = current()//*/name()]
[1]
]"/>
</xsl:template>
</xsl:stylesheet>