Reputation: 371
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
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?
Upvotes: 2
Views: 466
Reputation: 243479
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>
Upvotes: 1
Reputation: 101700
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 para
s 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 para
s those roles as the special case, and treat everything else as the default) is the better way to go.
Upvotes: 2