Question

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

  1. role="normal-play-paragraph" AND
  2. role ="no-indent" AND
  3. "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?

Was it helpful?

Solution

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.

OTHER TIPS

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>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top