2

I've been trying to figure out how to best modularize my XSLT stylesheets to facilitate re-use. I hit upon the idea of using <xsl:apply-imports/> as a way of introducing document-specific attributes to standard tag transformations. This is not working the way I expected it would, and I can't even begin to fathom what is going on here. Here is a simplified version of the stylesheet:

<!-- main.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet  version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:fo="http://www.w3.org/1999/XSL/Format">

<xsl:import href="html-customizations.xsl"/>

<xsl:output method="xml"
   indent="yes"
   omit-xml-declaration="no"/>

<xsl:template match="para">
  <fo:block>
      <xsl:attribute name="space-after">1em</xsl:attribute>
      <xsl:apply-templates/>
  </fo:block>
</xsl:template>

<!-- =============== -->
<!-- Inline Elements -->
<!-- =============== -->

<xsl:template match="i">
  <fo:inline font-style="italic">
    <xsl:apply-imports/>
    <xsl:apply-templates/>
  </fo:inline>
</xsl:template>

<!-- ================ -->
<!--      Tables      -->
<!-- ================ -->

<xsl:template match="table">
  <fo:table>
    <xsl:apply-imports/>
    <xsl:apply-templates/>
  </fo:table>
</xsl:template>

<xsl:template  match="tr">
  <fo:table-row>
    <xsl:apply-imports/>
    <xsl:apply-templates/>
  </fo:table-row>
</xsl:template>

<xsl:template match="td | th">
  <fo:table-cell>
    <xsl:apply-imports/>
    <xsl:apply-templates/>
  </fo:table-cell>
</xsl:template>
</xsl:stylesheet>

The imported stylesheet:

<!-- html-customizations.xsl -->
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet  version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:fo="http://www.w3.org/1999/XSL/Format">

<xsl:template  match="td | th">
  <xsl:attribute name="hyphenate">true</xsl:attribute>
</xsl:template>

</xsl:stylesheet>

Here is the XML input file:

<!-- test.xml -->
<para>
  <table>
    <tr><td>Spongebob Squarepants, <i>Chair</i></td></tr>
    <tr><td>Patrick Starfish, <i>Vice Cchair</i></td></tr>
    <tr><td>Squidword, <i>Secretary</i></td></tr>
  </table>
</para>

$ xalan -o out.xml test.xml main.xsl

out.xml:
<?xml version="1.0" encoding="UTF-8"?>
<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" space-after="1em">
  <fo:table>
    <fo:table-row>
<fo:table-cell hyphenate="true">Spongebob Squarepants, <fo:inline font-style="italic">ChairChair</fo:inline>
</fo:table-cell>
<fo:table-cell hyphenate="true">Spongebob Squarepants, <fo:inline font-style="italic">ChairChair</fo:inline>
</fo:table-cell>
</fo:table-row>
    <fo:table-row>
<fo:table-cell hyphenate="true">Patrick Starfish, <fo:inline font-style="italic">Vice CchairVice Cchair</fo:inline>
</fo:table-cell>
<fo:table-cell hyphenate="true">Patrick Starfish, <fo:inline font-style="italic">Vice CchairVice Cchair</fo:inline>
</fo:table-cell>
</fo:table-row>
    <fo:table-row>
<fo:table-cell hyphenate="true">Squidword, <fo:inline font-style="italic">SecretarySecretary</fo:inline>
</fo:table-cell>
<fo:table-cell hyphenate="true">Squidword, <fo:inline font-style="italic">SecretarySecretary</fo:inline>
</fo:table-cell>
</fo:table-row>

    <fo:table-row>
<fo:table-cell hyphenate="true">Spongebob Squarepants, <fo:inline font-style="italic">ChairChair</fo:inline>
...
...

As you can see, every child of an element matched by a template that includes <xsl:apply-imports/> is repeated! I included the imported stylesheet in order to illustrate what I'm trying to do. If I comment out this import:

<!--
<xsl:import href="html-customizations.xsl"/>
-->

The repeating behavior is the same:

<?xml version="1.0" encoding="UTF-8"?>
<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" space-after="1em">
  <fo:table>
    <fo:table-row>
<fo:table-cell>Spongebob Squarepants, <fo:inline font-style="italic">ChairChair</fo:inline>Spongebob Squarepants, <fo:inline font-style="italic">ChairChair</fo:inline>
</fo:table-cell>
<fo:table-cell>Spongebob Squarepants, <fo:inline font-style="italic">ChairChair</fo:inline>Spongebob Squarepants, <fo:inline font-style="italic">ChairChair</fo:inline>
</fo:table-cell>
</fo:table-row>
    <fo:table-row>
...
...

sans the attribute I'm trying to add from the imported stylesheet; i.e. just the presence of the <xsl:apply-imports/> processing instruction causes the output elements to be doubled. Also note that this is not just a xalan problem -- the same thing happens on MSXML on Windows 7.

Any thoughts? I was counting on this working, so am now pulling my hair out trying to figure out how to fix this so it works.

BTW, my assumptions of how <xsl:apply-imports/> can be used is based on the examples given under the xsl:import section of Michael Kay's book. If anyone knows of a reference that explains the behavior I'm seeing above, please share.

