I need to write a XSLT to get the value of the nearest dictionary value to a give node. For example, my structure could be as below
<rootnode>
<rootcontainer>
<dictionary>
<key1> value /<key1>
</dictionary>
<pages>
<page1>
<!--xslt goes here-->
</page1>
</pages>
</rootcontainer>
<dictionary>
<key1>
independent value
</key1>
<key2>
value 2
</key2>
</dictionary>
</rootnode>
I want to create variables $key1 and $key2 inside page1. The value of $key1 will be "value" and the value of $key2 will be "value 2". If rootcontainer\dictionary\key1 doesn't exist, the value of $key1 will be "independent value".
I hope this makes sense.
Here is a compact way to define the required variables:
<xsl:variable name="vKey1" select=
"(/*/rootcontainer/dictionary/key1
|
/*/dictionary/key1
)
[1]
"/>
<xsl:variable name="vKey2" select=
"(/*/rootcontainer/dictionary/key2
|
/*/dictionary/key2
)
[1]
"/>
When wrapped in a simple xslt stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="vKey1" select=
"(/*/rootcontainer/dictionary/key1
|
/*/dictionary/key1
)
[1]
"/>
<xsl:variable name="vKey2" select=
"(/*/rootcontainer/dictionary/key2
|
/*/dictionary/key2
)
[1]
"/>
<xsl:template match="/">
Key1: <xsl:value-of select="$vKey1"/>
Key2: <xsl:value-of select="$vKey2"/>
</xsl:template>
</xsl:stylesheet>
and applied on the provided XML document (corrected, as it was severely malformed):
<rootnode>
<rootcontainer>
<dictionary>
<key1> value </key1>
</dictionary>
<pages>
<page1> </page1>
</pages>
</rootcontainer>
<dictionary>
<key1> independent value </key1>
<key2> value 2 </key2>
</dictionary>
</rootnode>
the wanted, correct result is produced:
Key1: value
Key2: value 2
Explanation:
The expression:
(/*/rootcontainer/dictionary/key1
|
/*/dictionary/key1
)
[1]
means:
Take the nodeset of (potentially) the two elements and from them take the first one in document order.
Because, the second of these two elements comes later in document order, it will be the first (and selected), only when the first of the two XPath expressions surrounding the union ( |) operator, doesn't select any element.
I'm not sure I understand the question but you can assign a conditional value to a variable in two ways:
<xsl:choose>
<xsl:when test="your test condition">
<xsl:variable name="key1" select="your value">
</xsl:when>
<xsl:otherwise>
<xsl:variable name="key1" select="your alternative value">
</xsl:otherwise>
</xsl:choose>
Or more succintly:
<xsl:variable name="key1" select="if(your test condition) then your value else your alternative value;"/>
Update: Thanks for the update of the question. I'll give it a go now.
<xsl:template match="page1">
<xsl:choose>
<xsl:when test="../../preceding-sibling:dictionary[1]/key1">
<xsl:variable name="key1" select="../../preceding-sibling:dictionary[1]/key1">
</xsl:when>
<xsl:otherwise>
<xsl:variable name="key1" select="../../../following-sibling:dictionary[1]/key1">
</xsl:otherwise>
</xsl:choose>
</xsl:template>
So the value of $key1 will be the <key1> node in the preceding dictionary if there is one, and the <key1> node in the following dictionary if there isn't. Is that correct?
(You can use the if/then/else structure too if you want to, but I used xsl:choose because it's probably easier to read.)
Related
My question is about xsl:variable and the syntax for a predicate in an Xpath. I've boiled down my question to the point where this short XML can help me demonstrate:
<root>
<tabular>
<col halign="left"/>
<col halign="right"/>
<row>
<cell>Some content</cell>
<cell>Some content</cell>
</row>
</tabular>
</root>
In my application, when I am applying a template on a cell, I need to access the #halign of the corresponding col. In doing so, I have encountered a discrepancy between Xpath expressions that I thought should be equivalent. I would like to understand why this happens. To demonstrate, I apply the XSL at the end of this post using XSLT 1.0.
The cell template in my XSLT here is silly but it lays out the discrepancy I don't understand. Basically it repeatedly tries to print the #halign value corresponding to the second cell. First, using the $col variable that has value 2. Then using [position()=$col]. Then using [number($col)]. Then simply using [2], hard coded. Lastly, using a separate $colsel variable that was defined using a #select attribute.
I expect to see:
ancestor::tabular/col[...]/#halign
[2] makes right
[position()=2] makes right
[number(2)] makes right
(hard 2) [2] makes right
(var #select) [2] makes right
but instead I see:
ancestor::tabular/col[...]/#halign
[2] makes left
[position()=2] makes right
[number(2)] makes right
(hard 2) [2] makes right
(var #select) [2] makes right
Is anyone able to offer an explanation for why using [$col] behaves differently?
Here is the XSL:
<xsl:template match="/">
<xsl:apply-templates select="root/tabular"/>
</xsl:template>
<xsl:template match="tabular">
<xsl:apply-templates select="row"/>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="cell"/>
</xsl:template>
<?xml version='1.0'?> <!-- As XML file -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="root/tabular"/>
</xsl:template>
<xsl:template match="tabular">
<xsl:apply-templates select="row"/>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="cell[2]"/>
</xsl:template>
<xsl:template match="cell[2]">
<xsl:variable name="col">
<xsl:value-of select="2"/>
</xsl:variable>
<xsl:variable name="colsel" select="2"/>
<xsl:text>ancestor::tabular/col[...]/#halign</xsl:text>
<xsl:text>
</xsl:text>
<xsl:text> [</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[$col]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text> [position()=</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[position()=$col]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text> [number(</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>)] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[number($col)]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>(hard 2) [2] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[2]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>(var #select) [</xsl:text>
<xsl:value-of select="$colsel"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[$colsel]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Let us use a more convenient example:
XML
<root>
<item>first</item>
<item>second</item>
</root>
XSLT 1.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="num" select="2"/>
<xsl:variable name="str" select="string(2)"/>
<xsl:variable name="rtf">2</xsl:variable>
<xsl:template match="/root">
<results>
<num>
<xsl:copy-of select="item[$num]"/>
</num>
<str>
<xsl:copy-of select="item[$str]"/>
</str>
<rtf>
<xsl:copy-of select="item[$rtf]"/>
</rtf>
</results>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<results>
<num>
<item>second</item>
</num>
<str>
<item>first</item>
<item>second</item>
</str>
<rtf>
<item>first</item>
<item>second</item>
</rtf>
</results>
Now you ask why the difference in the results. The answer can be found in the XPath specification that prescribes how a predicate is to be evaluated:
A PredicateExpr is evaluated by evaluating the Expr and converting the
result to a boolean. If the result is a number, the result will be
converted to true if the number is equal to the context position and
will be converted to false otherwise; if the result is not a number,
then the result will be converted as if by a call to the boolean
function.
In the first instance the value of the $num variable is the number 2. Therefore the result of evaluating the expression within the predicate is a number, and the predicate will be true when the number is equal to the context position - which is only true for the item in the second position.
In the second instance, the value of the $str variable is the string "2". Therefore the expression within the predicate does not evaluate to a number and will be converted to boolean by doing:
boolean("2")
which returns true() for all items, regardless of their position.
In the third instance, the value of the $rtf variable is a result tree fragment that contains a text node that consists of the character "2". When placed in a predicate, the outcome will be similar to the previous instance: the result of evaluating the expression is not a number, and converting it to a boolean will produce a value of true(). Note that your:
<xsl:variable name="col">
<xsl:value-of select="2"/>
</xsl:variable>
does exactly the same thing.
Note also that in XSLT 1.0 the xsl:value-of instruction returns the value of the first node in the selected node-set. Therefore, if we change our template to:
<xsl:template match="/root">
<results>
<num>
<xsl:value-of select="item[$num]"/>
</num>
<str>
<xsl:value-of select="item[$str]"/>
</str>
<rtf>
<xsl:value-of select="item[$rtf]"/>
</rtf>
</results>
</xsl:template>
the result will be:
<results>
<num>second</num>
<str>first</str>
<rtf>first</rtf>
</results>
but still both items are selected by item[$str] and by item[$rtf].
Change the variable declaration to:
<xsl:variable name="col" select="2"/>
and it will behave as you expect and select the second col.
You had declared the variable using xsl:value-of: <xsl:value-of select="2"/>, which creates a computed text() node.
When you use that $col variable by itself in a predicate, that string value "2" it is evaluated as true() in the predicate test, rather than if it were a number() and would then be interpreted as short-hand for position() = 2.
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>
xml file like below I want concat value of name, add1, city, add2 with separated by comma
<Details>
<name>abc</name>
<profile>
<address>
<add1>ccc</add1>
<add2>bbb</add2>
<city>CA</city>
</address>
</profile>
</Details>
I want Output like below:-
abc, ccc, CA, bbb
(I mean city will come first before add2 and if any value is blank then it will adjust accordingly)
If you are looking to output all the text nodes within the Details element, you simply iterate over them all using xsl:for-each and use the position() function to output a comma if the node is not the first one
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Details">
<xsl:for-each select="//text()">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
So, if one of your elements has no text in it, it will not get output or have an extra comma.
<xsl:variable name="name">
<xsl:value-of select="Details/name"/>
</xsl:variable>
<xsl:variable name="add1">
<xsl:value-of select="Details/profile/address/add1"/>
</xsl:variable>
<xsl:variable name="add2">
<xsl:value-of select="Details/profile/address/add2"/>
</xsl:variable>
<xsl:variable name="city">
<xsl:value-of select="Details/profile/address/city"/>
</xsl:variable>
<xsl:value-of select="concat($name,',',$add1,',',$city,',',$add2)"/><br>
It will display the O/P like this abc, ccc, CA, bbb if add1 returns null then it will display like this abc, , CA, bbb
If you're using XSLT 2.0 you can use the () operator to construct a sequence in the order you want and then use the separator attribute on xsl:value-of to output the whole sequence with commas:
<xsl:template match="Details">
<xsl:value-of select="(name, profile/address/add1, profile/address/city,
profile/address/add2)" separator=", " />
</xsl:template>
If you want to filter out elements with an empty value (e.g. if the document contains <city/>) then you can do that with a predicate on the select expression:
(name, profile/address/add1, profile/address/city,
profile/address/add2)[normalize-space()]
The predicate removes from the sequence any nodes whose value is empty or consists entirely of whitespace.
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
I am having an issue trying to figure out var scoping on xslt. What I actually want to do it to ignore 'trip' tags that have a repeated 'tourcode'.
Sample XML:
<trip>
<tourcode>X1</tourcode>
<result>Budapest</result>
</trip>
<trip>
<tourcode>X1</tourcode>
<result>Budapest</result>
</trip>
<trip>
<tourcode>X1</tourcode>
<result>Budapest</result>
</trip>
<trip>
<tourcode>Y1</tourcode>
<result>london</result>
</trip>
<trip>
<tourcode>Y1</tourcode>
<result>london</result>
</trip>
<trip>
<tourcode>Z1</tourcode>
<result>Rome</result>
</trip>
XSLT Processor:
<xsl:for-each select="trip">
<xsl:if test="not(tourcode = $temp)">
<xsl:variable name="temp" select="tour"/>
// Do Something (Print result!)
</xsl:if>
</xsl:for-each>
Desired Output:
Budapest london Rome
You can't change variables in XSLT.
You need to think about it more as functional programming instead of procedural, because XSLT is a functional language. Think about the variable scoping in something like this pseudocode:
variable temp = 5
call function other()
print temp
define function other()
variable temp = 10
print temp
What do you expect the output to be? It should be 10 5, not 10 10, because the temp inside the function other isn't the same variable as the temp outside that function.
It's the same in XSLT. Variables, once created, cannot be redefined because they are write-once, read-many variables by design.
If you want to make a variable's value defined conditionally, you'll need to define the variable conditionally, like this:
<xsl:variable name="temp">
<xsl:choose>
<xsl:when test="not(tourcode = 'a')">
<xsl:text>b</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>a</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if test="$temp = 'b'">
<!-- Do something -->
</xsl:if>
The variable is only defined in one place, but its value is conditional. Now that temp's value is set, it cannot be redefined later. In functional programming, variables are more like read-only parameters in that they can be set but can't be changed later. You must understand this properly in order to use variables in any functional programming language.
Desired Output: Budapest london Rome
What you are after is grouping output by city name. There are two common ways to do this in XSLT.
One of them is this:
<xsl:template match="/allTrips">
<xsl:apply-templates select="trip" />
</xsl:template>
<xsl:template match="trip">
<!-- test if there is any preceding <trip> with the same <result> -->
<xsl:if test="not(preceding-sibling::trip[result = current()/result])">
<!-- if there is not, output the current <result> -->
<xsl:copy-of select="result" />
</xsl:if>
</xsl:template>
And the other one is called Muenchian grouping and #Rubens Farias just posted an answer that shows how to do it.
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="trip" match="trip" use="result" />
<xsl:template match="/trips">
<xsl:for-each select="trip[count(. | key('trip', result)[1]) = 1]">
<xsl:if test="position() != 1">, </xsl:if>
<xsl:value-of select="result"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>