check for successively numbered attributes - xslt

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>

Related

How are sequences spliced, and why is my variable's value a document node?

Look at the code below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="3.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<root>
<xsl:variable name="v1">
<xsl:variable name="a1" select="137"/>
<xsl:variable name="a2" select="(1, 3, 'abc')"/>
<xsl:variable name="a3" select="823"/>
<xsl:sequence select="$a1"/>
<xsl:sequence select="$a2"/>
<xsl:sequence select="$a3"/>
</xsl:variable>
<xsl:variable name="v2" as="item()+">
<xsl:variable name="b1" select="137"/>
<xsl:variable name="b2" select="(1, 3)"/>
<xsl:variable name="b3" select="823"/>
<xsl:variable name="b4" select="'abc'"/>
<xsl:sequence select="$b1"/>
<xsl:sequence select="$b2"/>
<xsl:sequence select="$b3"/>
<xsl:sequence select="$b4"/>
</xsl:variable>
<count>
<xsl:text>v1 count is: </xsl:text>
<xsl:value-of select="count($v1)"/>
</count>
<count>
<xsl:text>v2 count is: </xsl:text>
<xsl:value-of select="count($v2)"/>
</count>
<count>
<xsl:text>a2 count is: </xsl:text>
<xsl:value-of select="count((1, 3, 'abc'))"/>
</count>
</root>
</xsl:template>
</xsl:stylesheet>
The result ouput is:
<root>
<count>v1 count is: 1</count>
<count>v2 count is: 5</count>
<count>a2 count is: 3</count>
</root>
Why v2 count is different from v1 count? They seems to have the same items. How the sequence splice?
Why is v1 treated as the 'document-node' type?
Words "It looks like your post is mostly code; please add some more details." always prevent me to submit.
Well, you have different variable declarations, as one uses the as attribute and the other not.
And you seem to have inferred that your first case without any as declaration results in a document node (containing content).
As for the gory details of the various options, the spec treats your first case in https://www.w3.org/TR/xslt-30/#temporary-trees and outlines the various options of how as, select and content constructors in xsl:variable interact in https://www.w3.org/TR/xslt-30/#variable-values.

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.

choosing specific column value from the input xml

i have the below xml as input for which i have to do the xsl transformation
<emml>
<tradeEventHeader>
<tradeIdentifier>
<tradeId>104823343913</tradeId>
<systemReference>RDS</systemReference>
<systemDomainName>Internal</systemDomainName>
</tradeIdentifier>
<tradeStateIdentifier>
<tradeStateId>Validated</tradeStateId>
<systemReference>RDS</systemReference>
<tradeStateIdClassificationScheme>Vn State</tradeStateIdClassificationScheme>
</tradeStateIdentifier>
<tradeStateIdentifier>
<tradeStateId>Pending</tradeStateId>
<systemReference>Swapswire</systemReference>
<tradeStateIdClassificationScheme>Mang State</tradeStateIdClassificationScheme>
</tradeStateIdentifier>
<tradeStateIdentifier>
<tradeStateId>accpt_novated_sw</tradeStateId>
<systemReference>RDS</systemReference>
<tradeStateIdClassificationScheme>Clearing State</tradeStateIdClassificationScheme>
</tradeStateIdentifier>
</tradeEventHeader>
<emmlExtension systemId="RDS YTO">
<emmlMediumString idref="legId1" name="Roll Date Option">Short Initial</emmlMediumString>
</emmlExtension>
</emml>
as shown above in the input xml basically my objective is to identify the value of tradeStateIdClassificationScheme parameter and if the value of this parameter is equal to 'Clearing state' then with correspond to that i have to check the value of another column tradeStateId and if the value of the column tradeStateId starts with accpt_novated_sw then in that case we need to return true string and for rest other i need to return false string ..
i have come up with the below template in xslt 1.0 , please advise is it correct approach..
calling template :-
<isClearedNovated>
<xsl:call-template name="cleared_novated">
<xsl:with-param name="tradeStateId" select="emml/*/*/tradeStateIdentifier" />
</xsl:call-template>
</isClearedNovated>
called template :-
<xsl:template name="cleared_novated">
<xsl:param name="tradeStateId" />
<xsl:for-each select="$tradeStateId/tradeStateIdClassificationScheme">
<xsl:choose>
<xsl:when test="$tradeStateId[starts-with(tradeStateIdClassificationScheme,'accpt')] and systemReference='RDS'">
<xsl:value-of select="'true'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'false'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
I don't really understand, what exactly your needs are, but your XSLT probably does not what you want - I suspect it does nothing...
So maybe we can start with the suggestion below and you can tell, what has to be refined:
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="//tradeStateIdClassificationScheme"/>
</xsl:template>
<xsl:template match="tradeStateIdClassificationScheme[
. = 'Clearing State' and
../tradeStateId = 'accpt_novated_sw' and
../systemReference = 'RDS'
]">
<xsl:value-of select="concat(.,': true
')"/>
</xsl:template>
<xsl:template match="tradeStateIdClassificationScheme">
<xsl:value-of select="concat(.,': false
')"/>
</xsl:template>
<xsl:template match="#*|*"/>
</xsl:transform>
You find two templates dealing with tradeStateIdClassificationScheme, one matches your conditions, and one for all others.
Note that you didn't write about the contents of systemReference, while your trial template addresses this element. Therefore, I added this condition as well.
The output in this version is:
Vn State: false
Mang State: false
Clearing State: true

replacing text in xml using xslt

