1

Here is the scenario. I have XML documents with tags that look like this:

<para  a="A"  b="B"  c="C">

appearing in different classes of XML documents. The a and b attributes are completely generic and are handled exactly the same way in all documents. The optional c attribute is document class dependent, and will require different transforms, depending on the document class. I would like to write a stylesheet to be included or imported into document class-specific stylesheets which take care of doing the transform for and attributes a and b, which attribute c handled by the parent stylesheet. I can think of at least a couple of ways to do this, but am wondering if there is some canonical best way.

Let's call the stylesheet to be shared st-generic.xsl. Each of the templates in st-generic.xsl would be named:

<xsl:template match="para" name="generic-para">...</xsl:template>

The document class specific stylesheets would then import st-generic.xsl (rather than include, to set precedence), and would include templates that look like this:

<xsl:template match="para">
   <xsl:call-template name="generic-para"/>
   {other stuff}
   <xsl:apply-templates/>
</xsl:template>

This probably works, but seems a bit inelegant. For example, in most cases the generic-para template is all that is needed, so this template will need to similarly include an

<xsl:apply-templates/>

node in the template body. I'm wondering if there is a better way to do this?

pgoetz
  • 848
  • 1
  • 8
  • 18

4 Answers4

2

Without seeing more of your code and input, I don't see why you should use named templates.

Let's consider two hypothetical elements in your input:

<para  a="A"  b="B"  c="C">paracontent</para>
<div  a="A"  b="B"  c="C">divcontent</div>

Now, let us assume both attribute a and b are generic attributes that can be handled in the same way, no matter what element they occur on. c is processed in more than one way, depending on the parent element.

There is of course a template matching para elements

<xsl:template match="para">

and I don't see why you need a named template to process the attributes of this element. Why not simply apply-templates to all attributes?

<xsl:template match="para">
   <xsl:apply-templates select="@*"/>
   <!--Do stuff other than processing attributes...-->
</xsl:template>

Then, other templates (not named ones) would match the two generic attributes:

<xsl:template match="@a">
  <!--Process attribute a, no matter the parent element-->
</xsl:template>

and

<xsl:template match="@b">
  <!--Process attribute b, no matter the parent element-->
</xsl:template>

or perhaps even

<xsl:template match="@a|@b">
  <!--Process attributes a or b, no matter the parent element-->
</xsl:template>

whereas you would write separate templates for attribute c:

<xsl:template match="para/@c">
  <!--Process attribute c, if para is the parent-->
</xsl:template>

and

<xsl:template match="div/@c">
  <!--Process attribute c, if div is the parent-->
</xsl:template>

All of this code is still in separate templates and can be modularized and imported or included ad libitum.

Mathias Müller
  • 22,203
  • 13
  • 58
  • 75
  • I should have been a bit more clear. The attributes are entirely generic across document classes; not necessarily tags; i.e. attribute a in could be quite different from a in
    . I have one group of documents about elephants, and another group of documents about point-set topology. The tag is used in both document classes, and the treatment of the a attribute is completely the same in both, however the c attribute, if it exists, might be handled differently in an elephant document than it is in a point-set topology document.
    – pgoetz Mar 13 '15 at 09:40
  • That said, this is a pretty good solution if there are only a very small number of unique attributes. Then your solution above would work with more specific attribute matching in the template. Say c is the only unique attribute. Then the second line in the third code snippet above would be . The a and b attributes would just be handled in the main template or perhaps by calling another template. – pgoetz Mar 13 '15 at 19:37
1

The best I can think of at the moment is:

Include this in your st-generic.xsl file:

<xsl:template match="para">
    { do para processing }
    <xsl:apply-templates select="para" mode="custom" />
    <xsl:apply-templates />
</xsl:template>
<xsl:template match="para" mode="custom" priority="-5" />

Then when you need custom behavior, you can put this in your main template:

<xsl:template match="para" mode="custom">
    { do custom para processing }
</xsl:template>

this will be invoked between the { do para processing } and the <xsl:apply-templates /> in the generic file, so you can have the custom template focus on custom behavior.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • Yep, this was the second solution I came up with, although I was visualizing it with named templates -- I like your solution of using modes better. This seems like the best way to solve this problem, since nothing happens if you don't need and haven't supplied the custom mode hooks. – pgoetz Mar 13 '15 at 09:03
0

XSL provides a canonical way of solving this problem using the <xsl:apply-imports/> element.

In this particular example, you would have the st-generic.xsl stylesheet import a customizations.xsl stylesheet which would include optional customizations:

st-generic.xsl:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:import href="customizations.xsl"/>
   <xsl:template match="para">
      <xsl:if test="@a and string(@a)">
         {do stuff}
      </xsl:if>
      <xsl:if test="@b and string(@b)">
         {do other stuff}
      </xsl:if>
      <xsl:apply-imports/>
      <xsl:apply-templates/>
   </xsl:template>
/xsl:stylesheet>

customizations.xsl:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="para">
      <xsl:if test="@c and string(@c)">
         {do special stuff}
      </xsl:if>
   </xsl:template>
/xsl:stylesheet>

A completely flexible solution is provided if you also name the templates in the st-generic.xsl stylesheet, which then allows you to import st-generic.xsl into yet another stylesheet, calling the named templates as needed or using <xsl:apply-imports/> as used in the previous example.

pgoetz
  • 848
  • 1
  • 8
  • 18
0

So, my original answer proved not to work because of the unfortunate way that <xsl:apply-imports/> works when there is no matching template. See the answer to this question for details. The bottom line is it resorts to default templates, which ends up messing the output when you use it the way I had it in my original answer.

In pondering what to do about this I came up with an alternative solution I like better anyway, namely use attribute sets defined in an included stylesheet. For example, to handle the case proposed in the original question, you could do something like this:

The main stylesheet would look like this:

<xsl:include href="generic-attributes.xsl"/>

<xsl:template  match="para">
  <fo:block xsl:use-attribute-sets="para_default-attrs">
    <xsl:if test="@a and string(@a)">
     {do stuff}
    </xsl:if>
    <xsl:if test="@b and string(@b)">
     {do other stuff}
    </xsl:if>
     ~~ other stuff ~~
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>

The generic-attributes.xsl file would then contain this:

<xsl:attribute-set  name="para_default-attrs">
  <xsl:attribute name="a">A</xsl:attribute>
  <xsl:attribute name="b">B</xsl:attribute>
</xsl:attribute-set>
Community
  • 1
  • 1
pgoetz
  • 848
  • 1
  • 8
  • 18