(I'm posting this self-answered question because the typically offered solution to this issue is needlessly verbose and I'd like to set the record straight. I couldn't find an existing SO question for this, but if there is one, please close this as a duplicate.)
I am looking for a way to perform an XPath selection to select the current node only if it matches a certain condition. This would be useful, for example, when I want to conditionally apply an XSLT template to the current node:
<xsl:template match="Device">
<div>
<h2><xsl:value-of select="Name" /></h2>
<xsl:apply-templates select="???[Featured = 'true']" mode="featured" />
<p><xsl:value-of select="Description" /></p>
</div>
</xsl:template>
<xsl:template match="Book">
<div>
<h2><xsl:value-of select="Title" /></h2>
<xsl:apply-templates select="???[FeaturedBook = 'true']" mode="featured" />
<h3><xsl:value-of select="Author" /></h3>
<p><xsl:value-of select="Summary" /></p>
</div>
</xsl:template>
<xsl:template match="node()" mode="featured">
<p class='featured-notice'>This is a featured item!
Buy now to get a 15% discount.
</p>
</xsl:template>
I have tried using .[Featured = 'true'], but I get a syntax error. How can I do this?
I'm not going to add an input and output here since they are tangential to the question and would make it exceedingly long, but if you want to see what I have in mind, I have placed them here: input, output.
The syntax .[predicate] is not allowed in XPath 1.0 on account of the syntax rules (see the end of this post for the gritty details).
100% of the advice I have found says that the only option is to use self::node() for this:
self::node()[Featured = 'true']
This XPath tester is even specifically designed to tell users to use self::node()[predicate] if they try to use .[predicate], but this is not the only option.
A valid and more concise option is to just wrap the abbreviated step in parentheses:
(.)[Featured = 'true']
This is perfectly valid by XPath 1.0 syntax rules (and in my opinion, a lot clearer).
You can also use this approach with the .. abbreviated step, even going up multiple levels:
Select grandfather node if it is featured
../..[Featured = 'true'] - Not valid
../../../*[Featured = 'true'] - Valid, but not accurate
../../self::node()[Featured = 'true'] - Valid, but verbose
(../..)[Featured = 'true'] - Valid
Addendum: Why it's not possible to use .[predicate] in XPath 1.0
The following is the definition of a "step" in XPath 1.0 (basically, the pieces of an XPath node selection expression separated by slashes are called "steps"):
[4] Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
This means that one step consists of one of two possible options:
An axis specifier (which can be an empty string), followed by a node test, followed by 0 or more predicates
An abbreviated step: . or ..
There is no option to have an abbreviated step followed by predicates.
<xsl:template match="Device">
<div>
<h2><xsl:value-of select="Name" /></h2>
<xsl:apply-templates select="Featured[. = 'true']" />
<p><xsl:value-of select="Description" /></p>
</div>
</xsl:template>
<xsl:template match="Book">
<div>
<h2><xsl:value-of select="Title" /></h2>
<xsl:apply-templates select="FeaturedBook[. = 'true']" />
<h3><xsl:value-of select="Author" /></h3>
<p><xsl:value-of select="Summary" /></p>
</div>
</xsl:template>
<xsl:template match="FeaturedBook|Featured">
<p class='featured-notice'>This is a featured item!
Buy now to get a 15% discount.
</p>
</xsl:template>
Related
I try to convert my old html by xslt-script to my new xml stucture.
I have a Problem to converting the folowing source to my needed xml structure.
Source
<p>
<a class="DropDown">Example Text</a>
</p>
<div class="collapsed">
<table>..</table>
<p>..</p>
</div>
xml structure
<lq>
<p>Example Text</p>
<table>..</table>
<p>..</p>
</lp>
I tried the following xls, but the div class="collapsed" is not adopted into the lp tag.
<xsl:template match="p/a[#class='DropDown']">
<lp>
<p><xsl:apply-templates select="text()"/></p>
<xsl:if test="/p/a/following-sibling::*[1][self::div]">
<xsl:apply-templates select="*|text()"/>
</xsl:if>
</lp>
</xsl:template>
Can anyone tell me what I did wrong ore where the mistake is?
Thanks much
IMHO, you want to do:
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[a/#class='DropDown']">
<lp>
<p>
<xsl:value-of select="a"/>
</p>
<xsl:copy-of select="following-sibling::*[1][self::div]/node()"/>
</lp>
</xsl:template>
<xsl:template match="div[preceding-sibling::*[1][self::p/a/#class='DropDown']]"/>
As for your mistake:
You are testing the existence of some p that is the root element
and contains an a whose following sibling is div. None of these are true in the given example;
xsl:if does not change the context: your <xsl:apply-templates
select="*|text()"/> applies templates to the child nodes of the
current a;
Presumably you don't want the div to appear again in the original place -
so if you have another template to suppress it, you cannot use
<xsl:apply-templates> to insert it at the place you do want it -
at least not without using another mode.
(I'm posting this self-answered question because the typically offered solution to this issue is needlessly verbose and I'd like to set the record straight. I couldn't find an existing SO question for this, but if there is one, please close this as a duplicate.)
I am looking for a way to perform an XPath selection to select the current node only if it matches a certain condition. This would be useful, for example, when I want to conditionally apply an XSLT template to the current node:
<xsl:template match="Device">
<div>
<h2><xsl:value-of select="Name" /></h2>
<xsl:apply-templates select="???[Featured = 'true']" mode="featured" />
<p><xsl:value-of select="Description" /></p>
</div>
</xsl:template>
<xsl:template match="Book">
<div>
<h2><xsl:value-of select="Title" /></h2>
<xsl:apply-templates select="???[FeaturedBook = 'true']" mode="featured" />
<h3><xsl:value-of select="Author" /></h3>
<p><xsl:value-of select="Summary" /></p>
</div>
</xsl:template>
<xsl:template match="node()" mode="featured">
<p class='featured-notice'>This is a featured item!
Buy now to get a 15% discount.
</p>
</xsl:template>
I have tried using .[Featured = 'true'], but I get a syntax error. How can I do this?
I'm not going to add an input and output here since they are tangential to the question and would make it exceedingly long, but if you want to see what I have in mind, I have placed them here: input, output.
The syntax .[predicate] is not allowed in XPath 1.0 on account of the syntax rules (see the end of this post for the gritty details).
100% of the advice I have found says that the only option is to use self::node() for this:
self::node()[Featured = 'true']
This XPath tester is even specifically designed to tell users to use self::node()[predicate] if they try to use .[predicate], but this is not the only option.
A valid and more concise option is to just wrap the abbreviated step in parentheses:
(.)[Featured = 'true']
This is perfectly valid by XPath 1.0 syntax rules (and in my opinion, a lot clearer).
You can also use this approach with the .. abbreviated step, even going up multiple levels:
Select grandfather node if it is featured
../..[Featured = 'true'] - Not valid
../../../*[Featured = 'true'] - Valid, but not accurate
../../self::node()[Featured = 'true'] - Valid, but verbose
(../..)[Featured = 'true'] - Valid
Addendum: Why it's not possible to use .[predicate] in XPath 1.0
The following is the definition of a "step" in XPath 1.0 (basically, the pieces of an XPath node selection expression separated by slashes are called "steps"):
[4] Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
This means that one step consists of one of two possible options:
An axis specifier (which can be an empty string), followed by a node test, followed by 0 or more predicates
An abbreviated step: . or ..
There is no option to have an abbreviated step followed by predicates.
<xsl:template match="Device">
<div>
<h2><xsl:value-of select="Name" /></h2>
<xsl:apply-templates select="Featured[. = 'true']" />
<p><xsl:value-of select="Description" /></p>
</div>
</xsl:template>
<xsl:template match="Book">
<div>
<h2><xsl:value-of select="Title" /></h2>
<xsl:apply-templates select="FeaturedBook[. = 'true']" />
<h3><xsl:value-of select="Author" /></h3>
<p><xsl:value-of select="Summary" /></p>
</div>
</xsl:template>
<xsl:template match="FeaturedBook|Featured">
<p class='featured-notice'>This is a featured item!
Buy now to get a 15% discount.
</p>
</xsl:template>
Is it possible to have out-of-sequence tags within XSLT using 1.0? My initial guess is not, as it breaks the rules of XML.
Consider XML data that has X elements, and I want to split those X entries into blocks of 3 within individual <div> blocks. What I would like to do is something this, but obviously it is completely invalid code...
<div>
<xsl:for-each select="mydata">
<xsl:value-of select="myvalue"/><br/>
<xsl:if test="(position() mod 3)=0">
</div> <!-- This is invalid -->
<div> <!-- This is invalid -->
</xsl:if>
</xsl:for-each>
</div>
So for 8 elements, the example result would be
<div>
value1<br/>
value2<br/>
value3<br/>
</div>
<div>
value4<br/>
value5<br/>
value6<br/>
</div>
<div>
value7<br/>
value8<br/>
</div>
If the above is simply not possible (as I suspect it is not), can somebody suggest an acceptable way to group them like this?
(Please note, this must be an XSLT 1.0 solution)
What you're trying to do is possible, but it isn't a good idea. This is a better approach:
<xsl:apply-templates select="mydata[position() mod 3 = 1]" mode="group" />
<!-- Separate templates -->
<xsl:template match="mydata" mode="group">
<div>
<xsl:apply-templates select=". | following-sibling::mydata[position() < 3]" />
</div>
</xsl:template>
<xsl:template match="mydata">
<xsl:value-of select="myvalue"/><br/>
</xsl:template>
JLRishe shows you the solution.
Your problem is that you are thinking of your stylesheet as writing start and end tags. That's not what XSLT does: it writes a tree. You can't write half a node to the result tree. Think nodes, not tags.
When you have problems like this in which the output structure doesn't exactly match the input structure, another useful rule of thumb is that the structure of the stylesheet should reflect the tree structure of the output, not that of the input. Don't think "what shall I do with the next ABC input node", but rather "I need to generate an XYZ node in the result tree, how shall I compute its content?".
Here's a generic XSLT 1.0 question which I need to know to write an XSLT statement for processing docbook xml files. In my docbook XML, I'm trying to write a compound xpath statement in XSLT 1.0 that says, hardcode a new attribute "class = "play" for p tags in html output.
I want this action to be done for every <para> tag which does NOT have these attributes
role="normal-play-paragraph" AND
role ="no-indent" AND
"role="line-verse"
Here is my XML source:
<chapter xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="5.0" xml:id="play">
<title> Hamlet </title>
<para role="no-indent"> SPHINX. Do you think about it very much?</para>
<para role="normal-play-para"> INTERVIEWER. I do so say. </para>
<para>SPHINX. Hello </para>
<para> INTERVIEWER. dddddWhy I do so say. </para>
<para> SPHINX. Yes. </para>
<para role="line-verse"> Cosmologists have theorized or guessed</para>
</chapter>
I want the HTML output to look like this after Docbook XSLT processes it:
<html>
<body>
<p class="no-indent">SPHINX. Do you think about it very much? much. </p>
<p class="normal-play-para"> INTERVIEWER. I do so say. </p>
<p class="play">SPHINX. Hello </p>
<p class="play">INTERVIEWER. dddddWhy I do so say. </p>
<p class="play">SPHINX. Yes. </p>
<p class="line-verse"> Cosmologists have theorized or guessed</p>
</body>
<html>
The docbook xslt has 2 mechanisms at work which you don't really need to know about.
First, in <para role=""> elements, the value of role is changed into class of p. This is the default behavior.
Second, I'm using a special mode to hardcode a "class='play'" into p tags.
<xsl:template match="d:chapter[#xml:id = 'play']/d:para" mode="class.attribute" >
<xsl:param name="class" select="local-name(.)"/>
<xsl:attribute name="class">play</xsl:attribute>
</xsl:template>
However, I want class="play" to be hardcoded only when there are other attributes & values NOT present. I can modify the above statement to exclude all para tags with the attribute role="line-verse" :
<xsl:template match="d:chapter[#xml:id = 'play']/d:para[#role != 'line-verse']" mode="class.attribute" >
<xsl:param name="class" select="local-name(.)"/>
<xsl:attribute name="class">play</xsl:attribute>
</xsl:template>
But I need more than that. I want to exclude not only role= "line-verse," but also role="no-indent" and role="normal-play-para".
So I have to change the value of the xpath statement in the match attribute so that it excludes three attribute values. I haven't the foggiest idea how to do that. Does anybody know? Thanks.
Update about Answer:
First, I want to thank all of you for taking the time to understand my question and formulate an answer. I should mention that I am still a novice on this stuff, and also, my question was a little unfair because I am using some sophisticated/complicated Docbook XSL. Therefore I need an answer that doesn't cause collisions with the Docbook XSL stylesheets. Also, I realize that you wrote transformations that may be perfectly valid answers in generating html output if I were not also importing the docbook xsl.
The answer which I chose as "best" here may not be the most elegant, but simply the one that worked for me in the case when I am importing the epub3 docbook-ns stylesheets. So Mr. Rishe's one line answer actually does exactly I need it to do even if it isn't as elegant.
I really don't know what's going on in this customization which I started out with:
<xsl:template match="d:chapter[#xml:id = 'play']/d:para" mode="class.attribute" >
<xsl:param name="class" select="local-name(.)"/>
<xsl:attribute name="class">play</xsl:attribute>
</xsl:template>
What I do know is that it's invoking a <xsl:template name="generate.class.attribute"> which is found here. http://50.56.245.89/xsl-ns/xhtml-1_1/html.xsl
Another thing. Dimitre Novatchev's 2 answers looks as though they would work. By the way, you forgot to include the <xsl:param name="class" select="local-name(.)"/> statement -- which is easily fixed -- and that solution works.
However, Dimitre, I have another question. The second answer you gave used variables, which looks simple and functional. If I try it, my Saxon 6.5 parser gives a validation error. (E [Saxon6.5.5] The match pattern in xsl:template may not contain references to variables). Maybe it's something simple like a typo. But is it possible that variables are not allowed in XSLT 1.0 template matches?
Could you give this a try:
<!-- Special handling for paras with one of the three roles -->
<xsl:template
match="d:chapter[#xml:id = 'play']/d:para[#role = 'line-verse' or #role = 'normal-play-para' or #role - 'line-indent']"
mode="class.attribute" >
<xsl:attribute name="class">
<xsl:value-of select="#role" />
</xsl:attribute>
</xsl:template>
<!-- Other paras get the default class "play" -->
<xsl:template match="d:chapter[#xml:id = 'play']/d:para" mode="class.attribute">
<xsl:attribute name="class">play</xsl:attribute>
</xsl:template>
One step further would be to have the <xsl:attribute> in the template that's calling these templates, and just have the needed value in the class.attribute templates themselves. Something like this:
<xsl:template match="d:chapter[#xml:id = 'play']/d:para">
<p>
<xsl:attribute name="class">
<xsl:apply-templates select="." mode="class.attribute" />
</xsl:attribute>
...
</p>
</xsl:template>
<!-- Special handling for paras with one of the three roles -->
<xsl:template
match="d:chapter[#xml:id = 'play']/d:para[#role = 'line-verse' or #role = 'normal-play-para' or #role - 'line-indent']"
mode="class.attribute" >
<xsl:value-of select="#role" />
</xsl:template>
<!-- Other paras get the default class "play" -->
<xsl:template match="d:chapter[#xml:id = 'play']/d:para" mode="class.attribute">
<xsl:text>play</xsl:text>
</xsl:template>
To specifically answer your original question, if you really needed a template that specifically matches paras that don't have one of those #role values, you could match on this XPath:
d:chapter[#xml:id = 'play']/d:para[not(#role = 'line-verse' or #role = 'normal-play-para' or #role - 'line-indent')]
But I think the approach I've presented above (treat paras those roles as the special case, and treat everything else as the default) is the better way to go.
One possible solution is:
<xsl:template mode="class.attribute" match=
"d:chapter[#xml:id = 'play']
/d:para[not(#role = 'line-verse'
or #role = 'no-indent'
or #role = 'normal-play-para'
)]" >
<xsl:attribute name="class">play</xsl:attribute>
</xsl:template>
However, I would use a more flexible and extensible solution, that allows easy modification of the "non-play" values:
<xsl:param name="pNonPlayVals">
<val>line-verse</val>
<val>no-indent</val>
<val>normal-play-para</val>
</xsl:param>
<xsl:template mode="class.attribute" match=
"d:chapter[#xml:id = 'play']/d:para
[not(#role = document('')/*/xsl:param[#name='pNonPlayVals']/val)]" >
<xsl:attribute name="class">play</xsl:attribute>
</xsl: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.