XPath test if node value is number - xpath

How can I check if a node value is a number using XPath?
Any ideas?

Test the value against NaN:
<xsl:if test="string(number(myNode)) != 'NaN'">
<!-- myNode is a number -->
</xsl:if>
This is a shorter version (thanks #Alejandro):
<xsl:if test="number(myNode) = myNode">
<!-- myNode is a number -->
</xsl:if>

The shortest possible way to test if the value contained in a variable $v can be used as a number is:
number($v) = number($v)
You only need to substitute the $v above with the expression whose value you want to test.
Explanation:
number($v) = number($v) is obviously true, if $v is a number, or a string that represents a number.
It is true also for a boolean value, because a number(true()) is 1 and number(false) is 0.
Whenever $v cannot be used as a number, then number($v) is NaN
and NaN is not equal to any other value, even to itself.
Thus, the above expression is true only for $v whose value can be used as a number, and false otherwise.

There's an amazing type test operator in XPath 2.0 you can use:
<xsl:if test="$number castable as xs:double">
<!-- implementation -->
</xsl:if>

I'm not trying to provide a yet another alternative solution, but a "meta view" to this problem.
Answers already provided by Oded and Dimitre Novatchev are correct but what people really might mean with phrase "value is a number" is, how would I say it, open to interpretation.
In a way it all comes to this bizarre sounding question: "how do you want to express your numeric values?"
XPath function number() processes numbers that have
possible leading or trailing whitespace
preceding sign character only on negative values
dot as an decimal separator (optional for integers)
all other characters from range [0-9]
Note that this doesn't include expressions for numerical values that
are expressed in exponential form (e.g. 12.3E45)
may contain sign character for positive values
have a distinction between positive and negative zero
include value for positive or negative infinity
These are not just made up criteria. An element with content that is according to schema a valid xs:float value might contain any of the above mentioned characteristics. Yet number() would return value NaN.
So answer to your question "How i can check with XPath if a node value is number?" is either "Use already mentioned solutions using number()" or "with a single XPath 1.0 expression, you can't". Think about the possible number formats you might encounter, and if needed, write some kind of logic for validation/number parsing. Within XSLT processing, this can be done with few suitable extra templates, for example.
PS. If you only care about non-zero numbers, the shortest test is
<xsl:if test="number(myNode)">
<!-- myNode is a non-zero number -->
</xsl:if>

The one I found very useful is the following:
<xsl:choose>
<xsl:when test="not(number(myNode))">
<!-- myNode is a not a number or empty(NaN) or zero -->
</xsl:when>
<xsl:otherwise>
<!-- myNode is a number (!= zero) -->
</xsl:otherwise>
</xsl:choose>

You could always use something like this:
string(//Sesscode) castable as xs:decimal
castable is documented by W3C here.

I've been dealing with 01 - which is a numeric.
string(number($v)) != string($v) makes the segregation

Related

Use XPath:Replace on first instance of a single character [duplicate]

This question already has an answer here:
Match first only instance of a character [duplicate]
(1 answer)
Closed 3 years ago.
I'm trying to replace only the first instance of a character in a string like fred-3-1-2 with XPath::replace and replace it with a / so that the resulting string is fred/3-1-2. I cannot guarantee anything else about the original string other than that it will have one or more dashes in it. I'm having a ton of difficulty finding a regex pattern that works with XPath::replace and consistently matches only that particular first instance of -.
I feel like I came close with:
(?:.+?)(-)(?:.+)
But this also matches the full string as well, so it is no good.
Please do not offer solutions using anything but plain regular expressions that would work on https://regex101.com. The "flavor" of regex should abide by XPath/XQuery semantics (https://www.w3.org/TR/xmlschema-2/#regexs).
You can use:
replace('fred-3-1-2', '^([^-]*)-','$1/')
Check it. Result:
fred/3-1-2
Meaning:
From the start, match any non - character followed by one -
character, replace the match by the first captured group plus /
character
Do note: XPath/XQuery follows Perl regexp. The description in XML Schema you cite is extended in XPath/Xquery with the followings: "Matching the Start and End of the String", "Reluctant Quantifiers", "Captured Sub-Expressions", "Back-References"
Pure XPath 1.0 solution -- no regular expressions:
concat(substring-before('fred-3-1-2', '-'), '/', substring-after('fred-3-1-2', '-'))
In case the string is contained in a variable $s and it isn't known that it contains a -, then use:
concat(
substring(concat(substring-before($s, '-'), '/', substring-after($s, '-')),
1 div contains($s, '-')
),
substring($s, 1 div not(contains($s, '-')))
)
XSLT-based verification:
<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="s" select="'fred-3-1-2'"/>
<xsl:variable name="t" select="'fred+3+1+2'"/>
<xsl:template match="/">
<xsl:value-of select="concat(substring-before('fred-3-1-2', '-'), '/', substring-after('fred-3-1-2', '-'))"/>
=================
<xsl:value-of select=
"concat(
substring(concat(substring-before($s, '-'), '/', substring-after($s, '-')),
1 div contains($s, '-')
),
substring($s, 1 div not(contains($s, '-')))
)"/>
===============
<xsl:value-of select=
"concat(
substring(concat(substring-before($t, '-'), '/', substring-after($t, '-')),
1 div contains($t, '-')
),
substring($t, 1 div not(contains($t, '-')))
)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML file (not used), it evaluates the XPath expressions provided on this answer and produces the wanted, correct results: if the string contains - then its first (only) occurence in the string is replaced in the result of the evaluation; if the string doesn't contain any hyphens, then the result is the same string unchanged:
fred/3-1-2
=================
fred/3-1-2
===============
fred+3+1+2

