I have a variable in xslt that must contains several values which depends on the page.
I know that <xslt: variable> cannot do the job because variables cannot be changed.
Can xslt:param does the job and how can I change it value on the fly?
Roelof
Edit 1 : I try to explain what I want. I have a website which displays articles per month. What I really like is a script where I can filter the articles so only the articles of a particular month is displayed in pages. But I need to decide how many articles are displayed on each page. So the number of displayed articles is based on the month and the displayed page.
XSLT is a functional language, which, among other things, means that an xsl:variable or xsl:param, once specified cannot change its value.
This doesn't prevent the ability to express in XSLT the solution to any problem (XSLT is Turing-complete) and only requires one to change thinking based on the imperative programming paradigm.
Once you publish your specific problem, many readers will be able to provide complete XSLT solutions.
Here is a simple example:
We "accumulate" all wanted values in a variable, then output it:
<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:variable name="vOddsAccumulator">
<xsl:apply-templates/>
</xsl:variable>
<xsl:template match="/">
<xsl:value-of select="$vOddsAccumulator"/>
</xsl:template>
<xsl:template match="*/*"/>
<xsl:template match="num[. mod 2 = 1]">
<xsl:value-of select="concat(., ' ')"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied to the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted result is produced:
01 03 05 07 09
Related
This is all new to me and I'm trying to learn how it works by examining and modifying some pre-existing XSL on our system, so please excuse that I don't really have a test script to troubleshoot.
I have an exported list of rooms that exist on our system - and a survey of criteria within those rooms.
I want to lookup the surveyed room and alter the behaviour of the output depending on whether it exists on the export or not.
Some sample data below which gives an idea of what I want to achieve. I'm just not sure how to (a) link to the reference xml and (b) query it in this way.
Reference listing XML (name = spaces.xml):
<Listing>
<Space>
<Code>Room1</Code>
</Space>
<Space>
<Code>Room2</Code>
</Space>
<Space>
<Code>Room3</Code>
</Space>
<Space>
<Code>Room4</Code>
</Space>
</Listing>
Survey XML example
<Survey>
<Record>
<Ref>1</Ref>
<Space>Room1</Space>
<Data>123</Data>
</Record>
<Record>
<Ref>2</Ref>
<Space>Room2</Space>
<Data>456</Data>
</Record>
<Record>
<Ref>3</Ref>
<Space>Room5</Space>
<Data>789</Data>
</Record>
</Survey>
As room 5 is not in the listing, the output should behave differently
XSLT so far. This doesn't include the reference to the listing.xml file or the proper test condition
xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml"/>
<xsl:template match="Record">
<OutputData>
<SurveyRef>
<xsl:value-of select="Ref"/>
</SurveyRef>
<xsl:choose>
<xsl:when test="##SPACE IS PRESENT IN SPACES.XML##">
<SpaceRef>
<xsl:value-of select="Space"/>
</SpaceRef>
<xsl:otherwise>
<SpaceRef>CheckTheSpace</SpaceRef>
</xsl:otherwise>
</xsl:choose>
<SurveyResult>
<xsl:value-of select="Data"/>
</SurveyResult>
</OutputData>
</xsl:template>
</xsl:stylesheet>
Hoped-for output
<OutputData>
<SurveyRef>1</SurveyRef>
<SpaceRef>Room1</SpaceRef>
<SurveyResult>123</SurveyResult>
</OutputData>
<OutputData>
<SurveyRef>2</SurveyRef>
<SpaceRef>Room2</SpaceRef>
<SurveyResult>456</SurveyResult>
</OutputData>
<OutputData>
<SurveyRef>3</SurveyRef>
<SpaceRef>CheckTheSpace</SpaceRef>
<SurveyResult>789</SurveyResult>
</OutputData>
Any help with this much appreciated, as I'm sure it will help me get to use this tool more
effectively.
Thanks.
how to (a) link to the reference xml
It depends on where exactly the reference document will be. If you place it at the same location where your XSLT stylesheet is, then you can refer to it using just its name within the document() function. Otherwise you need to use either a relative or a full absolute path.
and (b) query it in this way.
The most convenient way to resolve a cross-reference is by using a key - for example:
XSLT 2.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:key name="spc" match="Space" use="Code" />
<xsl:template match="/Survey">
<root>
<xsl:for-each select="Record">
<OutputData>
<SurveyRef>
<xsl:value-of select="Ref"/>
</SurveyRef>
<SpaceRef>
<xsl:value-of select="if (key('spc', Space, document('space.xml'))) then Space else 'CheckTheSpace'"/>
</SpaceRef>
<SurveyResult>
<xsl:value-of select="Data"/>
</SurveyResult>
</OutputData>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
I have a XSL file in which I am creating a field like this:
<ServiceText>
<xsl:value-of select="concat(Yrs,'-',Mos,'-',Days,'-',Hrs)" />
</ServiceText>
The values of 'Yrs,'-',Mos,'-',Days,'-',Hrs , I am receiving from a Web service response and assiging it to the XSL directly. I cannot do any modification to the data in code for these fields, because that is how the ocde is. All data manipulation is on the xslt.
I want to do a data filtering on xslt as follows:
if value of yrs =-1 then yrs=""
if value of mos =-1 then mos=""
if value of Days =-1 then Days=""
if value of Hrs =-1 then Hrs=""
How can I do it on the XSL file?
XSLT 2.0:
<xsl:template match="/root">
<ServiceText>
<xsl:value-of select="string-join((Yrs, Mos, Days, Hrs)[.!=-1],'-')" />
</ServiceText>
</xsl:template>
See link for a Working example
In XSLT 1.0 use something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*/*[not(.= -1)]">
<xsl:value-of select="concat(., substring('-', 2 - not(self::Hrs)))"/>
</xsl:template>
<xsl:template match="*/*[. = -1]"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<Yrs>-1</Yrs>
<Mos>7</Mos>
<Days>15</Days>
<Hrs>3</Hrs>
</t>
the wanted result is produced:
7-15-3
Do Note:
It seems that there is an assumption that the "-1" values form a contiguous group (right to left in the sequence Yrs, Mos,Days).
If this assumption is violated, it would be impossible to understand what is the missing part in 2013-10-8 -- is it the months or the days ?
So here is my problem...
I have 2 xsl transformations and they both have xsl:for-each in them as a starting point.
I need to create one (master) xslt which will call them. But of course, there is a catch.
This new xslt should give output one node from first xslt, then one node from second.(both xslt have EmployeeId, but are basically different reports that have to be printed one after another).
Because these existing xslt's have for-each in them, when I include them in this master xslt I get an output: all nodes from first xslt, then all nodes from second.
Also this 2 xslt's have to be backward compatible, so they should work as before if they are not called from this master template.
I'm a starter to xslt and I managed to create some reports when working with only one xslt,but I can't seem to find the solution to this problem, so I appreciate all the help I can get.
I was thinking of creating a new xslt that would be a mix of two that I have, but this was ruled out by my boss.
Thanks,
Benxy
EDIT:
Here goes some examples as requested:
This is the first xslt:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="4.0" encoding="windows-1250" indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/" name="testXslt1">
<xsl:for-each select="a">
<table>
<tr>
<td>
<xsl:value-of select="#SomeData"></xsl:value-of>
</td>
etc.
</tr>
<xsl:apply-templates select="b" mode="tmp"/>
</table>
</xsl:for-each>
</xsl:template>
<xsl:template match="node()" mode="tmp">
<tr >
<td><xsl:value-of select="#SomeOtherData"></xsl:value-of></td>
etc.
</tr>
</xsl:template>
Second xslt is similar to first one.
In Master xslt I would import both of them and in for each call templates testXslt1 and testXslt2.
This new xslt should give output one
node from first xslt, then one node
from second
Here is a simple example how this can be done:
This transformation combines the results of two templates that are applied on the XML document.
The first templates produces twice the value of every /*/* node. The second transformation produces the square of the value of every /*/* node. The results of applying the two remplates each on the XML document are mixed together in alternation as required, and this is the final result:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vrtfResults1">
<xsl:apply-templates select="/"
mode="transform1"/>
</xsl:variable>
<xsl:variable name="vrtfResults2">
<xsl:apply-templates select="/"
mode="transform2"/>
</xsl:variable>
<xsl:variable name="vResults1"
select="ext:node-set($vrtfResults1)/node()"/>
<xsl:variable name="vResults2"
select="ext:node-set($vrtfResults2)/node()"/>
<xsl:for-each select="$vResults1">
<xsl:variable name="vPos" select="position()"/>
<xsl:copy-of select=".|$vResults2[$vPos]"/>
</xsl:for-each>
<xsl:if test=
"count($vResults2) > count($vResults2)">
<xsl:copy-of select=
"$vResults2[position()>count($vResults1)]"/>
</xsl:if>
</xsl:template>
<xsl:template match="/" mode="transform1">
<xsl:for-each select="/*/*">
<xsl:copy>
<xsl:value-of select=".+."/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
<xsl:template match="/" mode="transform2">
<xsl:for-each select="/*/*">
<xsl:copy>
<xsl:value-of select=".*."/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on this XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is produced:
<num>2</num>
<num>1</num>
<num>4</num>
<num>4</num>
<num>6</num>
<num>9</num>
<num>8</num>
<num>16</num>
<num>10</num>
<num>25</num>
<num>12</num>
<num>36</num>
<num>14</num>
<num>49</num>
<num>16</num>
<num>64</num>
<num>18</num>
<num>81</num>
<num>20</num>
<num>100</num>
I have a situation where I think I need to daisy chain my xslt transformation (i.e. that output of one xslt transform being input into another). The first transform is rather complex with lots of xsl:choice and ancestor xpaths. My thought is to transform the xml into xml that can then be easily transformed to html.
My question is 'Is this standard practice or am I missing something?'
Thanks in advance.
Stephen
Performing a chain of transformations is used quite often in XSLT applications, though doing this entirely in XSLT 1.0 requires the use of the vendor-specific xxx:node-set() function. In XSLT 2.0 no such extension is needed as the infamous RTF datatype is eliminated there.
Here is an example (too-simple to be meaningful, but illustrating completely how this is done):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates select="/*/*"/>
</xsl:variable>
<xsl:variable name="vPass1"
select="ext:node-set($vrtfPass1)"/>
<xsl:apply-templates mode="pass2"
select="$vPass1/*"/>
</xsl:template>
<xsl:template match="num[. mod 2 = 1]">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="num" mode="pass2">
<xsl:copy>
<xsl:value-of select=". *2"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is produced:
<num>2</num>
<num>6</num>
<num>10</num>
<num>14</num>
<num>18</num>
Explanation:
In the first step the XML document is transformed and the result is defined as the value of the variable $vrtfPass1. This copies only the num elements that have odd value (not even).
The $vrtfPass1 variable, being of type RTF, is not directly usable for XPath expressions so we convert it to a normal tree, using the EXSLT (implemented by most XSLT 1.0 processors) function ext:node-set and defining another variable -- $vPass1 whose value is this tree.
We now perform the second transformation in our chain of transformations -- on the result of the first transformation, that is kept as the value of the variable $vPass1. Not to mess with the first-pass template, we specify that the new processing should be in a named mode, called "pass2". In this mode the value of any num element is multiplied by two.
XSLT 2.0 solution (no RTFs):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vPass1" >
<xsl:apply-templates select="/*/*"/>
</xsl:variable>
<xsl:apply-templates mode="pass2"
select="$vPass1/*"/>
</xsl:template>
<xsl:template match="num[. mod 2 = 1]">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="num" mode="pass2">
<xsl:copy>
<xsl:value-of select=". *2"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If this is your situation (or may become your situation):
Transform initial xml to mediary xml.
Maybe transform mediary xml into final1_html.
Maybe transform mediary xml into final2_html (not at all like final1_html).
or
Transform initial xml into mediary xml. This is reasonably likely to change over time.
Transform mediary xml to final_html. This in not likely to change over time.
Then it makes sense to use a two step transformation.
If this is your situation:
Transform initial xml to mediary xml.
Transform mediary xml to final_html.
Then consider not two stepping. Instead just perform one transformation.
I wouldn't think it was standard practice, in particular since you can transform one XML dialect directly to another.
However, if the processing is complex, splitting it to several steps (applying a different transform in each step) can indeed simplify each step and make sense.
It really depends on the particular situation.
I have some complex XSLT 2.0 transformations. I'm trying to find out if there is general purpose way to ensure that no empty tags are output. So... conceptually, a final stage of processing that recursively removes all empty tags. I understand this could be done by a separate XSLT that did nothing but filter out empty tags, but I need to have it all packaged together in a single one.
This XSLT 2.0 transformation illustrates how multi-pass (in this case 2-pass) processing can be done:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" mode="#all">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates select="$vPass1/*" mode="non-empty"/>
</xsl:template>
<xsl:template match="text()[xs:integer(.) mod 2 eq 0]"/>
<xsl:template match="*[not(node())]" mode="non-empty"/>
</xsl:stylesheet>
when applied on this XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
It creates a result document in the first pass (which is captured in the $vPass1 variable), in which all <num> elements with contents even integer are stripped off their content and are empty. Then, in the second pass, applied in a specific mode, all empty elements are removed.
The result of the transformation is:
<nums>
<num>01</num>
<num>03</num>
<num>05</num>
<num>07</num>
<num>09</num>
</nums>
Do note the use of modes, and the special modes #all and #current.
Update: The OP now wants in a comment to delete "recursively" "all nodes that have no non-empty descendant".
This can be implemented simpler using no explicit recursion. Just change:
<xsl:template match="*[not(node())]" mode="non-empty"/>
to:
<xsl:template match="*[not(descendant::text())]" mode="non-empty"/>