tags:

views:

295

answers:

3

i would like to create a master template in XSLT, which could be stored in a separate file. Every other Page stylesheets share it, with xsl:import.

master.xslt

 <xsl:template match="Page">
  <html>
   <head>
   </head>
   <body>

     <call-template name="Content"/>

   </body>
  </html> 
 </xsl:template>
<xsl:stylesheet>

page.xslt

<xsl:stylesheet>
<xsl:import href="master.xslt"/>

<xsl:template match="/">
  <apply-templates match="Page"/>
</xsl:template>

<xsl:template name="Content">
  ... apply something page-specific
</xsl:template>

</xsl:stylesheet>

page.xml

<Page>
 ... something page-specific
</Page>

Can i improve this solution?

  • i cannot start from master stylesheet, because i will need xsl:import everything.
  • i dont want master.xslt contain references on each particular page.

Another decision (which is against the xslt spirit) maybe such:

master.xslt

<xsl:template name="masterHead">
 <html>
  <head>
  </head>
  <body>
</xsl:template>

<xsl:template name=masterEnd>
 </body>
 </html> 
</xsl:template>

</xsl:stylesheet>

page.xslt

<xsl:stylesheet>
<xsl:import href="master.xslt"/>

<xsl:template match="/">
  <call-template name=masterHead>
   ... apply something page-specific
  <call-template name=masterEnd/>
</xsl:template>

</xsl:stylesheet>

we don't need any general root <Page> element.

A: 

That looks about right to me... very common to what I have used in the past (although I've often used <xsl:include/>, but either should work). The main change I might make is to make the match more explicit (at least in the master xslt) - i.e.

<xsl:template match="/Page"> <!-- leading slash -->

so it won't accidentally match Page elements at other locations (for example, data-paging, like <Page Index="3" Size="20"/>).

One other common thing I do is to add a "*" match that uses xsl:message to throw an error if I don't have a more-specific match for a node. This makes it more obvious when you have a typo, etc.

Marc Gravell
Aa leading / is actually not a good decision. The value of the @match attribute is a "match pattern" and as such itdoesn't need to be an absolute XPath expression. To which nodes a template is being applied is determined by the (dynamic) context of the <xsl:apply-templates> that selects it.
Dimitre Novatchev
@Dimitre: it is a good idea if you only want it to match root Page elements.
Marc Gravell
+1  A: 

Using <xsl:import> is the right design decision. This is exactly the main use-case this XSLT directive was intended for.

One can go further even more -- lookup for the <xsl:apply-imports> directive, and in addition to how an imported stylesheet can apply templates about whose actions and meaning it absolutely doesn't know anything. The latter is called Higher-Order-Functions and is implemented in XSLT with the FXSL library (written entirely in XSLT).

Dimitre Novatchev
A: 

I'm actually glad to have found this example as I've been looking for verification that this is actually the correct approach to a master/slave template setup. However the examples provided did not work out of the box on tomcat - so just to help others who only knows how to copy paste here are a working tomcat set of master / slave files.

Master.xsl :

<?xml version="1.0" encoding="iso-8859-1" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="xml" encoding="iso-8859-15" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" indent="no"/>
<!-- http://stackoverflow.com/questions/646878/master-stylesheet-sharing-in-xslt -->
 <xsl:template match="ms247">
  <html>
   <head>
    <title>test</title>
   </head>

   <body>
    <div style="border: 1px solid black; width: 200px; float: left; margin: 10px; padding: 5px;">
     <xsl:call-template name="left"/>
    </div>
    <div style="border: 1px solid black; width: 200px; float: left; margin: 10px; padding: 5px;">
     <xsl:call-template name="content"/>
    </div>
    <div style="border: 1px solid black; width: 200px; float: left; margin: 10px; padding: 5px;">
     <xsl:call-template name="right"/>
    </div>
   </body>
  </html>
 </xsl:template>


 <xsl:template name="content">
  <span style="color: red">Content template is empty - overrule in page template.</span>
 </xsl:template>

 <xsl:template name="left">
  <span style="color: red">Left template is empty - overrule in page template.</span>
 </xsl:template>

 <xsl:template name="right">
  <span style="color: red">Right template is empty - overrule in page template.</span>
 </xsl:template>
</xsl:stylesheet>

And slave.xsl:

<?xml version="1.0" encoding="iso-8859-1" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:import href="master.xsl"/>

 <xsl:template name="content">
  ... apply something page-specific
 </xsl:template>

 <xsl:template name="right">
  And we have RIGHT content!
  <!-- Execute matching template which is NOT triggered automatically -->
  <xsl:apply-templates select="params/param"/>
 </xsl:template>

 <!-- And we do not define any left template -->

 <!-- Example -->
 <xsl:template match="ms247/params/param">
  Paramters on page: <xsl:value-of select="@name"/><br/>
 </xsl:template>
</xsl:stylesheet>

Hope this can help others - do not be shy to drop me a note.

Morten Slott Hansen