Multiple tags

1. Applying the same template to multiple tags
2. Applying two templates to the same element

1.

Applying the same template to multiple tags

Jeni Tennison

>Given that I'm really stuck with about 15 different tags, and they're all
>formatted identically, I'd like to change this so that I can use the same
>template for multiple items that are formatted identically

The select attribute specifies a node set that the node you're looking at is matched against. You want that node set to contain all menu, option, screen etc. nodes. You can combine node sets using the '|' operator, so try:

  <xsl:template match="menu | option | screen | ...">
    ...
  </xsl:template>

2.

Applying two templates to the same element

Wendell Piez

The original question went like this.

I have a set of combinations for various renderings of <title> according to attributes and XPath position that handle titles through in my articles and other documents and so forth that I write using TEI (present code shown below).

I now want to add one more universal style application that should also apply in other attribute situations when necessary: I want to apply the TITUS font to all cases of <title lang="sa"> (Language = Sanskrit). I can't add another <xsl:template match="title">, and I can't simply add it as an choose/if/when option, since it is not simply an option, but a document-wide application. I guess there must be a way of doing this.

<xsl:template match="title">
    <xsl:choose>
    <xsl:when test="ancestor::listBibl">
       <xsl:choose>
         <xsl:when test="@level='m'">
            <span style="font-style:italic">
         <xsl:apply-templates/>. </span>
        </xsl:when>
    <xsl:when test="@level='j'">
    <span style="font-style:italic">
      <xsl:apply-templates/>. 
    </span> 
</xsl:when>
<xsl:when test="@level='a'">
&quot;<xsl:apply-templates/>.&quot;
  </xsl:when>
<xsl:when test="@level='u'">
&quot;<xsl:apply-templates/>.&quot;
  </xsl:when>
 <xsl:when test="@rend='bold'">
  <span style="font-weight:bold"><xsl:apply-templates/></span>
</xsl:when>
<xsl:otherwise>
  <span style="font-style:italic"><xsl:apply-templates/></span>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
     <xsl:choose>
<xsl:when test="@level='m'">
    <span style="font-style:italic"><xsl:apply-templates/></span> 
</xsl:when>
<xsl:when test="@level='j'">
    <span style="font-style:italic">
      <xsl:apply-templates/></span> 
</xsl:when>
<xsl:when test="@level='a'">
&quot;<xsl:apply-templates/>&quot; 
  </xsl:when>
<xsl:when test="@level='u'">
&quot;<xsl:apply-templates/>&quot; 
  </xsl:when>
<xsl:otherwise>
  <span style="font-style:italic"><xsl:apply-templates/></span>
</xsl:otherwise>
</xsl:choose></xsl:otherwise>
</xsl:choose>
</xsl:template>

Wendell came back with

I actually think Chuck's Sanskrit spanning is a good use case. The only reason it's not fully clear yet is that he didn't provide a sample of input data. If you put this bit of input together with the code he provided in the thread, you'll get the idea:

<biblStruct id="Wayman-1984">
   <monogr>
     <author>Wayman, Alex</author>
     <title level="m" lang="sa">The Sarvarahasyatantra</title>
     <imprint>...</imprint>
   </monogr>
   <series>
     <title level="s">Acta Indologica</title>
   </series>
</biblStruct>

The problem is to get the title with @lang="sa" into an extra span in the output (over and above what you get for being a title[@level="m"]).

Discriminate two templates that you will use to handle your titles. One will handle their basic output based on their @level or @rend (what you already have); the other will provide the extra span wrapper around the Sanskrit ones.

To do this we first have to determine which will be the "outer" template. In your case, I think it's the one that will fire based on the setting of @lang. Your <span style="font-family: 'Titus'"> will wrap the spans and such you're already generating.

To achieve this we put it in a named template:

<xsl:template name="Sanskrit-wrap">
   <xsl:choose>
     <xsl:when test="@lang = 'sa'">
       <span style="font-family: 'Titus'">
         <xsl:apply-templates/>
       </span>
     </xsl:when>
     <xsl:otherwise>
       <xsl:apply-templates/>
     </xsl:otherwise>
   </xsl:choose>
