Very simple XSLT string question - xslt

How would I do "substring(variable,1,1) between a-z or A-Z" then do X else do Y using XSLT? I know that one option would be using regex but I would expect there to be something that wasn't quite so much overkill.

A simple XSLT 1.0 solution:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="vLetters"
select="'ABCDEFGHIKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="vText" select="'1Text'"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test=
"contains($vLetters, substring($vText,1,1))">
Letter
</xsl:when>
<xsl:otherwise>
Not Letter
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (not used),
produces the wanted result:
Not Letter
Depending on the specific case one can add whatever processing is necessary to each of the two "clauses" (<xsl:when> and <xsl:otherwise>) of the <xsl:choose> instruction.

And for XSLT 2.0, you can use the regular expression function matches:
<xsl:choose>
<xsl:when test="matches($variable1, '^[a-zA-Z].*$')">
Match
</xsl:when>
<xsl:otherwise>
NoMatch
</xsl:otherwise>
</xsl:choose>

Related

Extract parts of inputs strings

I am trying to extract equipment names from strings and would like if someone could help me find a good way to do this.
My input string can either contain 1 or 2 equipment names, consisting of EQ followed 1 to 3 digits, for example :
LocationEQ3Suffix
LocationEQ5EQ8Suffix
So in the first instance I would need 'EQ3' and in the second instance I would need 'EQ5' and 'EQ8'.
I need the output to be in a text format, for example :
SomeText.EQ3
SomeText.EQ5
SomeText.EQ8
I was thinking there might be a way to do this with xsl:analyze-string and a regex like EQ[0-9]{1,3}.
Any help is appreciated.
I started something like this, but I don't think it's the right approach.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="input" select="'LocationEQ3EQ4Funct'"/>
<xsl:choose>
<!-- Case with 2 EQ -->
<xsl:when test="matches($input, 'EQ[0-9]{1,3}EQ[0-9]{1,3}')">
<xsl:value-of select="$input"/>
</xsl:when>
<!-- Case with 1 EQ -->
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You say you want to use xsl:analyze-string but you're not.
An implementation using it would look something like:
<xsl:analyze-string select="input-string" regex="EQ\d{{1,3}}">
<xsl:matching-substring>
<xsl:text>SomeText.</xsl:text>
<xsl:value-of select="." />
<xsl:text>
</xsl:text>
</xsl:matching-substring>
</xsl:analyze-string>
Demo: https://xsltfiddle.liberty-development.net/a9Hk1a

Xpath 'instance of' not working in solr xslt

