==================================================
How To Make Your Own Renderer For Silva Documents.
==================================================

:Author: Eric Casteleijn


1. The Stylesheet
=================

This is 90% of the work really, and should be handled with
care. Stylesheets that are syntactically incorrect may in some cases
cause strange behaviour in Python/Zope, since the bindings for
libxml2/libxslt are not as robust at error handling as we'd like them
to be. When you want to test your stylesheet for the first time, it
may be a good idea to validate it outside of Zope first, and perhaps
even to run a test transformation on an export of the desired contenttype
with xsltproc.

Building the stylesheet can be very simple or very complicated depending
on what you want it to do. For a relatively simple documented example
see images_to_the_right.xslt::

 <?xml version="1.0" encoding="UTF-8" ?>
 <xsl:stylesheet
  exclude-result-prefixes="doc silva silva-content silva-extra"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:doc="http://infrae.com/ns/silva_document"
  xmlns:silva="http://infrae.com/ns/silva"
  xmlns:silva-content="http://infrae.com/namespaces/metadata/silva"
  version="1.0">
  
 <!--
  An example of an alternative stylesheet for rendering Silva Documents.
  The above namespaces should not be changed. They could be added to for
  those who have extended Silva Document XML and used their own namespace.
  -->
  

 <!--  
  This is hackish, but no other way was found to get the relative
  url of the two stylesheets right. The xsl:import is weird in this, IMHO.
  Python uses a string interpolation to get the right url. This also
  should probably not be changed. This import gets the document with
  the normal renderers for all the xml elements that can occur in a
  Silva Document. These renderers are then overridden in this file.
  No changes should be made to doc_elements.xslt.
  -->

  <xsl:import href="%(url)s/doc_elements.xslt"/>

 <!-- 
  In this example we want to render all content in in two table cells.
  The right one containing all images in order, and the left one containing
  everything else. The match="/" matches the document element of the 
  Silva xml (usually <silva>) and starts to build the html from there.
  -->
  
  <xsl:template match="/">
    <table>
      <tr>
        <td valign="top">
          <xsl:apply-templates/>
        </td>
        <td valign="top">
          <xsl:apply-templates mode="images" />
        </td>
      </tr>
    </table>
  </xsl:template>

 <!--
  Nothing special needs to be done with silva_document or content for our
  purposes here but it could for your own renderer.
  -->
  
  <xsl:template match="silva:silva_document">
    <xsl:apply-templates />
  </xsl:template>

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

 <!--
  The real content of the document begins here.
  -->

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

 <!--
  The metadata is ignored, except for the title. You can access all 
  metadata fields here in the same manner.
  -->

  <xsl:template match="silva:metadata">
    <h2 class="heading">
      <xsl:value-of select="silva:set[@id='silva-content']/silva-content:maintitle" />
    </h2>
  </xsl:template>

 <!--
  In the 'normal' mode i.e. no mode specified, used in the left table
  cell, all images are ignored.

  A special case are images in tables. If we move those out to the
  right, the table layouts are messed up, so we keep the images there.
  -->

  <xsl:template match="doc:image[@link]">
    <xsl:if test="ancestor::doc:table">
      <a href="{@link}">
        <img src="{@path}" />
      </a>
    </xsl:if>
  </xsl:template>

  <xsl:template match="doc:image[not(@link)]">
    <xsl:if test="ancestor::doc:table">
      <img src="{@path}" />
    </xsl:if>
  </xsl:template>
  
  <xsl:template match="doc:table" mode="images">
  </xsl:template>

  <!--
  In the 'images' mode (mode="images"), used in the right table
  cell, all text() nodes are ignored, and the images are shown, as
  links if they have a link attribute, as plain images, if not.
  -->
  
  <xsl:template match="text()" mode="images">
  </xsl:template>
    

  <xsl:template match="doc:image[@link]" mode="images">
    <a href="{@link}">
      <img src="{@path}" />
    </a>
    <br />
  </xsl:template>

  <xsl:template match="doc:image[not(@link)]" mode="images">
    <img src="{@path}" /><br />
  </xsl:template>

 <!--
  These are all the overrides needed, but one could quite easily pick
  certain elements and override them to render them differently. I would
  start with copying the xsl:template for the element from 
  doc_elements.xslt to your own stylesheet, and modify it there until
  it does what you want. 
  -->
  
 </xsl:stylesheet>

(To see how the normal rendering is done with XSLT, have a look at
normal_view.xslt.)

To create your own stylesheet, it may be useful to use
images_to_the_right.xslt or normal_view.xslt as starting points. Do
not modify doc_elements.xslt as that could break your renderer in
future versions of Silva, when new elements may be added.

The best and cleanest way to add new renderers, is to register them from
an extension. That way you can upgrade Silva itself while leaving your
custom renderers intact. See the SilvaRendererDemo Extension 
(http://cvs.infrae.com/SilvaRendererDemo/) for an example extension
that registers two such renderers. The amount of code that has 
to be written is very minimal.



2. The Renderer
===============

Once your stylesheet is done, you need to build a renderer that uses
it. For example take a look at imagesonrightrenderer.py::

 #!/usr/bin/python
 import os

 # Zope
 from Globals import InitializeClass

 # Silva
 from Products.Silva.transform.renderer.xsltrendererbase import XSLTRendererBase

 class ImagesOnRightRenderer(XSLTRendererBase):
     def __init__(self):
         XSLTRendererBase.__init__(self)

         # for creating your own renderer, copying this file, and
         # modifying the self._name and the filename of the stylesheet
         # should be enough

         self._name = 'Images on Right'
         self._stylesheet_path = os.path.join(
             os.path.dirname(os.path.abspath(__file__)),
             "images_to_the_right.xslt")
         self._stylesheet = None

 InitializeClass(ImagesOnRightRenderer)

All this really does is make a 'wrapper' for your stylesheet and gives
it a name to use in Silva, so that it can be registered.


3. Registering the Renderer
===========================

All you have to do now is register the renderer with Silva. This
happens in the __init__.py of your extension. Add an import statement like::

from Products.SilvaRendererDemo.demorenderers import DemoDocumentRenderer

for your own renderer and modify the initialize method to register it::

  def initialize(context):
    
      reg = getRendererRegistry()
    
      reg.registerRenderer(
          'Silva Document',
          'Demo Document Renderer',
          DemoDocumentRenderer())


4. Using the Renderer
=====================

There are two ways to use your renderer in Silva. For a specific
document you can switch between renderers om the properties screen by 
changing the 'content renderer'.  The preview, public preview and public 
view will start using the selected renderer.

Content can also use a default renderer. You can set the default
renderer by going to the 'services' tab in your Silva root, clicking
on 'service renderer registry'. Switching the default renderer for a type
will affect all content of that type in your Silva instance that is set to 
use the "(Default)" renderer.

Try switching the default renderer for Silva Document Version from
'Do Not Use New-Style Renderers', that causes the old XMLWidget rendering
system to be used, to 'Normal View (XSLT)', that uses the fancy newfangled
XSLT rendering. The results should look 100% identical, but you may find
that the latter gives you a significant speed improvement, especially
for larger documents.