</xsl:template>

But we don't want just to fire this -- we want it to apply not to the title, but to the result of processing our other template. We do this by extending the new template so it does not simply process down the tree as normally, but instead wraps a parameter we send it:

<xsl:template name="Sanskrit-wrap">
   <xsl:param name="contents">
     <xsl:apply-templates/>
   </xsl:param>
   <xsl:choose>
     <xsl:when test="@lang = 'sa'">
       <span style="font-family: 'Titus'">
         <xsl:copy-of select="$contents"/>
       </span>
     </xsl:when>
     <xsl:otherwise>
       <xsl:copy-of select="$contents"/>
     </xsl:otherwise>
   </xsl:choose>
</xsl:template>

Notice we provide our parameter with a default, namel the same result-tree-fragment we get from applying templates to the children of the current node -- this will come in handy.

Now we have to fix our first template so it calls the second -- the trick here is, now we have a way to get our result *into* the Sanskrit wrapper --

<xsl:template match="title">
   <xsl:call-template name="Sanskrit-wrap">
     <xsl:with-param name="contents">
       <xsl:choose>
         <xsl:when test="ancestor::listBibl">
            <xsl:choose>
              <xsl:when test="@level='m'">
                 <span style="font-style:italic"><xsl:apply-templates/>. 
</span>
              </xsl:when>
              <xsl:when test="@level='j'">
                 <span style="font-style:italic"><xsl:apply-templates/>. 
</span>
              </xsl:when>
              <xsl:when 
test="@level='a'">&quot;<xsl:apply-templates/>.&quot;</xsl:when>
              <xsl:when 
test="@level='u'">&quot;<xsl:apply-templates/>.&quot;</xsl:when>
              <xsl:when test="@rend='bold'">
                <span style="font-weight:bold"><xsl:apply-templates/></span>
              </xsl:when>
              <xsl:otherwise>
                <span
style="font-style:italic"><xsl:apply-templates/></span>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:when>
          <xsl:otherwise>
            <xsl:choose>
              <xsl:when test="@level='m'">
                <span
style="font-style:italic"><xsl:apply-templates/></span>
              </xsl:when>
              <xsl:when test="@level='j'">
                <span
style="font-style:italic"><xsl:apply-templates/></span>
              </xsl:when>
              <xsl:when 
test="@level='a'">&quot;<xsl:apply-templates/>&quot;</xsl:when>
              <xsl:when 
test="@level='u'">&quot;<xsl:apply-templates/>&quot;</xsl:when>
              <xsl:otherwise>
                <span
style="font-style:italic"><xsl:apply-templates/></span>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:with-param>
    </xsl:call-template>
</xsl:template>

I haven't figured out what to call this technique, although I'm finding lots of uses for it. Note that you can also wrap other processing results in <span style="font-family: 'Titus'"> simply by sending in other parameters to the same template -- or none, since it defaults to xsl:apply-templates, which is what you usually want.

So imagine, if you will:

<xsl:template match="title">
   <xsl:call-template name="Sanskrit-wrap">
     <xsl:with-param name="contents">
       <xsl:choose>
         <xsl:when test="ancestor::listBibl">
            <xsl:choose>
              <xsl:when test="@level='m' or @level='j'">
                 <xsl:call-template name="italicize">
                   <xsl:with-param name="contents">
                     <xsl:apply-templates/>. <xsl:text/>
                   </xsl:with-param>
                 </xsl:call-template>
              </xsl:when>
              <xsl:when test="@level='a' or @level='u'">
                 <xsl:call-template name="quote">
                   <xsl:with-param name="contents">
                     <xsl:apply-templates/>.<xsl:text/>
                   </xsl:with-param>
                 </xsl:call-template>
              </xsl:when>
              <xsl:when test="@rend='bold'">
                 <xsl:call-template name="embolden"/>
              </xsl:when>
              <xsl:otherwise>
                 <xsl:call-template name="italicize"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:when>
          <xsl:otherwise>
            <xsl:choose>
              <xsl:when test="@level='a' or @level='u'">
                <xsl:call-template name="quote"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:call-template name="italicize"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:with-param>
    </xsl:call-template>
