Is it correct that it is possible to use XPath only on the document node?
I have an XSLT where I want to do the following:
<xsl:variable name="parameters" select="doc('document.xml')//element"/>
<xsl:variable name="file" select="$parameters/file/#file"/>
but it selects nothing.
Related
If I do something like this:
<xsl:variable name="something">
<xsl:value-of select="node1" /><br />
<xsl:value-of select="node2" /><br />
<xsl:value-of select="node3" /><br />
<xsl:value-of select="node4" /><br />
</xsl:variable>
<h1><xsl:value-of select="$something" /></h1>
The line breaks are ignored. Is that right?
I'm using xslt 1.0 if that makes any difference.
Thanks in advance.
You should be using xsl:copy-of here, not xsl:value-of because xsl:value-of only outputs the text value of a node
<h1><xsl:copy-of select="$something" /></h1>
You should use copy-of instead of value-of in order to maintain the <br/>. The reason is that the value-of looks for the text of the node that you select so if you want the text and the elements of that node you would need the copy and not the value.
Since you are using XSLT 1.0 you may also want to look into exslt:node-set for better handling of result tree fragments like this.
As a side note, your h1 is being closed by an h2 which is not valid. Hopefully that was just a copy and paste error.
Input XML:
<derivatives>
<derivative id="4" name="Audio Content">
<operator id="1" name="Reliance">
<referenceCode code="62033815">
<mobileCircle id="1" name="Maharashtra"/>
</referenceCode>
</operator>
<operator id="22" name="Aircel">
<referenceCode code="811327">
<mobileCircle id="1" name="Maharashtra"/>
</referenceCode>
</operator>
</derivative>
</derivatives>
Expected Output XML:
<hellotune>
<operator>Aircel</operator>
<vcode>811327</vcode>
</hellotune>
Current output (which is wrong):
<hellotune>
<operator>Aircel</operator>
<vcode/>
</hellotune>
XSL (which is not working):
<xsl:if test="derivatives/derivative/operator[#name='Aircel']">
<hellotune>
<operator>Aircel</operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</hellotune>
</xsl:if>
Note: Using XSL v1.0. Not mentioned the complete XSL for brevity.
Based on the XSL you provided, it can be presumed that the context node is the root node, but starting from the root node, the path referenceCode/#code does not match anything in your input. Appending derivatives/derivative/operator/ before that path would succeed in finding a referenceCode #code attribute, but it would find the wrong one. Try this push-style approach:
<xsl:template match="/">
<xsl:apply-templates select="derivatives/derivative/operator[#name='Aircel']" />
</xsl:template>
<xsl:template match="operator">
<hellotune>
<operator><xsl:value-of select="#name" /></operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</hellotune>
</xsl:template>
The xpath xpression in the element <vcode> is not referring to any node in the input document. The best way to match all the nodes is to use
//
like in your code you can use
//referenceCode/#code
(but this is only for information purpose and using same can't get your result).
you can try this way:
<xsl:template match="/">
<hellotune>
<xsl:for-each select="//operator">
<xsl:if test="./#name='Aircel'">
<operator><xsl:value-of select="#name"/></operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</xsl:if>
</xsl:for-each>
</hellotune>
</xsl:template>
Hope this helps :)
I'm trying to write a EXSLT function, but for some reason it doesn't seem to return the result. The function is supposed to look up a node in another document:
<func:function name="toc:element">
<xsl:param name="id" />
<xsl:for-each select="$toc">
<func:result select="key('id', $id)" />
</xsl:for-each>
</func:function>
With xsl:message I can see that it indeed receives a valid $id and the key() returns a single node; but when I call it from another template, it seems to produce no result.
Environment: libxml2/libxslt, not sure how to check the version; I'm using them from lxml v3.2.3.
Found a solution: if I add an intermediate variable, everything works.
<func:function name="toc:element">
<xsl:param name="id" />
<xsl:for-each select="$toc">
<xsl:variable name="result" select="key('id', $id)" />
<func:result select="$result" />
</xsl:for-each>
</func:function>
Apparently, the function result uses the original context to evaluate.
Imagine the following xml
<elements>
<element>
<elementID>0x1000</elementID>
<elementSort>1</elementSort>
<elementName>Master Joda</elementName>
<modifyDate>1979-01-01</modifyDate>
</element>
<element>
<elementID>0x1000</elementID>
<elementSort>1</elementSort>
<elementName>Master Yoda</elementName>
<modifyDate>1979-01-05</modifyDate>
</element>
<element>
<elementID>0x2000</elementID>
<elementSort>2</elementSort>
<elementName>Luke Skywalker</elementName>
<modifyDate>1979-01-08</modifyDate>
</element>
</elements>
I use the following xslt to select a list of unique IDs into a variable
<xsl:variable name="ids"
select="elements/element/elementID[not(.=following::elementID)]" />
Then i let xslt build some html for each ID (the output will be a horizontal list of elements per ID)
<xsl:for-each select="$ids">
<xsl:variable name="elementID" select="." />
<div class="itemContainer clear" style="width:{$containerWidth}">
<xsl:for-each select="/elements/element[elementID=$elementID]">
<xsl:sort select="modifyDate" />
<xsl:call-template name="elementTemplate" />
</xsl:for-each>
</div>
</xsl:for-each>
The problem is: how can i sort the elements in the first level of the for-each nesting (the IDs) without having the Element by which i want to sort in the list itself (the ID list).
In practical terms: how can i sort by Jedi hierarchy (master -> pupil), if elementSort 1 means master and elementSort 2 means pupil, having multiple elements per hierarchy in each row, which are then ordered by modifyDate.
Instead of:
<xsl:for-each select="/elements/element/elementID[elementID=$elementID]">
<xsl:sort select="modifyDate" />
<xsl:call-template name="elementTemplate" />
</xsl:for-each>
BTW, the above is obviously incorrect, because an elementID element doesn't have any elementID child at all.
use:
<xsl:for-each select="/elements/element[elementID=$elementID]">
<xsl:sort select="elementSort" data-type="number" />
<xsl:sort select="modifyDate" />
<xsl:call-template name="elementTemplate" />
</xsl:for-each>
I have found a solution though it is probably not very good "xslt-design".
I store list of the unique elementSorts in an additional variable and put another for-each around the original first one. Then i combine the conditions (uniqueness and sortID) while setting the variable holding the unique element IDs.
<xsl:variable name="ids"
select="elements/element/elementID[not(.=following::elementID) and ../elementSort=$sort]" />
edit:
probably even better:
<xsl:variable name="ids"
select="elements/element[elementSort=$sort]/elementID[not(.=following::elementID)]" />
another note
if there are other nodes in the xml document that can contain elementID nodes, than you would have to specify the following:: clause as follows to avoid unwanted behaviour:
<xsl:variable name="ids"
select="elements/element[elementSort=$sort]/elementID[not(.=following::element/elementID)]" />
That way only elementIDs inside of element nodes are taken into consideration for evaluating the uniqueness, comes in handy if there were nodes that relate to the element nodes by elementID.
I need to be able to store a node set in variable and then perform more filting/sorting on it afterward. All the examples I've seen of this involve either using XSL2 or extensions neither of which are really an option.
I've a list of hotels in my XML doc that can be sorted/filtered and then paged through 5 at a time. I'm finding though I'm repeating alot of the logic as currently I've not found a good way to store node-sets in xsl variable and then use xpath on them for further filtering/sorting.
This is the sort of thing I'm after (excuse the code written of the top of my head so might not be 100%):
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="3_star_or_less" select="/results/hotels[number(rating) <= 3]" />
<xsl:for-each select="3_star_or_less">
<xsl:sort select="rating" />
</xsl:for-each>
Has anyone got an example of how best to do this sort of thing?
Try this example:
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="three_star_or_less"
select="$hotels[number(rating) <= 3]" />
<xsl:for-each select="$three_star_or_less">
<xsl:sort select="rating" />
<xsl:value-of select="rating" />
</xsl:for-each>
There is no problem storing a node-set in a variable in XSLT 1.0, and no extensions are needed. If you just use an XPath expression in select attribute of xsl:variable, you'll end up doing just that.
The problem is only when you want to store the nodes that you yourself had generated in a variable, and even then only if you want to query over them later. The problem here is that nodes you output don't have type "node-set" - instead, they're what is called a "result tree fragment". You can store that to a variable, and you can use that variable to insert the fragment into output (or another variable) later on, but you cannot use XPath to query over it. That's when you need either EXSLT node-set() function (which converts a result tree fragment to a node-set), or XSLT 2.0 (in which there are no result tree fragments, only sequences of nodes, regardless of where they come from).
For your example as given, this doesn't seem to be a problem. Rubens' answer gives the exact syntax.
Another note, if you want to be able to use the variable as part of an XPath statement, you need to select into the variable with <xsl:copy-of select="."/> instead of <xsl:value-of select="."/>
value-of will only take the text of the node and you wont be able to use the node-set function to return anything meaningful.
<xsl:variable name="myStringVar">
<xsl:value-of select="."/>
</xsl:variable>
<!-- This won't work: -->
<Output>
<xsl:value-of select="node-set($myStringVar)/SubNode" />
</Output>
<xsl:variable name="myNodeSetVar">
<xsl:copy-of select="."/>
</xsl:variable>
<!-- This will work: -->
<Output>
<xsl:value-of select="node-set($myNodeSetVar)/SubNode" />
</Output>