I am using XSLT to convert an XML document to an HTML document.
The XML document contains a <list> element at different points (the list is interrupted by standard paragraphs) that I am converting to an <ol>.
I would like the points within the list to be numbered as one continuous series through the document.
My current solution involves setting a variable:
<xsl:variable name="start">
<xsl:number level="any" from="body" count="list"/>
</xsl:variable>
The variable is then used to set the attribute value:
<xsl:template match="list">
<ol start="{$start}">
<xsl:apply-templates/>
</ol>
</xsl:template>
However, the generated HTML document contains an empty attribute:
<ol start="">
The result is a numbered list that restarts from 1 with every <ol> element.
Since your variable isn't within the xsl:template we can only assume you have declared it as a global variable, in which case its value will never change.
Try using a function instead (requires XSLT 2.0+):
<xsl:function name="f:start" as="xs:string">
<xsl:param name="node" as="node()"/>
<xsl:number select="$node" level="any" from="body" count="list"/>
</xsl:function>
and then <ol start="{f:start(.)}">
Alternatively (and this also works in XSLT 1.0) you can do this with an attribute set:
<xsl:attribute-set name="start-number">
<xsl:attribute name="start">
<xsl:number level="any" from="body" count="list"/>
</xsl:attribute>
</xsl:attribute-set>
and then <ol xsl:use-attribute-sets="start-number">
Related
I would like to pass a for each loop as a parameter to another template
<xsl:template name="dataTableGroup">
<xsl:call-template name="test">
<xsl:with-param name="pContent1">
<xsl:for-each
select="NewDataSet/Table[generate-id() = generate-id(key('countryKey', concat(Unit, ReportingBusUnitDesc)))]">
<tr>
<td class="columnTextRight">
<xsl:value-of select="ReportingBusUnitDesc"/>
</td>
</tr>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
and use it here
<xsl:template name="test">
<xsl:param name="pContent1"/>
<xsl:for-each select="$pContent1">
</xsl:for-each>
</xsl:template>
but instead of nodes I get only values. Idealy I would like to have it work with xslt 1.0.
If you use an XSLT 1.0 processor then the parameter value is a result tree fragment so you need to use a processor specific extension function like exsl:node-set (http://exslt.org/exsl/functions/node-set/index.html) first to convert it into a node set e.g.
<xsl:template name="test">
<xsl:param name="pContent1"/>
<xsl:for-each select="exsl:node-set($pContent1)/tr" xmlns:exsl="http://exslt.org/common">
</xsl:for-each>
</xsl:template>
Node that doing a copy of a result tree fragment is possible without using any extension e.g. <xsl:copy-of select="$pContent1"/> should do.
Also for the completeness of the example I have declared the namespace for the extension function on the xsl:for-each element, normally you would put it on the stylesheet's root element xsl:stylesheet/transform and additionally use exclude-result-prefixes="exsl" to ensure the namespace doesn't occur on any result elements.
With an XSLT 2 or 3 processor you shouldn't have any problems to use the variable directly as a fragment node containing your tr elements.
I try to create a variable, which I can use in a later template:
<xsl:variable name="fc">
<xsl:choose>
<xsl:when test="self::node()='element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Unfortunately it does not work.
<xsl:template match="element1">
<h1><font color="{$fc}"><xsl:value-of select="self::node()"/></font></h1>
</xsl:template>
What am I doing wrong?
Here is the extensive code:
XML:
<root
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.test.com scheme.xsd" xmlns="http://www.test.com" xmlns:tst="http://www.test.com">
<elementA>
<elementB tst:name="name">
<elementC tst:name="name">
<element1> Test1 </element1>
<element2> Test2 </element2>
</elementC >
</elementB>
</elementA>
</root>
All the elements are qualified and part of the namespace "http://www.test.com".
XSLT:
<xsl:template match="/">
<html>
<body><xsl:apply-templates select="tst:root/tst:elementA/tst:elementB/tst:elementC/tst:element1"/>
</body>
</html>
</xsl:template>
<xsl:variable name="var_fc">
<xsl:choose>
<xsl:when test="local-name(.)='tst:element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:template match="tst:element1">
<h2><font color="{$var_fc}"><xsl:value-of select="self::node()"/></font></h2>
</xsl:template>
As a result, element1 should turn gray, but it always turn red.
You can't use a variable for this, as the content of an xsl:variable is evaluated just once at definition time, whereas you want to evaluate some logic every time the variable is referenced, in the current context at the point of reference.
Instead you need a template, either a named one:
<xsl:template name="fc">
<xsl:choose>
<xsl:when test="local-name()='element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:template>
or (better) a pair of matching templates with a mode, to let the template matcher do the work:
<!-- match any node whose local name is "element1" -->
<xsl:template mode="fc" match="node()[local-name() = 'element1']">gray</xsl:template>
<!-- match any other node -->
<xsl:template mode="fc" match="node()">red</xsl:template>
When you want to use this logic:
<h1>
<font>
<xsl:attribute name="color">
<xsl:apply-templates select="." mode="fc" />
</xsl:attribute>
Seeing as you have the tst prefix mapped in your stylesheet you could check the name directly instead of using the local-name() predicate:
<xsl:template mode="fc" match="tst:element1">gray</xsl:template>
<xsl:template mode="fc" match="node()">red</xsl:template>
XSLT variables are designed not to be changeable. Actually they could be named constants. If your variable fc is created global, it will use the root element for choose. You have to use choose in the actual template to be tested against the current element. If you want to have "red" and "gray" defined only once, create two variables with just that text content and use these instead the plain text in the choose.
Maybe it is a typo:
<xsl:when test=self::node()='element1'">gray</xsl:when>
should be:
<xsl:when test="self::node()='element1'">gray</xsl:when>
there is a missing quote.
I think instead of test="self::node()='element1'" you want test="self::element1" or test="local-name(.) = 'element1'".
A couple of other errors in your code:
(1) self::node() = 'element1'
tests whether the content of the element is "element1", not whether its name is "element1"
(2) local-name(.)='tst:element1'
will never be true because the local name of a node never contains a colon.
Experienced users would often write this code using template rules:
<xsl:template mode="var_fc" match="tst:element1">gray</xsl:template>
<xsl:template mode="var_fc" match="*">red</xsl:template>
and then
<xsl:apply-templates select="." mode="var_fc"/>
for performance testing purposes I want to take a small XML file and create a bigger one from it - using XSLT. Here I plan to take each entity (Campaign node in the example below) in the original XML and copy it n times, just changing its ID.
The only way I can think of to realize this, is a xsl:for-each select "1 to n". But when I do this I do not seem to be able to access the entity node anymore (xsl:for-each select="campaigns/campaign" does not work in my case). I am getting a processor error: "cannot be used here: the context item is an atomic value".
It seems that by using the "1 to n" loop, I am loosing the access to my actual entity. Is there any XPath expression that gets me access back or does anyone have a completely different idea how to realize this?
Here is what I do:
Original XML
<campaigns>
<campaign id="1" name="test">
<campaign id="2" name="another name">
</cmpaigns>
XSLT I try to use
<xsl:template match="/">
<xsl:element name="campaigns">
<xsl:for-each select="1 to 10">
<xsl:for-each select="campaigns/campaign">
<xsl:element name="campaign">
<xsl:copy-of select="#*[local-name() != 'id']" />
<xsl:attribute name="id"><xsl:value-of select="#id" /></xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
Define a variable as the first thing in the match, like so:
<xsl:variable name="foo" select="."/>
This defines a variable $foo of type nodeset. Then access it like this
<xsl:for-each select="$foo/campaigns/campaign">
...
</xsl:for-each>
I have
<xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="expression" select="name()" ></xsl:variable>
</xsl:for-each>
below I have href and i want to set the value this expression variable in href #######
Delete
I also tried with :
Delete
None of them worked.
Can Anybody help me out how to do that?
Basically my task is to find the expression for current node and send the expression for the same in href?
Adding More Info :
<br/><xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="expression" select="name()" />
</xsl:for-each>
<b>Click Me</b>
when above xsl code is transformed it is giving below error:
Variable or parameter 'expression' is undefined.
In XSLT1.0, you could try setting the variable like so:
<xsl:variable name="expression">
<xsl:for-each select="ancestor-or-self::*">
<xsl:text>/</xsl:text>
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="$name"/>
</xsl:for-each>
</xsl:variable>
So, assuming the following XML structure
<As>
<a>
<Bs>
<b>5</b>
</Bs>
</a>
<a>
<Bs>
<b>9</b>
</Bs>
</a>
<a/>
<a>
<Bs>
<b>12</b>
<b>14</b>
<b>15</b>
</Bs>
</a>
</As>
If you were positioned on the b element with the value of 14, then expression would be set to /As/a/Bs/b
However, this does not take into account multiple nodes with the same name, and so would not be sufficient if you wanted accurate XPath to select the node.
Instead, you could try the following:
<xsl:variable name="expression">
<xsl:for-each select="ancestor-or-self::*">
<xsl:text>/</xsl:text>
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="$name"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="count(preceding-sibling::*[name() = $name]) + 1"/>
<xsl:text>]</xsl:text>
</xsl:for-each>
</xsl:variable>
This would return /As[1]/a[4]/Bs[1]/b[2], which may be what you want.
Very simply, variables are scoped in XSL and exist only within the containing tag. Thus the variable named expression exists only within the for-each block. Also, variables can only be set once. Attempting to set a variable value a second time has no effect.
Therfore you have to declare the variable at or above the level where you want to use it, and put all the code to generate the value inside the variable declaration. If you can use XSLT2, the following will do what you want:
string-join(for $n in ancestor-or-self::* return name($n), '/')
I know it can also be done in XSLT 1 with a recursive template, but I don't have an example handy.
Apart from rewriting a lot of XSLT code (which I'm not going to do), is there a way to find the position of an element within its parent, when the context is arbitrarily set to something else? Here's an example:
<!-- Here are my records-->
<xsl:for-each select="/path/to/record">
<xsl:variable name="record" select="."/>
<!-- At this point, I could use position() -->
<!-- Set the context to the current record -->
<xsl:for-each select="$record">
<!-- At this point, position() is meaningless because it's always 1 -->
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
</xsl:for-each>
<!-- This template expects the current context being set to a record -->
<xsl:template name="SomeTemplate">
<!-- it does stuff with the record's fields -->
<xsl:value-of select="SomeRecordField"/>
<!-- How to access the record's position in /path/to or in any other path? -->
</xsl:template>
NOTE: This is a simplified example. I have several constraints keeping me from implementing obvious solutions, such as passing new parameters to SomeTemplate, etc. I can really only modify the internals of SomeTemplate.
NOTE: I'm using Xalan 2.7.1 with EXSLT. So those tricks are available
Any ideas?
You could use
<xsl:value-of select="count(preceding-sibling::record)" />
or even, generically,
<xsl:value-of select="count(preceding-sibling::*[name() = name(current())])" />
Of course this approach will not work if you process a list of nodes that is not uniform, i.e.:
<xsl:apply-templates select="here/foo|/somewhere/else/bar" />
Position information is lost in such a case, unless you store it in a variable and pass that to the called template:
<xsl:variable name="pos" select="position()" />
<xsl:for-each select="$record">
<xsl:call-template name="SomeTemplate">
<xsl:with-param name="pos" select="$pos" />
</xsl:call-template>
</xsl:for-each>
but obviously that would mean some code rewriting, which I realize you want to avoid.
Final hint: position() does not tell you the position of the node within its parent. It tells you the position of the current node relative to the list of nodes you are processing right now.
If you only process (i.e. "apply templates to" or "loop over") nodes within one parent, this happens to be the same thing. If you don't, it's not.
Final hint #2: This
<xsl:for-each select="/path/to/record">
<xsl:variable name="record" select="."/>
<xsl:for-each select="$record">
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
</xsl:for-each>
is is equivalent to this:
<xsl:for-each select="/path/to/record">
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
but the latter works without destroying the meaning of position(). Calling a template does not change context, so . will refer to the correct node withing the called template.