==================================================
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::

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

 class ImagesOnRightRenderer(XSLTRendererBase):
     def __init__(self):
         XSLTRendererBase.__init__(self, 'images_on_the_right.xslt', __file__)

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. All the real
work is done in the XSLTRendererBase base class. You need to pass the
special __file__ name to instruct the renderer base to look for the
stylesheet in the directory the module is in. This is just a special
name that is available in Python modules.

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: select a renderer
for a specific content object, or select a renderer as a global policy
for all objects of a type (except those which have a specific renderer
selected).

For a specific content object you can switch between renderers om the
properties screen by changing the 'content renderer'. Note that this
option is only available if there are alternative renderers available
for this object at all. The preview, public preview and public view
will start using the selected renderer. These renderers are associated
with the version for versioned content, so changing the renderer only
affects the public presentation of an object after the object has been
published.

All content that has renderers registered for it can also be
explicitly selected not use the new-style renderer but the "old" Silva
system instead. This can be selected by using 'Do not use new-style
renderer'. 

Finally, the default option is set to '(Default)'. This instructs the
system to use the global policy for this content object. The default
global policy for all objects is to use the old-style renderer.

To change the global policy for a particular content type, go to the
'services' tab in your Silva root, and click 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. Again, you can only switch
renderers if any alternatives are registered for that content type.

Try switching the default renderer for Silva Document from 'Do not use
new-style renderer', that causes the old XMLWidget rendering system to
be used, to 'Basic XSLT Renderer', that uses the 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.
