Rephrasing my question properly:
I have XSLT where I need to check for node values for specific data.
E.g. (pseudocode):
If only H occurs then set target element value as 'H'
Else if
Only B or N then set target element value as 'BN'
If Source XML:
<n1>N</n1>
<n1>B</n1>
<n1>N</n1>
Target node:
BN
If Source XML:
<n1>H</n1>
<n1>H</n1>
<n1>H</n1>
Target node:
H
There is no attribute. I have to only set text on meeting the condition in the target node.
Can I use something like:
<xsl:variable name="elements">
<xsl:for-each select="/test/elem">
<xsl:value-of select="."/>
<xsl:if test="position() < last()">
</xsl:if>
</xsl:for-each>
</xsl:variable>
Would the above give me concat of all element values and then I can do a check to see the if that contains x then I can do a set text?
When this stylesheet
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="value">
<xsl:choose>
<xsl:when test="/root[not(n1 != 'H')]">
<xsl:value-of select="'H'"/>
</xsl:when>
<xsl:when test="/root[not(n1[. != 'B' and . != 'N'])]">
<xsl:value-of select="'BN'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'Something else'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:text>$value = </xsl:text>
<xsl:value-of select="$value"/>
</xsl:template>
</xsl:stylesheet>
is given this input
<root>
<n1>H</n1>
<n1>H</n1>
<n1>H</n1>
</root>
it produces the desired output
$value = H
Similarly with
<root>
<n1>N</n1>
<n1>B</n1>
<n1>N</n1>
</root>
or with
<root>
<n1>N</n1>
<n1>N</n1>
<n1>N</n1>
</root>
we get
$value = BN
And with
<root>
<n1>N</n1>
<n1>B</n1>
<n1>x</n1>
</root>
or with
<root>
<n1>N</n1>
<n1>B</n1>
<n1>H</n1>
</root>
we get
$value = Something else
The string H, BN or Something else is the value of a variable. Instead of just outputting the value of that variable you can of course set it as the contents of a new element you create.
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.
While transforming a document, I need to 'look up' certain node contents in a 'map', and write those values.
I inlined my 'map' in the transformation.
<xsl:variable name="inlinedmap">
<kat id="stuff">value</kat>
<!-- ... -->
</xsl:variable>
<xsl:variable name="map" select="document('')/xsl:stylesheet/xsl:variable[#name='inlinedmap']" />
<xsl:template match="/">
<xsl:for-each select="/*/foo">
<!-- 'bar' contents should equal to contents of 'kat' -->
<xsl:variable name="g" select="$map/key[.=bar]"/>
<xsl:choose>
<xsl:when test="$g != ''">
<xsl:value-of select="$g/#id"/>
</xsl:when>
<xsl:otherwise>
ERROR
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
I'm always getting ERROR value.
I can't put map value's into attributes, because they contain letters that get escaped.
How can I make it work?
I think there are a few problems here:
You seem to be looking for key elements in your variable, but they're called kat there (typo?)
You seem to be trying to reference the bar child of the context node inside the loop, but you need to use current() to do that
You should create this map as elements in your own namespace instead of an xsl:variable
Here's a complete example. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my">
<my:vars>
<kat id="stuff">value</kat>
<!-- ... -->
</my:vars>
<xsl:variable name="map" select="document('')/*/my:vars/*"/>
<xsl:template match="/">
<xsl:for-each select="/*/foo">
<!-- 'bar' contents should equal to contents of 'kat' -->
<xsl:variable name="g" select="$map[.=current()/bar]"/>
<xsl:choose>
<xsl:when test="$g != ''">
<xsl:value-of select="$g/#id"/>
</xsl:when>
<xsl:otherwise>
ERROR
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Applied to this input:
<root>
<foo><bar>value</bar></foo>
<foo><bar>value1</bar></foo>
<foo><bar>value2</bar></foo>
<foo><bar>value3</bar></foo>
</root>
Produces this output (one match):
stuff
ERROR
ERROR
ERROR
I am new to XSLT.
I have a input XML file which needs to be shown as a different output XML. I am using the xslt for transformation.
Input XML:
<Row>
<Column>abc.xyz.ijm</Column>
<Row>
Output XML:
<abc>
<xyz>
<ijm>String</ijm>
</xyz>
</abc>
I tried using xsl:when along with substring-before and substring-after functions but the result xml is not close to what I want.
How to know the last occurence of '.' so that <ijm>String</ijm> is constructed followed by the end tags of the words that are found before each of the previous occurences of the '.' so that </xyz> and </abc> can be added as shown in the output xml above?
Any code snippet is not at all appreciated.
Thanks in advance.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Column/text()" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length()">
<xsl:choose>
<xsl:when test="not(contains($pText,'.'))">
<xsl:element name="{$pText}">String</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{substring-before($pText,'.')}">
<xsl:call-template name="tokenize">
<xsl:with-param name="pText"
select="substring-after($pText,'.')"/>
</xsl:call-template>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document (corrected to be well-formed):
<Row>
<Column>abc.xyz.ijm</Column>
</Row>
produces the wanted, correct result:
<abc>
<xyz>
<ijm>String</ijm>
</xyz>
</abc>
Explanation:
Recursively called named template with stop condition: the $pText parameter is either the empty string or a string that doesn't contain the period character.
Intermediate action: Create an element whose name is the substring-before the '.' character, then call yourself recursively with the text after the first period character as parameter.
Stop action: Create an element with name -- the whole string in the parameter, and value: the string "String".
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.
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"><</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.