I want use xslt in solr to output result in json without specific solr attributes.
{
"techproducts": [
{
"id": "GB18030TEST",
"price": "0.0"
},
{
"id": "SOLR1000",
"price": "0.0"
},
{
"id": "UTF8TEST",
"price": "0.0"
}
],
"paging": {
"count": 3
}
}
The test of type of field (numeric or boolean) test=". instance of xs:integer" in order to add or not quotes do not work and generate a 500 error.
http://localhost:8090/solr/techproducts/select?q=*&fq=price:0&fl=id,price&wt=xslt&tr=json_paginate.xsl
Caused by: javax.xml.transform.TransformerConfigurationException: solrres:/xslt/json_paginate.xsl: line 37: Attribut 'test' obligatoire manquant.
Any suggestions?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" >
<xsl:strip-space elements="*"/>
<xsl:output method="text" indent="no" media-type="application/json"/>
<xsl:template match='/'>
<xsl:text>{"techproducts":[</xsl:text>
<xsl:apply-templates select="response/result/doc"/>
<xsl:text>],</xsl:text>
<xsl:text>"paging":{"count":</xsl:text><xsl:value-of select="response/result/#numFound"/><xsl:text>}}</xsl:text>
</xsl:template>
<xsl:template match="doc">
<xsl:variable name="pos" select="position()"/>
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>{</xsl:text>
<xsl:apply-templates>
<xsl:with-param name="pos"><xsl:value-of select="$pos"/></xsl:with-param>
</xsl:apply-templates>
<xsl:text>}</xsl:text>
</xsl:template>
<xsl:template match="doc/*">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>"</xsl:text><xsl:value-of select="#name"/><xsl:text>":</xsl:text>
<xsl:choose>
<!-- if integer, do not add quotes -->
<xsl:when test=". instance of xs:integer">
<xsl:value-of select="."/>
</xsl:when>
<!-- if boolean, do not add quotes -->
<xsl:when test=". instance of xs:boolean">
<xsl:value-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:text>"</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
To solve the problem :
I have upgraded xslt version in solr http://wiki.apache.org/solr/XsltResponseWriter and used
<xsl:choose>
<!-- if decimal, do not add quotes -->
<xsl:when test=". castable as xs:decimal">
<xsl:value-of select="."/>
</xsl:when>
<!-- if boolean, do not add quotes -->
<xsl:when test=". castable as xs:boolean">
<xsl:value-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:text>"</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
</xsl:otherwise>
</xsl:choose>
Here is how to achieve in XSLT 1.0 the intended behavior of the provided XSLT 2.0 code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match=
"*[not(*) and text()
and
not(contains('|true|false|', concat('|', ., '|')))]">
<xsl:value-of select='concat(&apos;"&apos;, ., &apos;"&apos;)'/>
</xsl:template>
<xsl:template match="*[not(*) and text() and number(.) = .]" priority="3">
<xsl:value-of select="number(.)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<a>1</a>
<b>true</b>
<c>false</c>
<d>-3</d>
<e>x</e>
<f>.25</f>
</t>
The wanted, correct result is produced:
1
true
false
-3
"x"
0.25
Do note: In addition to the behavior that is intended in the provided XSLT 2.0 code, the above transformation also implements correct output of JSON-acceptable representation for any decimal number.
In case only processing and displaying integer values is wanted, just use:
floor(.) = .
Update:
The OP has just revealed that he could actually use an XSLT 2.0 processor.
Here is one possible XSLT 2.0 solution, which represents correctly the strings "true" and "false" as JSON Booleans, any integers and decimals to JSON numbers, and any other atomic value -- to quoted string:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match=
"*[not(*) and text() and not(. = ('true', 'false'))]">
<xsl:value-of select='concat(&apos;"&apos;, ., &apos;"&apos;)'/>
</xsl:template>
<xsl:template match="*[not(*) and text() and . castable as xs:decimal]" priority="3">
<xsl:value-of select="number(.)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above, the same correct, wanted result is produced:
1
true
false
-3
"x"
0.25
Your goal is to achieve syntactically correct JSON data: no quotes for numbers and the strings "true" and "false". That's what your attempt handling these data types tells me.
In this case the following xsl:choose part should work - at least I hope so, since we don't know your input data:
XSLT-2.0: (*)
<xsl:choose>
<!-- if number, do not add quotes -->
<xsl:when test="number(.)">
<xsl:value-of select="."/>
</xsl:when>
<!-- if boolean, do not add quotes -->
<xsl:when test="matches(.,'true|false','i')">
<xsl:value-of select="lower-case(.)"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>"</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
</xsl:otherwise>
</xsl:choose>
It should be mentioned (see comments) that all numbers get printed without quotation marks. Not only integers. Which should be ok for a valid JSON output.
(*) the first version of my answer had a remarkable amount of errors in it which I apologize for (downvoting that answer was quite appropriate). The version shown above has been tested with Saxon and worked as expected.

XSLT Node availability check

I want to check a variable have any node or any attribute.
XSL:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:variable name="testvar">
<test><name first="Isaac" last="Sivakumar" middle="G"></name></test>
</xsl:variable>
<xsl:choose>
<xsl:when test="normalize-space($testvar)">
<xsl:value-of select="$testvar"></xsl:value-of>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'NO XML DATA AVAILABLE'"></xsl:value-of>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When I try to run the above code I am getting "NO XML DATA AVIALABLE." I need to check weather a variable has any node / any attributes irrespective of it has data or not.
Can you please help me to fix this.
With XSLT 1.0 your variable has a value of type "result tree fragment", you need to use an extension function to convert it to a node set first to be able to address nodes e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common" version="1.0">
<xsl:template match="/">
<xsl:variable name="testvar">
<test><name first="Isaac" last="Sivakumar" middle="G"></name></test>
</xsl:variable>
<xsl:choose>
<xsl:when test="exsl:node-set($testvar)/node()">
<xsl:copy-of select="$testvar"></xsl:value-of>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'NO XML DATA AVAILABLE'"></xsl:value-of>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Using normalize-space or value-of does not make much sense given that your XML in the result tree fragment has all data in attributes and no text nodes with data.
And the test test="exsl:node-set($testvar)/node()" is just an example, it could of course use e.g. test="exsl:node-set($testvar)//name" to test for a specific element like a name element.
Given XSLT 1.0 but EXSLT common support it might be better to check with http://www.exslt.org/exsl/functions/object-type/index.html e.g.
<xsl:choose>
<xsl:when test="exsl:object-type($testvar) = 'string' and $testvar = ''">
<xsl:value-of select="'NO XML DATA AVAILABLE'"/>
</xsl:when>
<xsl:when test="exsl:object-type($testvar) = 'node-set'">
<xsl:copy-of select="$testvar"/>
</xsl:when>
</xsl:choose>
Given XSLT 2.0 I would simply check e.g if ($testvar instance of xs:string and $testvar = '') then 'NO XML DATA AVAILABLE' else $testvar.

check for successively numbered attributes