Xpath 1.0 using an arithmetic operators

Let's say we have this:
something
Now is there a way to return the #href like: "www.something/page/2". Basically to return the #href value, but with the substring-after(.,"page/") incremented by 1. I've been trying something like
//a/#href[number(substring-after(.,"page/"))+1]
but it doesn't work, and I don't think I can use
//a/#href/number(substring-after(.,"page/"))+1
It's not precisely a paging think, so that I can use the pagination, I just picked that for an example. The point is just to find a way to increment a value in xpath 1.0. Any help?
What you can do is
concat(
translate(//a/#href, '0123456789', ''),
translate(//a/#href, translate(//a/#href, '0123456789', ''), '') + 1
)
So that concatenates the 'href' attribute with all digits being removed with the the sum of 1 and the 'href' with anything but digits being removed.
That might suffice is all digits in your URLs occur at the end of your URL. But generally XPath 1.0 is good at selecting nodes in your input but bad at constructing new values based on parts of node values.
There is a simpler way to achieve this, just take the substring after the page, add 1, and then munge it all back together:
This XPath is based on the current node being the #href attribute:
concat(substring-before(.,'page/'),
'page/',
substring-after(.,'page/')+1
)
Your order of operations is a little, well, out of order. Use something like this:
substring-after(//a/#href, 'page/') + 1
Note that it is not necessary to explicitly convert the string value to a number. From the spec:
The numeric operators convert their operands to numbers as if by
calling the number function.
Putting it all together:
concat(
substring-before(//a/#href, 'page/'),
'page/',
substring-after(//a/#href, 'page/') + 1)
Result:
www.something/page/2

How to convert the int number into money format in X-path expression?

I want to convert the number (ie.1000) directly into into the money format like(ie.1,000).how should i do that?
In XSLT there is format-number(), but in pure XPath you'll have to do it the hard way. Perhaps you should do the formatting in the host language that you call XPath from?
You can use
<xsl:value-of select="format-number($wartosc, "###,###,###,###,##0.00")" />
You can use regex.
If $number is a string containing your integer number, then you can use:
replace(replace(
if (string-length($number) mod 3 eq 2) then concat("0", $number)
else if (string-length($number) mod 3 eq 1) then concat("00", $number)
else $number,
"([0-9]{3})", ",$1"),
"^[,0]+", "")

Regex in xpath?

I want to find a table cell that contains the link (\d{0,3} )?pieces.
How would I need to write this xpath?
Can I simply insert the xpath directly into the Capybara search? Or do I need to do something special to indicate it is a regex? Or can I not do it at all?
Xpath 1.0
XPath 1.0 does not include regular expression support. You should be able to achieve the desired match with the following expression:
//td/a['pieces'=substring(#href, string-length(#href) -
string-length('pieces') + 1) and
'pieces'=translate(#href, '0123456789', '') and
string-length(#href) > 5 and
string-length(#href) < 10]
The first test in the predicate checks that the string ends with pieces. The second test ensures that the entire string equals pieces when all of the digits are removed (i.e. there are no other characters). The final two tests ensure that the entire length of the string is between 6 and 9, which is the length of pieces plus zero to three digits.
Test it on the following document:
<table>
<tr>
<td>test0</td>
<td>no match</td>
<td>no match</td>
<td>test1</td>
<td>test2</td>
<td>no match</td>
<td>test3</td>
</tr>
</table>
It should match only the test0, test1, test2, and test3 links.
(Note: The expression may be further complicated by the possibility of other characters preceding the portion you're attempting to match.)
XPath 2.0
Achieving this in XPath 2.0 is trivial with the matches function.
//td/a[
substring-after(concat(#href ,'x') ,'pieces')='x'
and
111>=concat(0 ,translate( substring-before(#href ,'pieces') ,'0123456789 -.' ,'1111111111xxx'))
]
This is another solution, not necessarily better, but, perhaps, interesting.
The first conjunct is true just when #href contains exactly one occurrence
of 'pieces', and it is at the end.
The second conjunct is true just when the part of #href before 'pieces' is empty
or is a numeral made entirely of digits (no .,-, or white-space), with at most 3 digits.
The number of 1's in the '111>=' is the maximum number of digits that will match.
Reference: http://www.w3.org/TR/xpath
The substring-after function returns the substring of the first argument string that follows the first occurrence of the second argument string in the first argument string, or the empty string if the first argument string does not contain the second argument string.
The substring-before function returns the substring of the first argument string that precedes the first occurrence of the second argument string in the first argument string, or the empty string if the first argument string does not contain the second argument string.
... a string that consists of optional whitespace followed by an optional minus sign followed by a Number followed by whitespace is converted to the IEEE 754 number ... any other string is converted to NaN
Number ::= Digits ('.' Digits?)? | '.' Digits
An attribute node has a string-value. The string-value is the normalized value as specified by the XML Recommendation [XML]
The normalize-space function returns the argument string with whitespace normalized by stripping leading and trailing whitespace and replacing sequences of whitespace characters by a single space.

last entry in a loop

I have an associative array and I generate a lot of different things with this array.
The output I need has the form
aa, ab, ac, ad, af, ak, az
So the last entry does not have a comma after it
{section name=i loop=$aColums}
{if $aColums[i].contshow eq 'y'}
{$aColums[i].Name}
{endif}
{/section}
My problem is that I don't know when I've reached the last value, which has the contshow=y attribute. So my next thought was to apply the comma before I write aColums[i].Name. But here I have a similar problem becuase I don't know when I've reached the first value with contshow=y.
Does anyone have a suggestion?
There is an alternative method, using the section's .last property.
{section name=i loop=$aColums}
{if $aColums[i].contshow eq 'y'}
{$aColums[i].Name}{if $smarty.section.i.last eq false}, {/if}
{endif}
{/section}
This adds a ', ' after every output Name - unless it's the last iteration of the {section}. I'm assuming that your $aColums array data doesn't already have commas tacked on.
You also have another option - pregenerate this string in PHP using implode:
$aColumsString = implode(', ', $aColums);
$smarty->assign('aColumsString', $aColumsString);
Then just output to the template as needed. If you require the list with commas more than once, this is probably the more efficient method. If you need it once, it's probably a toss-up effiency-wise.
The general way to deal with this is to write the commas before each element. In this way, the special-case is the first element (which doesn't need a preceding comma) rather than the last, and it's a lot easier to work out whether you're seeing the first element or not. Just set a boolean flag to true initially, then set it to false after matching an entry.
Mind you, Smarty might have a utility function for "joining" the array with a given string (comma, in this case). If such a function exists, using it directly would be the best option.
In Smarty 3, this is much simplified:
{foreach $aColumns AS $aCol}
{$aCol}{if not $aCol#last}, {/if}
{/foreach}

Resources