</xsl:template>

Jeni adds

Wendell wrote:
> Discriminate two templates that you will use to handle your titles.
> One will handle their basic output based on their @level or @rend
> (what you already have); the other will provide the extra span
> wrapper around the Sanskrit ones.

For interest, XSLT 2.0 provides a really neat way of dealing with this kind of problem: using <xsl:next-match>. Basically what <xsl:next-match> says is "find the next best matching template, apply that to the current node, and insert the result here".

So in this case, you would have one template with a high priority that recognised the Sanskrit titles, created the <span> and put the result of using the next match inside the <span>:

<xsl:template match="title[@lang = 'sa']" priority="4">
  <span style="font-family: 'Titus'">
    <xsl:next-match />
  </span>
</xsl:template>

Then you could have another set of templates, at a lower priority, that would deal with the formatting based on the level and rend attributes. Note that because <title> elements without a lang attribute with the value 'sa' wouldn't match the above template, they'd fall immediately through to this template instead.

<xsl:template match="title" priority="3">
  <xsl:choose>
    <xsl:when test="@level = ('m', 'j')">
      <span style="font-style:italic"><xsl:next-match /></span>
    </xsl:when>
    <xsl:when test="@level = ('a', 'u')">"<xsl:next-match />"</xsl:when>
    <xsl:when test="@rend = 'bold'">
      <span style="font-weight:bold"><xsl:next-match /></span>
    </xsl:when>
    <xsl:otherwise>
      <span style="font-style:italic"><xsl:next-match /></span>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

and finally have a level that deals with those <title> elements within <listBibl> elements, which need to have a . put after their values:

<xsl:template match="listBible//title" priority="2">
  <xsl:next-match /><xsl:text>.</xsl:text>
</xsl:template>

When there aren't any matching templates in the stylesheet itself, the built-in template gets used instead.

You can do the same kind of thing in XSLT 1.0 using <xsl:apply-imports>, but you have to split the different layers of processing into different stylesheets (which imports the stylesheet containing the templates with the next highest priority), which can be a real pain.

Or the other thing I'd do here in XSLT 1.0 is to use modes:

<xsl:template match="title[@lang = 'sa']">
  <span style="font-family: 'Titus'">
    <xsl:apply-templates select="." mode="style" />
  </span>
</xsl:template>

<xsl:template match="title">
  <xsl:apply-templates select="." mode="style" />
</xsl:template>

<xsl:template match="title" mode="style">
  <xsl:choose>
    <xsl:when test="@level = 'm' or @level = 'j'">  *** 1
      <span style="font-style:italic">
        <xsl:apply-templates select="." mode="listBibl" />
      </span>
    </xsl:when>
    <xsl:when test="@level = 'a' or @level = 'u'">
      <xsl:text>"</xsl:text>
      <xsl:apply-templates select="." mode="listBibl" />
      <xsl:text>"</xsl:text>
    </xsl:when>
    <xsl:when test="@rend = 'bold'">
      <span style="font-weight:bold">
        <xsl:apply-templates select="." mode="listBibl" />
      </span>
    </xsl:when>
    <xsl:otherwise>
      <span style="font-style:italic">
        <xsl:apply-templates select="." mode="listBibl" />
      </span>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="listBible//title" mode="listBibl">
  <xsl:apply-templates /><xsl:text>.</xsl:text>
</xsl:template>

<xsl:template match="title" mode="listBibl">
  <xsl:apply-templates />
</xsl:template>

Note *** 1:

if one of the operands of the general comparison operators (=, !=, < or >) is a sequence then you get true if any of the comparisons between the the items in that sequence and the other operand are true.

Now that you can create sequences of strings, numbers etc. then it's a lot easier to do:

  @level = ('m', 'j', ...)

rather than:

  @level = 'm' or @level = 'j' or ...