I have a situation where I need to check for attribute values that may be successively numbered and input a dash between the start and end values.
<root>
<ref id="value00008 value00009 value00010 value00011 value00020"/>
</root>
The ideal output would be...
8-11, 20
I can tokenize the attribute into separate values, but I'm unsure how to check if the number at the end of "valueXXXXX" is successive to the previous value.
I'm using XSLT 2.0
You can use xsl:for-each-group with #group-adjacent testing for the number() value subtracting the position().
This trick was apparently invented by David Carlisle, according to Michael Kay.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vals"
select="tokenize(root/ref/#id, '\s?value0*')[normalize-space()]"/>
<xsl:variable name="condensed-values" as="item()*">
<xsl:for-each-group select="$vals"
group-adjacent="number(.) - position()">
<xsl:choose>
<xsl:when test="count(current-group()) > 1">
<!--a sequence of successive numbers,
grab the first and last one and join with '-' -->
<xsl:sequence select="
string-join(current-group()[position()=1
or position()=last()]
,'-')"/>
</xsl:when>
<xsl:otherwise>
<!--single value group-->
<xsl:sequence select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:variable>
<xsl:value-of select="string-join($condensed-values, ',')"/>
</xsl:template>
</xsl:stylesheet>

what is correct way to test for xs:decimal in XSL?

I'm trying to display different information depending on incoming data. If it's an integer, I want to display just the number, if it's a decimal, I want to use 0.00# pattern. Ya, I know, a bit mixed up, but that's the development spec. :>
I have the following XSL for this specific section but I can't see to get past the xsl:when error message of
"Expected end of expression, found
'castable'. number(SAVG) -->castable
<-- as xs:decimal"
<xsl:choose>
<xsl:when test="number(SAVG) > 0">
<xsl:choose>
<xsl:when test="number(SAVG) castable as xs:decimal">
<xsl:value-of select="format-number(SAVG, '###,###,##0.00#')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number(SAVG, '###,###,##0.###')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="number(SAVG) = 0">
<xsl:text disable-output-escaping="yes">&lt;</xsl:text>1
</xsl:when>
<xsl:otherwise>N/A</xsl:otherwise>
</xsl:choose>
I tried looking/poking around for answers and I have tried "instance of", I've tried using xsl:if, etc but I can't seem to get this to work. Any help would be greatly appreciated.
Thanks.
From comments:
Yes, we are using 1.0. I'm sorry I'm
new to the XSL processing, how do I
glue your XSL and input to generate
the html?
I. XSLT 1.0:
There are no xs:integer and xs:decimal in the XPath 1.0 data model used by XSLT 1.0.
Here is a code snippet that you may use:
<xsl:choose>
<xsl:when test="not(floor(SAVG) = SAVG)">
<xsl:value-of select="format-number(SAVG, '###,###,##0.00#')"/>
</xsl:when>
<xsl:otherwise> <!-- Integer value -->
<xsl:value-of select="SAVG"/>
</xsl:otherwise>
</xsl:choose>
Do note: To test if a numeric value is an integer, we use the following test:
floor($someNum) = $someNum
Here is one way to do this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select=
"for $num in (3, 3.14)
return
if($num instance of xs:integer)
then ($num, ' is xs:integer', '
')
else if($num instance of xs:decimal)
then ($num, ' is xs:decimal', '
')
else ($num, ' is something else', '
')
"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the wanted, correct result is produced:
3 is xs:integer
3.14 is xs:decimal
Or, using the format-number() function as per your example:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select=
"for $num in (3, 3.14)
return
if($num instance of xs:integer)
then (format-number($num, '###,###,##0.###'), '
')
else if($num instance of xs:decimal)
then (format-number($num, '###,###,##0.00#'), '
')
else ()
"/>
</xsl:template>
</xsl:stylesheet>
produces:
3
3.14
This XSLT 1.0 stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="test">
<xsl:choose>
<!-- Not number or number less than zero -->
<xsl:when test="0 > SVGA or number(SVGA) != SVGA">
<xsl:text>N/A</xsl:text>
</xsl:when>
<!-- Number zero -->
<xsl:when test="SVGA = 0">
<xsl:text><1</xsl:text>
</xsl:when>
<!-- Integer number -->
<xsl:when test="floor(SVGA) = SVGA">
<xsl:value-of select="format-number(SVGA,'###,###,##0.###')"/>
</xsl:when>
<!-- Double number -->
<xsl:otherwise>
<xsl:value-of select="format-number(SVGA,'###,###,##0.00#')"/>
</xsl:otherwise>
</xsl:choose>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
<test>
<SVGA>0</SVGA>
</test>
<test>
<SVGA>12131</SVGA>
</test>
<test>
<SVGA>123.5654</SVGA>
</test>
<test>
<SVGA>-12.1</SVGA>
</test>
<test>
<SVGA>-7528</SVGA>
</test>
<test>
<SVGA>zero</SVGA>
</test>
</root>
Output:
<1
12,131
123.565
N/A
N/A
N/A
Edit 3: Better test order (plus Dimitre's expression), better test case, closer input sample to questioner.