I am finally beginning to understand how xslt works.
Since I will be creating several more xslts in the future I would like to write them well.
I am wondering whether there is a preferred way to get the data of an xml tag.
Is it better to use select="." select=" tag name " or is it irrelevant?
For example:
<xsl:value-of select="." />
or
<xsl:value-of select="Vert_Prism" />
To get the data enclosed in the Vert_Prism tag.
<Vert_Prism>1.5</Vert_Prism>
Thanks,
It depends on your context. If your current node is Vert_Prism, then you would use <xsl:value-of select="." /> to get the text value of the current node.
OTOH, <xsl:value-of select="Vert_Prism" /> is an abbreviation of <xsl:value-of select="child::Vert_Prism" /> - so this would not return anything, unless the current Vert_Prism has a child element with the same name. However, it would work fine from the context of the parent node of Vert_Prism.
Related
If I have an XPath to a NodeSet - it is my understanding that the following XSLT will iterate over each node that matches the provided XPath. And the "value-of" function, matching on "." should return the data from each matching node.
<xsl:for-each select="//some/X-Path/Here/*">
<xsl:value-of select="." />
</xsl:for-each">
What if I have an xpath to a single element though? Suddenly doing a "for-each" over that single node seems silly. But I can't find a comparable "feature" in XSLT that accomplishes the same behavior - but where the input is just a single element, rathern than a collection.
<xsl:match select="//some/X-Path/Here[1]">
<xsl:value-of select="." />
</xsl:match">
Or something to this effect. Am I missing something obvious?
Well, you basically answered your question yourself. Iterating over a node set of size one may look a little strange but there are other ways to do it. One is creating a match template as you suggested and having it called through <apply-templates>. The other way is to insert the value directly as #halfbit suggested. IMHO it is very difficult to say what is the best method. It definitely depends on the context.
However, maybe two more thoughts in favor of <for-each> compared to using <value-of>.
The former only execute if the expression actually exists whereas the latter always executes. This is, of course, not not bad for the <value-of> part since it should be emtpty but it may be unhandy if it is surrounded by wrappers which might screw up your output. So in other words a
...
<xsl:for-each select="//some/X-Path/Here/*">
output something here
<xsl:value-of select="." />
output something here
</xsl:for-each">
...
has an implicit if-condition for the block, which on the other hand the construct
...
output something here
<xsl:value-of select="//some/X-Path/Here/*" />
output something here
...
does not. So, you would have to surround it by an <if> then:
...
<xsl:if test="//some/X-Path/Here/*">
output something here
<xsl:value-of select="//some/X-Path/Here/*" />
output something here
<xsl:if>
...
The other thing is that the <for-each> tag changes the context node which may be handy if you want to access more than element down the path of your XPath. So, for example,
...
<xsl:for-each select="//some/X-Path/Here/*">
<xsl:value-of select="#attr1" />
<xsl:value-of select="#attr2" />
<xsl:value-of select="#attr3" />
...
<xsl:value-of select="#attrN" />
</xsl:for-each">
...
is simply shorter (and may be easier to read) than
...
<xsl:if test="//some/X-Path/Here/*">
<xsl:value-of select="//some/X-Path/Here/*/#attr1" />
<xsl:value-of select="//some/X-Path/Here/*/#attr2" />
<xsl:value-of select="//some/X-Path/Here/*/#attr3" />
...
<xsl:value-of select="//some/X-Path/Here/*/#attrN" />
<xsl:if>
...
especially if you have a lengthy XPath expression.
I seem to be having an issue preserving the disable-output-escaping when using that value inside of an xsl:copy-of.
Here's my code:
<xsl:call-template name="Display">
<xsl:with-param name="text">
<xsl:value-of select="content" disable-output-escaping="yes" />
</xsl:with-param>
</xsl:call-template>
<xsl:template name="Display">
<xsl:param name="text" />
<span><xsl:copy-of select="$text" /></span>
</xsl:template>
Any special characters that were kept as-is from the xsl:value-of statement are escaped when they're used in the xsl:copy-of statement.
For example:
<xsl:value-of select="$text" disable-output-escaping="yes"> will display this: รจ
<xsl:copy-of select="$text"> will display è
I'd like to know if there is any way around this?
As per Spec, the disable-output-escaping attribute can be specified only on <xsl:value-of> and the <xsl:text> instructions.
You need the DOE only on the xslt instruction that actually outputs the value, not on one that sets a parameter value.
Solution:
Replace:
<span><xsl:copy-of select="$text"/></span>
with:
<span><xsl:value-of select="$text" disable-output-escaping="yes"/></span>
Do note: Typically one should avoid using DOE, as it breaks the XSLT architectural model and usually isn't needed. Also, the DOE feature isn't mandatory and not all XSLT 1.0 processors support it.
Note 2: You don't actually need DOE in your case at all. The output from the XSLT transformation should be displayed by the browser as expected.
disable-output-escaping controls the action of the serializer when handed a text node. It's meaningless when the text node isn't being handed to a serializer, for example when it is added to a temporary tree.
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.
As I have understood the match atribute of a template tag, it defines what part of the xml tree that will be enclosed in the template.
However ther seem to be some exceptions, I have a working peace of code, lite this:
<xsl:template match="/root/content">
<xsl:for-each select="/root/meta/errors/error">
<p>
<strong>Error:</strong> <xsl:value-of select="message" /> (<xsl:value-of select="data/param" />)<br />
<xsl:for-each select="data/option">
<xsl:value-of select="." /><br />
</xsl:for-each>
</p>
<br /><br />
</xsl:for-each>
</xsl:template>
But when I try to add a conditional like this:
<xsl:template match="/root/content">
<xsl:if test="not(/root/meta/error/errors/data/param)"-->
<xsl:for-each select="/root/meta/errors/error">
<p>
<strong>Error:</strong> <xsl:value-of select="message" /> (<xsl:value-of select="data/param" />)<br />
<xsl:for-each select="data/option">
<xsl:value-of select="." /><br />
</xsl:for-each>
</p>
<br /><br />
</xsl:for-each>
<xsl:call-template name="trip_form">
<xsl:with-param name="type" select="'driver'" />
<xsl:with-param name="size" select="'savetrip'" />
</xsl:call-template>
</xsl:if>
</xsl:template>
It doesn't work any more, why, and how can I make it work again?
Attribute matches are applied when you ask for it (you are pulling with complex and unneeded for-each resulting in no attribute matching at all), otherwise they are ignored. That's why the copy idiom is used with specific attribute apply-templates:
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="* | #*" />
</xsl:copy>
</xsl:template>
When it comes to the order in which they are applied, the order is the document order, which means: after the element is applied, its attributes will be applied (in undetermined order) and then the element's children are applied. Attributes never have children and their parent is the containing element.
"it defines what part of the xml tree that will be enclosed in the template."
No. It is called when the processor encounters input that matches the specification, or when you specifically apply this input by using xsl:apply-templates. Your code should not use xsl:for-each, that's rarely needed. Instead, use xsl:apply-templates. This will also give you the possibility to match the attributes when you like.
Normally, you don't (need to) specify the parent in the match-attribute of apply-templates. And you surely don't write down the whole path inside the templates each time, that will wreak havoc on usability of your stylesheet... Try something like this instead and have a look at some XSL tutorials on the net (w3schools provides some basic information and Tennison's book is next to invaluable to learn about this variant of functional programming):
<xsl:template match="/">
<xsl:apply-templates select="/root/content" />
</xsl:template>
<xsl:template match="content">
<xsl:apply-templates select="errors/error" />
</xsl:template>
<xsl:template match="error">
<p>
<strong>Error:</strong>
<xsl:value-of select="message" />
(<xsl:value-of select="data/param" />)
<br />
<xsl:apply-templates select="data/option" />
</p>
<br /><br />
</xsl:template>
<xsl:template match="option">
<xsl:value-of select="." /><br />
</xsl:template>
"It doesn't work any more, why, and how can I make it work again?"
Because your if-statement is probably always true (or always false). Reason: if anywhere in your document the XPath is correct, it will always be false, if it is never correct, it will always be true. Using xsl:if with an XPath that starts in the root will, for the live of the transformation, always yield the same result. Not sure what you are after, so I cannot really help you further here. Normally, instead of xsl:if, we tend to use a matching template (again, yes, I know it gets boring ;).
Note: you ask something about attributes in your question, this I tried to answer in the opening paragraph (before this edit). However, there's nothing about attributes inside your code, so I don't know how to really help you.
Note on the note: LarsH suggests that you perhaps mean to ask about the match-attribute inside xsl:template. If so, the answer lies in the text above anywhere, where I talk about apply-templates and the sort. In short: the input document is processed, node by node, possibly directed by xsl:apply-templates, and it tries to find a matching template for each node it's currently at. That's all there is to it.
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>