Mathias Müller
  • 22,203
  • 13
  • 58
  • 75
pgoetz
  • 848
  • 1
  • 8
  • 18

1 Answers1

4

I agree that the behaviour of apply-imports is difficult to understand. The problem is that apply-imports always finds a template that matches the current node, even if the user did not define it. In that case, the default template applies.

The following stylesheet works:

XSLT Stylesheet

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet  version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format">

    <xsl:import href="html-customizations.xsl"/>

    <xsl:output method="xml"
        indent="yes"
        omit-xml-declaration="no"/>

    <xsl:template match="para">
        <fo:block>
            <xsl:attribute name="space-after">1em</xsl:attribute>
            <xsl:apply-templates/>
        </fo:block>
    </xsl:template>

    <!-- =============== -->
    <!-- Inline Elements -->
    <!-- =============== -->

    <xsl:template match="i">
        <fo:inline font-style="italic">
            <xsl:apply-templates/>
        </fo:inline>
    </xsl:template>

    <!-- ================ -->
    <!--      Tables      -->
    <!-- ================ -->

    <xsl:template match="table">
        <fo:table>

            <xsl:apply-templates/>
        </fo:table>
    </xsl:template>

    <xsl:template  match="tr">
        <fo:table-row>

            <xsl:apply-templates/>
        </fo:table-row>
    </xsl:template>

    <xsl:template match="td | th">
        <fo:table-cell>
            <xsl:apply-imports/>
            <xsl:apply-templates/>
        </fo:table-cell>
    </xsl:template>
</xsl:stylesheet>

As you can see, I have removed two apply-imports elements, only leaving the one inside template/@match='td | th'. Then, the output will be

XML Output

<?xml version="1.0" encoding="UTF-8"?>
<fo:block xmlns:fo="http://www.w3.org/1999/XSL/Format" space-after="1em">
    <fo:table>
        <fo:table-row>
         <fo:table-cell hyphenate="true">Spongebob Squarepants, <fo:inline font-style="italic">ChairChair</fo:inline>
         </fo:table-cell>
      </fo:table-row>
        <fo:table-row>
         <fo:table-cell hyphenate="true">Patrick Starfish, <fo:inline font-style="italic">Vice CchairVice Cchair</fo:inline>
         </fo:table-cell>
      </fo:table-row>
        <fo:table-row>
         <fo:table-cell hyphenate="true">Squidword, <fo:inline font-style="italic">SecretarySecretary</fo:inline>
         </fo:table-cell>
      </fo:table-row>
    </fo:table>
</fo:block>

What exactly is happening?

apply-imports looks for a template that

  • matches the current node
  • matches the current mode
  • is inside an imported stylesheet

Now, the crucial bit is: this instruction will invoke the built-in templates if no such template can be found in an imported stylesheet. In the case of tr:

<xsl:template  match="tr">
  <fo:table-row>
    <xsl:apply-imports/>
    <xsl:apply-templates/>
  </fo:table-row>
</xsl:template>

The default action for element nodes is traversing it and applying templates to its content, so the snippet above actually translates to

<xsl:template  match="tr">
  <fo:table-row>
    <xsl:apply-templates/>
    <xsl:apply-templates/>
  </fo:table-row>
</xsl:template>

and this is why the output contains duplicates. I assume now you also understand why commenting out xsl:import did not help, otherwise I'm glad to elaborate.


Since you were also asking for a reference, this is explained in the XSLT 2.0 and XPath 2.0 Programmer's Reference by Michael Kay, page 238.

Community
  • 1
  • 1
Mathias Müller
  • 22,203
  • 13
  • 58
  • 75
  • Thanks, Mathias -- that answers my question. In your example, you also need to take the out of the `match="i"` template -- notice that the italicized text is still doubled; e.g. ChairChair. Unfortunately, this doesn't solve my problem, as I want the main style sheet (actually the design is simplified for the question -- actually, there is in intermediate stylesheet) which I want to be able to use completely unedited. If I have to edit this stylesheet, I might as well just put everthing into the templates there. – pgoetz Apr 29 '15 at 21:23
  • Meanwhile, I think I've thought of another way of doing this: call generic attribute sets on each element that are defined in an included stylesheet and which might or might not have any content. I will think about this a bit more and then post an example for the benefit of the next person who comes along with a question about this. Thanks again! I thought I was going insane trying to debug this yesterday, as the actual stylesheet is quite long and complicated. It took me several hours before I realized that was responsible for my misery. – pgoetz Apr 29 '15 at 21:30
  • @pgoetz You are right, edited my answer to remove the `apply-imports`. Wouldn't assigning attribute sets to elements not also require you to edit the main stylesheet? – Mathias Müller Apr 29 '15 at 21:42
  • No, I would include the attribute sets in a separate stylesheet and then call them generically: ``. The attribute set `customize-td` is defined in a separated included stylesheet and will generally be empty (but can be used to set document-specific default attributes for the entire document). – pgoetz Apr 29 '15 at 22:13