I have an XML file which has some values in child Element aswell in attributes.
If i want to replace some text when specific value is matched how can i achieve it?
I tried using xlst:translate() function. But i cant use this function for each element or attribute in xml.
So is there anyway to replace/translate value at one shot?
<?xml version="1.0" encoding="UTF-8"?>
<Employee>
<Name>Emp1</Name>
<Age>40</Age>
<sex>M</sex>
<Address>Canada</Address>
<PersonalInformation>
<Country>Canada</country>
<Street1>KO 92</Street1>
</PersonalInformation>
</Employee>
Output :
<?xml version="1.0" encoding="UTF-8"?>
<Employee>
<Name>Emp1</Name>
<Age>40</Age>
<sex>M</sex>
<Address>UnitedStates</Address>
<PersonalInformation>
<Country>UnitedStates</country>
<Street1>KO 92</Street1>
</PersonalInformation>
</Employee>
in the output, replaced text from Canada to UnitedStates.
so, without using xslt:transform() functions on any element , i should be able to replace text Canada to UnitedStates irrespective of level nodes.
Where ever i find 'Canada' i should be able to replace to 'UnitedStates' in entire xml.
So how can i achieve this.?
I. XSLT 1.0 solution:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:Reps>
<rep>
<old>replace this</old>
<new>replaced</new>
</rep>
<rep>
<old>cat</old>
<new>tiger</new>
</rep>
</my:Reps>
<xsl:variable name="vReps" select=
"document('')/*/my:Reps/*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name()}">
<xsl:call-template name="replace">
<xsl:with-param name="pText" select="."/>
</xsl:call-template>
</xsl:attribute>
</xsl:template>
<xsl:template match="text()" name="replace">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<xsl:choose>
<xsl:when test=
"not($vReps/old[contains($pText, .)])">
<xsl:copy-of select="$pText"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vthisRep" select=
"$vReps/old[contains($pText, .)][1]
"/>
<xsl:variable name="vNewText">
<xsl:value-of
select="substring-before($pText, $vthisRep)"/>
<xsl:value-of select="$vthisRep/../new"/>
<xsl:value-of select=
"substring-after($pText, $vthisRep)"/>
</xsl:variable>
<xsl:call-template name="replace">
<xsl:with-param name="pText"
select="$vNewText"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>
<a attr1="X replace this Y">
<b>cat mouse replace this cat dog</b>
</a>
<c/>
</t>
produces the wanted, correct result:
<t>
<a attr1="X replaced Y">
<b>tiger mouse replaced tiger dog</b>
</a>
<c/>
</t>
Explanation:
The identity rule is used to copy "as-is" some nodes.
We perform multiple replacements, parameterized in my:Reps
If a text node or an attribute doesn't contain any rep-target, it is copied as-is.
If a text node or an attribute contains text to be replaced (rep target), then the replacements are done in the order specified in my:Reps
If the string contains more than one string target, then all targets are replaced: first all occurences of the first rep target, then all occurences of the second rep target, ..., last all occurences of the last rep target.
II. XSLT 2.0 solution:
In XSLT 2.0 one can simply use the standard XPath 2.0 function replace(). However, for multiple replacements the solution would be still very similar to the XSLT 1.0 solution specified above.

xsl get array of elements

Hi
I need get array of elements (before "-" if exist) by xsl.
xml is
<Cars>
<Car Trunck="511"/>
<Car Trunck="483-20"/>
<Car Trunck="745"/>
</Cars>
xsl is
<xsl:variable name="testarr">
<xsl:for-each select="//Cars//Car/#Trunck">
<xsl:value-of select="number(substring(.,1,3))" />
</xsl:for-each>
</xsl:variable>
(i suppose that all numbers is three-digit number, if someone knows a solution for all conditions will be glad to hear the proposal)
if i do this
i get all numbers in one line: 511483745
and i need get them in array
because i also need get the max value
thanks
Hi I need get array of elements
(before "-" if exist) [...] i need get
them in array because i also need get
the max value
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="/Cars/Car/#Trunck">
<xsl:sort select="concat(substring-before(.,'-'),
substring(., 1 div not(contains(.,'-'))))"
data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:value-of
select="concat(substring-before(.,'-'),
substring(.,1 div not(contains(.,'-'))))"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
745
XPath 2.0 one line:
max(/Cars/Car/#Trunck/number(replace(.,'-.*','')))
You could use the substring-before and substring-after functions: See the excellent ZVON tutorial
http://zvon.org/xxl/XSLTreference/Output/function_substring-after.html
In your example you are only extracting the values (which are strings) which get concatenated. Perhaps you need to wrap the result in your own element
<xsl:for-each select="//Cars//Car/#Trunck">
<truck>
<xsl:value-of select="number(substring(.,1,3))" />
</truck>
</xsl:for-each>
While you have two good answers (especially that by #Alejandro), here's one from me that I think is even better:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pTopNums" select="2"/>
<xsl:template match="/*">
<xsl:apply-templates select="*">
<xsl:sort data-type="number" order="descending"
select="substring-before(concat(#Trunck,'-'),'-')"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Car">
<xsl:if test="not(position() > $pTopNums)">
<xsl:value-of select=
"substring-before(concat(#Trunck,'-'),'-')"/>
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (the originally provided one, slightly changed to be more challenging):
<Cars>
<Car Trunck="483-20"/>
<Car Trunck="311"/>
<Car Trunck="745"/>
</Cars>
produces the wanted, correct result (the top two numbers that are derived from #Trunck as specified in the question):
745
483