Theming with collective.xdv

« Return to page index

An introduction to rules based theming with collective.xdv

1. Introduction & Overview

This document demonstrates use of the collective.xdv add-on, but parts of the article is also relevant to Deliverance.

Project moved
XDV has been renamed to Diazo, and this integration package has been replaced by plone.app.theming. Visit the Diazo website and the plone.app.theming PyPI page for further information.

This package offers a simple way to develop and deploy Plone themes using the XDV engine. If you are not familiar with XDV or rules-based theming, check out the XDV documentation.

Installation

collective.xdv depends on:

These will all be pulled in automatically if you are using zc.buildout and follow the installation instructions.

To install collective.xdv into your Plone instance, locate the file buildout.cfg in the root of your Plone instance directory on the file system, and open it in a text editor. Locate the section that looks like this:

# extends = http://dist.plone.org/release/3.3/versions.cfg
extends = versions.cfg
versions = versions

It may also have a URL in the "extends" section, similar to the commented-out first line, depending on whether you pull the Plone configuration from the network or locally.

To add collective.xdv to our setup, we need some slightly different versions of a couple of the packages, so we extend the base config with a version list from the good-py service, so change this part of the configuration so it looks like this:

extends =
    versions.cfg
    http://good-py.appspot.com/release/collective.xdv/1.0?plone=3.3.5
versions = versions

Note that the last part of the URL above before the ? is the xdv version number. There may be a newer version by the time you read this, so check out the overview page for the known good set.

Replace ?plone=3.3.5 with the version of Plone you are using. This dependency versions appropriate to your Plone.

What happens here is that the dependency list for collective.xdv specifies some new versions for you via the good-py URL. This way, you don't have to worry about getting the right versions, Buildout will handle it for you.

Next step is to add the actual collective.xdv add-on to the "eggs" section of buildout.cfg. Look for the section that looks like this:

eggs =
    Plone

This section might have additional lines if you have other add-ons already installed. Just add the collective.xdv on a separate line, like this:

eggs =
    Plone
    collective.xdv [Zope2.10]

Note the use of the [Zope2.10] extra, which brings in the ZPublisherEventsBackport package for forward compatibility with Zope 2.12 / Plone 4. If you are using Zope 2.12 or later (e.g. with Plone 4), you should do:

eggs =
    Plone
    collective.xdv

Note that there is no need to add a ZCML slug as collective.xdv uses z3c.autoinclude to configure itself automatically.

Once you have added these lines to your configuration file, it's time to run buildout, so the system can add and set up collective.xdv for you. Go to the command line, and from the root of your Plone instance (same directory as buildout.cfg is located in), run buildout like this:

$ bin/buildout

You will see output similar to this:

Getting distribution for 'collective.xdv==1.0'.
Got collective.xdv 1.0.
Getting distribution for 'plone.app.registry'.
Got plone.app.registry 1.0a1.
Getting distribution for 'plone.synchronize'.
Got plone.synchronize 1.0b1.
...

If everything went according to plan, you now have collective.xdv installed in your Zope instance.

Next, start up Zope, e.g with:

$ bin/instance fg

Then go to the "Add-ons" control panel in Plone as an administrator, and install the "XDV theme support" product. You should then notice a new "XDV Theme" control panel in Plone's site setup. 

Usage

In the "XDV Theme" control panel, you can set the following options:

Enabled yes/no
Whether or not the transform is enabled.
Domains
A list of domains (including ports) that will be matched against the HOST header to determine if the theme should be applied. Note that 127.0.0.1 is never styled, to ensure there's always a way back into Plone to change these very settings. However, 'localhost' should work just fine.
Theme
A file path or URL pointing to the theme file. This is just a static HTML file.
Rules
The filesystem path to the rules XML file.
Alternate themes
A list of definitions of alternate themes and rules files for a different path. Should be of the form 'path|theme|rules' where path may use a regular expression syntax, theme is a file path or URL to the theme template and rule is a file path to the rules file.
XSLT extension file
It is possible to extend XDV with a custom XSLT file. If you have such a file, give its URL here.
Absolute prefix
If given, any relative URL in an <img /><link /><style /> or <script /> in the theme HTML file will be prefixed by this URL snippet when the theme is compiled. This makes it easier to develop theme HTML/CSS on the file system using relative paths that still work on any URL on the server.
Unstyled paths
This is used to give a list of URL patterns (using regular expression syntax) for pages that will not be styled even if XDV is enabled. By default, this includes the 'emptypage' view that is necessary for the Kupu editor to work, and the manage_* pages that make up the ZMI.

Note that when Zope is in debug mode, the theme will be re-compiled on each request. In non-debug mode, it is compiled once on startup, and then only if the control panel values are changed. 

Resources in Python packages

When specifying the rules, theme and/or XSLT extension files, you should normally use a file path. If you are distributing your theme in a Python package that is installed using Distribute/setuptools (e.g. a standard Plone package installed via buildout), you can use the special python URL scheme to reference your files.

For example, if your package is called my.package and it contains a directory mytheme, you could reference the file rules.xml in that file as:

``python://my.package/mytheme/rules.xml``

This will be resolved to an absolute file:// URL by the collective.xdv.

Static Files and CSS

Typically, the theme will reference static resources such as images or stylesheets. It is usually a good idea to keep all of these in a single, top-level directory to minimise the risk of clashes with Plone content paths.

If you are using Zope/Plone standalone, you will need to make your static resources available through Zope, or serve them from a separate (sub-)domain. Here, you have a few options:

  • Create the static resources as File content objects through Plone.
  • Create the resources inside the portal_skins/custom folder in the ZMI.
  • Install the resources through a filesystem product.

The latter is most the appropriate option if you are distributing your theme as a Python package. In this case, you can register a resource directory in ZCML like so:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">

    ...

    <browser:resourceDirectory
        name="my.package"
        directory="mytheme"
        />

    ...

</configure>

The mytheme directory should be in the same directory as the configure.zcml file. You can now put your theme, rules and static resources here.

If you make sure that your theme uses only relative URLs to reference any stylesheets, JavaScript files, or images that it needs (including those referenced from stylesheets), you should now be able to view your static theme by going to a URL like:

http://localhost:8080/Plone/++resource++my.package/theme.html

You can now set the "Absolute prefix" configuration option to be '/++resource++my.package'. XDV will then turn those relative URLs into appropriate absolute URLs with this prefix.

If you have put Apache, nginx or IIS in front of Zope, you may want to serve the static resources from the web server directly instead. 

Using portal_css to manage your CSS

Plone's "resource registries", including the portal_css tool, can be used to manage CSS stylesheets. This offers several advantages over simply linking to your stylesheets in the template, such as:

  • Detailed control over the ordering of stylesheets
  • Merging of stylesheets to reduce the number of downloads required to render your page
  • On-the-fly stylesheet compression (e.g. whitespace removal)
  • The ability to include or exclude a stylesheet based on an expression

It is usually desirable (and sometimes completely necessary) to leave the theme file untouched, but you can still use portal_css to manage your stylesheets. The trick is to drop the theme's styles and then include all styles from Plone. For example, you could add the following rules:

<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

The use of an "or" expression for the content in the <append /> rule means that the precise ordering is maintained.

For an example of how to register stylesheets upon product installation using GenericSetup, see below. In short - use the cssregistry.xml import step in your GenericSetup profile directory.

There is one important caveat, however. Your stylesheet may include relative URL references of the following form:

background-image: url(../images/bg.jpg);

If your stylesheet lives in a resource directory (e.g. it is registered in portal_css with the id++resource++my.package/css/styles.css), this will work fine so long as the registry (and Zope) is in debug mode. The relative URL will be resolved by the browser to ++resource++my.package/images/bg.jpg.

However, you may find that the relative URL breaks when the registry is put into production mode. This is because resource merging also changes the URL of the stylesheet to be something like:

/plone-site/portal_css/Suburst+Theme/merged-cachekey-1234.css

To correct for this, you have a few options:

  1. Replace your static stylesheet with something dynamic so that you can calculate it relative an absolute path on the fly. This obviously will not work if you want to be able to view the theme standalone.
  2. Change your URLs to use an absolute path, e.g. /++resource++my.theme/images/bg.jpg. Again, this will break the original stylesheet. However, you can perhaps create a Plone-only override stylesheet that overrides each CSS property that uses a url().
  3. Avoid using portal_css for your static stylesheets.
  4. Use Plone 4. :-) In Plone 4 (b3 and later), the portal_css tool has an option to parse a stylesheet for relative URLs and apply an absolute prefix based on the stylesheet's debug-mode URL. The option is called applyPrefix in the cssregistry.xml syntax.

Controlling Plone's default CSS

It is sometimes useful to show some of Plone's CSS in the styled site. You can achieve this by using an XDV<append /> rule or similar to copy the CSS from Plone's generated <head /> into the theme. You can use the portal_css tool to turn off the style sheets you do not want.

However, if you also want the site to be usable in non-themed mode (e.g. on a separate URL), you may want to have a larger set of styles enabled when XDV is not used. To make this easier, you can use the following expressions as conditions in the portal_css tool (and portal_javascripts, portal_kss), in portal_actions, in page templates, and other places that use TAL expression syntax:

request/HTTP_X_XDV | nothing

This expression will return True if XDV is currently enabled, in which case an HTTP header "X-XDV" will be set. By default, this will check both the 'enabled' flag in the XDV control panel, and the current domain. If you later deploy the theme to a fronting web server such as nginx, you can set the same request header there to get the same effect, even if collective.xdv is uninstalled.

Use:

not: request/HTTP_X_XDV | nothing

to 'hide' a style sheet from the themed site.

A worked example

There are many ways to set up an XDV theme. For example, you could upload the theme and rules as content in Plone use absolute paths to configure them. You could also serve them from a separate static web server, or even load them from the filesystem.

To create a deployable theme, however, it is often best to create a simple Python package. This also provides a natural home for theme-related customisations such as template overrides.

Although a detailed tutorial is beyond the scope of this help file, a brief, worked example is shown below.

  1. Create a package and install it in your buildout:

    $ cd src
    $ paster create -t plone my.theme

See the buildout manual for details

If you have a recent ZopeSkel installed, this should work. Pick easy mode. Answer "yes" when asked if you want to register a profile.

Then edit buildout.cfg to add your new package (my.theme above) to the develop and eggs lists.

  1. Edit setup.py inside the newly created package

The install_requires list should be:

install_requires=[
      'setuptools',
      'collective.xdv',
  ],

Re-run buildout:

$ bin/buildout
  1. Edit configure.zcml inside the newly created package.

Add a resource directory inside the <configure /> tag. Note that you may need to add the browser namespace, as shown.

<configure

xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:i18n="http://namespaces.zope.org/i18n" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="my.theme">

<genericsetup:registerProfile
name="default" title="my.theme" directory="profiles/default" description="Installs the my.theme package" provides="Products.GenericSetup.interfaces.EXTENSION" />
<browser:resourceDirectory
name="my.theme" directory="static" />

</configure>

Here, we have used the package name, my.theme, for the resource directory name. Adjust as appropriate.

  1. Add a static directory next to configure.zcml.
  2. Put your theme and rules files into this directory.

For example, you may have a theme.html that references images in a sub-directory images/ and stylesheets in a sub-directory css/. Place this file and the two directories inside the newly created static directory.

Make sure the theme uses relative URLs (e.g. <img src="images/foo.jpg" />) to reference its resources. This means you can open theme up from the filesystem and view it in its splendour.

Also place a rules.xml file there. See the XDV documentation for details about its syntax. You can start with some very simple rules if you just want to test:

<?xml version="1.0" encoding="UTF-8"?>
<rules
    xmlns="http://namespaces.plone.org/xdv"
    xmlns:css="http://namespaces.plone.org/xdv+css">

    <!-- Head: title -->
    <replace theme="/html/head/title" content="/html/head/title" />

    <!-- Base tag -->
    <replace theme="/html/head/base" content="/html/head/base" />

    <!-- Drop styles in the head - these are added back by including them from Plone -->
    <drop theme="/html/head/link" />
    <drop theme="/html/head/style" />

    <!-- Pull in Plone CSS -->
    <append theme="/html/head" content="/html/head/link | /html/head/style " />

</rules>

These rules will pull in the <title /> tag (i.e. the browser window's title), the <base /> tag (necessary for certain Plone URLs to work correctly), and Plone's stylesheets.

See below for some more useful rules.

  1. Create the installation profile

The generated code above for the <genericsetup:registerProfile /> tag contains a reference to a directoryprofiles/default. You may need to create this next to configure.zcml if it doesn't exist already, i.e. create a new directory profiles and inside it another directory default.

In this directory, add a file called metadata.xml containing:

<metadata>
    <version>1</version>
    <dependencies>
        <dependency>profile-collective.xdv:default</dependency>
    </dependencies>
</metadata>

This will install collective.xdv into Plone when my.theme is installed via the add-on control panel later.

Also create a file called registry.xml, with the following contents:

<registry>

    <!-- collective.xdv settings -->

    <record interface="collective.xdv.interfaces.ITransformSettings" field="domains">
        <value>
            <element>domain.my:8080</element>
        </value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="rules">
        <value>python://my.theme/static/rules.xml</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="theme">
        <value>python://my.theme/static/theme.html</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="absolute_prefix">
        <value>/++resource++my.theme</value>
    </record>

</registry>

Replace my.theme with your own package name, and rules.xml and theme.html as appropriate.

This file configures the settings behind the XDV control panel.

Hint: If you have played with the control panel and want to export your settings, you can create a snapshot in the portal_setup tool in the ZMI. Examine the registry.xml file this creates, and pick out the records that relate to collective.xdv. You should strip out the <field /> tags in the export, so that you are left with <record />and <value /> tags as shown above.

Also, add a cssregistry.xml in the profiles/default directory to configure the portal_css tool:

<?xml version="1.0"?>
<object name="portal_css">

 <!-- Set conditions on stylesheets we don't want to pull in -->
 <stylesheet
     expression="not:request/HTTP_X_XDV | nothing"
     id="public.css"
     />

 <!-- Add new stylesheets -->
 <!-- Note: applyPrefix is not available in Plone < 4.0b3 -->

 <stylesheet title="" authenticated="False" cacheable="True"
    compression="safe" conditionalcomment="" cookable="True" enabled="on"
    expression="request/HTTP_X_XDV | nothing"
    id="++resource++my.theme/css/styles.css" media="" rel="stylesheet"
    rendering="link"
    applyPrefix="True"
    />

</object>

This shows how to set a condition on an existing stylesheet, as well as registering a brand new one. We've setapplyPrefix to True here, as explained above. This will only work in Plone 4.b3 and later. For earlier versions, simply take this out.

  1. Test

Start up Zope and go to your Plone site. Your new package should show as installable in the add-on product control panel. When installed, it should install collective.xdv as a dependency and pre-configure it to use your theme and rule set. By default, the theme is not enabled, so you will need to go to the control panel to switch it on.

You can now compare your untouched theme, the unstyled Plone site, and the themed site by using the following URLs:

  • http://localhost:8080 (or whatever you have configured as the styled domain) for a styled Plone. If you used the sample rule above, this will look almost exactly like your theme, but with the <title /> tag (normally shown in the title bar of your web browser) taken from Plone.
  • http://127.0.0.1:8080 (presuming this is the port where Plone is running) for an unstyled Plone.
  • http://localhost:8080/++resource++my.theme/theme.html for the pristine theme. This is served as a static resource, almost as if it is being opened on the filesystem.

Common rules

To copy the page title:

<!-- Head: title -->
<replace theme="/html/head/title" content="/html/head/title" />

To copy the <base /> tag (necessary for Plone's links to work):

<!-- Base tag -->
<replace theme="/html/head/base" content="/html/head/base" />

To drop all styles and JavaScript resources from the theme and copy them from Plone's portal_css tool instead:

<!-- Drop styles in the head - these are added back by including them from Plone -->
<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

To copy Plone's JavaScript resources:

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/script" />

To copy the class of the <body /> tag (necessary for certain Plone JavaScript functions and styles to work properly):

<!-- Body -->
<prepend theme="/html/body" content="/html/body/attribute::class" />

Other tips

  • Firebug is an excellent tool for inspecting the theme and content when building rules. It even has an XPath extractor.
  • Read up on XPath. It's not as complex as it looks and very powerful.
  • Run Zope in debug mode whilst developing so that you don't need to restart to see changes to theme, rules or, resources.

Changelog

1.0rc9 - 2010-08-05

  • Use an IBeforeTraverseEvent on the Plone site root instead of an IPubAfterTraversal event to hook in the X-XDV request header. This makes the header work on 404 error pages. [optilude]
  • Add collective.directoryresourcepatch to the Zope2.10 extras. This allows for subdirectories to be traversed by the ResourceRegistries while running Plone 3/Zope 2.10. [dunlapm]
  • Require lxml>=2.2.4. The Zope2 KGS lists lxml=2.2, a version which errors on invalid html. [elro]
  • Fix extra.xsl support. [elro]

1.0rc8 - 2010-05-24

1.0rc7 - 2010-05-23

  • UPGRADE NOTE: Reinstall product in the Add-ons control panel.
  • Switch on XInclude processing always. [elro]
  • Fix Windows install. For running under Plone 4 on Windows, you must specify:
    [versions]
    lxml = 2.2.4
    until a newer lxml Windows binary egg is released. [elro]
  • Instead of the external resolver, let lxml read the network. You must now explicitly enable Read network in the control panel. [elro]

1.0rc6 - 2010-05-21

  • Fix transform caching to account for different virtual hosts of the same site and make cache invalidation work across ZEO clients. [elro]

1.0rc5 - 2010-04-21

  • Fix in-Plone content inclusion via the href mechanism, including the use of relative paths in hrefs. [optilude]
  • Ensured that the absolute prefix would work even in a virtual hosting scenario where the aboslute path of the site root is '/'. [optilude]
  • Added an event handler which will set an HTTP request header 'X-XDV' if XDV is enabled for the incoming domain. This can be used as a check in e.g. portal_css, for example with a TALES expression like 'request/HTTP_X_XDV | nothing'. The @@xdv-check/enabled method now just checks for the existence of this variable too. The idea is that it is easier to replicate this in a pure-XSLT deployment scenario with collective.xdv disabled, for example by setting the same request header in nginx or Apache. [optilude]
  • Made all zope paths resolve relative to the Plone site. [marshalium]
  • Add support for resolving files with http/ftp absolute urls and zope paths. [marshalium]
  • Make absolute_prefix prepend the Plone site path if necessary. This means that an absolute prefix starting with / is always relative to the Plone site root. [optilude]
  • Add support for the python:// pseudo-scheme for the theme, rules and extrauri files. See README.txt for details. [optilude]
  • Improve the wording in the control panel [optilude]
  • Fix a bug whereby the cached transforms (in non-debug-mode) would leak across Plone sites in the same instance. [optilude]
  • Remove the boilerplate parameter. Use extraurl instead. [optilude]
  • Let collective.xdv depend on the new XDV egg, instead of dv.xdvserver. [optilude]
  • Only invoke the transformation if collective.xdv is in fact installed. Note: you may need to re-install the product after upgrading. [optilude]
  • Use plone.transformchain to sequence transformation activities. Among other things, this helps us avoid re-parsing/serialising lxml trees when other things in the chain prefer to work with such representations of the response. It also helps control the sequence of post-publication events. [optilude]
  • Zope 2.12 / Plone 4 compatability. [lrowe]

1.0rc4 - 2009-10-27

  • Style error responses as well as successful responses. [lrowe]
  • Use ZPublisher events instead of plone.postpublicationhook for compatibility with Zope 2.12 / Plone 4. For Zope2.10 / Plone 3.x, you must now specify "collective.xdv [Zope2.10]" in your buildout to bring in the package ZPublisherEventsBackport. [lrowe]
  • Added support for extraurl parameter [mhora]
  • Added alternate themes and modified transform so it can decide by a path regular expression which theme and rules files it will use for transformation [mhora]
  • Add /manage in unstyled paths default list. [encolpe]

1.0a2 - 2009-07-12

  • Catch up with changes in plone.registry's API. [optilude]

1.0a1 - 2009-04-17

  • Initial release

1.1. Introduction

The current state of Plone theming.

Why a new approach?

The current approach to theming (also known as "skinning") Plone sites (Plone 1.0 to 3.x) has been steadily evolving over a number of years, and is powerful, but somewhat complex. 

As with most software, the reason for this complexity is a side effect of the evolution of the product over time — adding more functionality and more flexibility to meet the needs of power users, while still retaining the same initial approach to the problem space.

For Plone, the current theming approach has served us well for the last 8 years, but we realized it was time to re-evaluate how theming was done, since it's such an integral part of setting up a Plone site.

The main goals for a new approach were:

  • No requirement for the people doing the theming to know anything about Plone or Python.
  • Use standard tools and libraries whenever they are available.
  • Reduce the number of concepts you have to learn in order to get started.
  • When requiring you to learn something new, let it be a standard solution that is useful even outside of the Plone world.
  • Try to stay as close to the HTML and CSS mindset as possible, as the people doing theming are usually the same people that do the HTML code for the design.
  • Make it possible to apply an existing HTML/CSS design to a Plone site while keeping the original markup instead of having to re-do the design in a way that makes Plone happy.
  • Let the theme work standalone, without introducing any additional markup. It should look like a standard HTML page with CSS, JS and images.

About the future of theming in Plone

For the upcoming versions of Plone, we are 99% sure that we'll use a variant of what is described here. It will probably change slightly in how it integrates into the product, and what knobs are available to deploy a theme in a standard way — but the fundamental approach will be the same.

What we are offering you with the collective.xdv package is a way to make use of the likely future standard of theming, today. We'll keep this document updated as Plone progresses with newer versions, and if you're reading this documentation in a book or another printed version, always check http://plone.org/theming to view the latest version of this document. There will be a revision history attached, which will detail the latest changes to this document.

Is xdv ready for serious deployments?

Currently, the plone.org web site itself is using xdv for its theme — so it's battle-tested and ready for serious, high-traffic sites.

1.2. Background & History

Before starting, let us explain briefly the history of xdv, and why it exists.

When people talk about this new approach to theming, they will often refer to the general approach as "Deliverance-based". The original Deliverance project was started by Paul Everitt a long time ago, and was further enhanced by Ian Bicking, who is its current maintainer.

Along the way, Deliverance got more powerful and expanded beyond the initial goals, and started handling cases that were not included in the original scope.

Long story short, a new implementation of the same basic approach was started, called "xdv". This is a stripped-down, pure XSLT implementation of the Deliverance concept, and can be compiled down and used directly inside a web server like Apache, IIS or nginx (pronounced "Engine X") as a standard XSLT transform, without any extra software running.

This does not mean that xdv is somehow better than the original Deliverance implementation, or that it makes it obsolete — they are different tools with slightly different goals, sharing the same basic approach. We'll get to an overview of which approach is appropriate for what cases in a moment.

The final piece of this puzzle is the add-on called collective.xdv, which takes xdv and packages it up to make it very convenient for use with Plone.

As a side note, the "collective" namespace is a common pattern in Plone add-ons, and denotes software that is managed collectively by the Plone community — and not necessarily by the Plone Foundation.

Why do you need to know all this? It's useful to keep in mind that you can carry across what you learn in using collective.xdv to the Deliverance project, should you need to do that at a later point. The basic syntax and approach is largely the same, although the details of the implementation can differ slightly.

1.3. Choosing the appropriate theming approach

You have several alternatives when it comes to theming Plone at this point. Let’s look at what makes them different.

At the moment, there are several ways to theme a Plone site. What are your options, and which is appropriate for a given case?

Comparison of the available approaches

Product package

Pros:

  • The ultimate in flexibility and control
  • More flexibility when it comes to theming select parts of a site or sub-sites

Cons:

  • More complex, requires a passing familiarity wtih ZCML and Python
  • Less upgrade-resistant, since templates change
  • Shipping your own templates will get you out of sync with the main product

collective.xdv

Pros:

  • Can be compiled down to XSLT transforms that run as part of the web server process, so you don't need a separate proxy or WSGI setup. This also means a slight performance advantage over Deliverance.
  • Easy to bootstrap with Plone.

Cons:

  • Uses XPath selectors instead of the more familiar CSS selectors (not as big a deal as you may fear, since we can use a plug-in called Firebug to make it create the XPath expressions for us, as we'll see later).

Deliverance

Pros compared to xdv:

  • Standalone, can be used without Plone to theme other sites and systems, has a good WSGI story.
  • Supports a more familiar CSS syntax in addition to XPath.

Cons compared to xdv:

  • Less integrated with Plone, requires you to understand more of the ecosystem and setup.
  • Uses its own non-standard (but CSS-like) syntax, can't be deployed inside a web server process.

Which one should I use?

A general rule-of-thumb to help you decide what approach to choose:

Use the add-on/product approach when…
You need extreme granularity of control, and are willing to learn some Python and ZCML to get what you want.
Use xdv when…
You'd like to keep the theme separate from the code, and want a reusable theme approach that can potentially be deployed as part of the web server process. You don't want to worry about most of the "plumbing", and are comfortable using best practices from the Plone community.
Use Deliverance when…
You have advanced theming needs, including the ability to apply the same theme to multiple frameworks/web apps in addition to Plone, and are willing to set up a proxy or WSGI pipeline to make it happen.

Of course, reality is always a bit more complicated — you can apply an xdv-based theme to non-Plone setups too, if you know what you're doing.

The important part to know is that you can move between xdv and Deliverance pretty easily, so starting out with collective.xdv is likely to be a great start, even if you end up using Deliverance later.

1.4. Tools & Prerequisites

Software you will need to follow along with this documentation.

Before we get started, make sure you have the following software available:

  • The latest version of Plone (Plone 3.3 or newer for the best experience)
  • Latest version of Firefox (available for all platforms)
  • The Firebug add-on for Firefox
  • Your text editor of choice
  • Access to the Terminal or command line on the system you are working on
  • A network connection (to make sure Buildout can download its packages) — you can work around this by downloading the packages separately if you don't have a network connection, but it's outside of the scope of this tutorial. Consult the Buildout documentation if you need to do offline packages.

And again: if you're reading this in printed form, make sure you check the online version at http://plone.org/theming for the latest version recommendations, as versions and Buildout dependency URLs may change slightly over time.

Got everything set up? Great, let's get down to business.

2. Installing collective.xdv

How to install collective.xdv and add it to your Plone instance

Project moved
XDV has been renamed to Diazo, and this integration package has been replaced by plone.app.theming. Visit the Diazo website and the plone.app.theming PyPI page for further information.

This package offers a simple way to develop and deploy Plone themes using the XDV engine. If you are not familiar with XDV or rules-based theming, check out the XDV documentation.

Installation

collective.xdv depends on:

These will all be pulled in automatically if you are using zc.buildout and follow the installation instructions.

To install collective.xdv into your Plone instance, locate the file buildout.cfg in the root of your Plone instance directory on the file system, and open it in a text editor. Locate the section that looks like this:

# extends = http://dist.plone.org/release/3.3/versions.cfg
extends = versions.cfg
versions = versions

It may also have a URL in the "extends" section, similar to the commented-out first line, depending on whether you pull the Plone configuration from the network or locally.

To add collective.xdv to our setup, we need some slightly different versions of a couple of the packages, so we extend the base config with a version list from the good-py service, so change this part of the configuration so it looks like this:

extends =
    versions.cfg
    http://good-py.appspot.com/release/collective.xdv/1.0?plone=3.3.5
versions = versions

Note that the last part of the URL above before the ? is the xdv version number. There may be a newer version by the time you read this, so check out the overview page for the known good set.

Replace ?plone=3.3.5 with the version of Plone you are using. This dependency versions appropriate to your Plone.

What happens here is that the dependency list for collective.xdv specifies some new versions for you via the good-py URL. This way, you don't have to worry about getting the right versions, Buildout will handle it for you.

Next step is to add the actual collective.xdv add-on to the "eggs" section of buildout.cfg. Look for the section that looks like this:

eggs =
    Plone

This section might have additional lines if you have other add-ons already installed. Just add the collective.xdv on a separate line, like this:

eggs =
    Plone
    collective.xdv [Zope2.10]

Note the use of the [Zope2.10] extra, which brings in the ZPublisherEventsBackport package for forward compatibility with Zope 2.12 / Plone 4. If you are using Zope 2.12 or later (e.g. with Plone 4), you should do:

eggs =
    Plone
    collective.xdv

Note that there is no need to add a ZCML slug as collective.xdv uses z3c.autoinclude to configure itself automatically.

Once you have added these lines to your configuration file, it's time to run buildout, so the system can add and set up collective.xdv for you. Go to the command line, and from the root of your Plone instance (same directory as buildout.cfg is located in), run buildout like this:

$ bin/buildout

You will see output similar to this:

Getting distribution for 'collective.xdv==1.0'.
Got collective.xdv 1.0.
Getting distribution for 'plone.app.registry'.
Got plone.app.registry 1.0a1.
Getting distribution for 'plone.synchronize'.
Got plone.synchronize 1.0b1.
...

If everything went according to plan, you now have collective.xdv installed in your Zope instance.

Next, start up Zope, e.g with:

$ bin/instance fg

Then go to the "Add-ons" control panel in Plone as an administrator, and install the "XDV theme support" product. You should then notice a new "XDV Theme" control panel in Plone's site setup. 

Usage

In the "XDV Theme" control panel, you can set the following options:

Enabled yes/no
Whether or not the transform is enabled.
Domains
A list of domains (including ports) that will be matched against the HOST header to determine if the theme should be applied. Note that 127.0.0.1 is never styled, to ensure there's always a way back into Plone to change these very settings. However, 'localhost' should work just fine.
Theme
A file path or URL pointing to the theme file. This is just a static HTML file.
Rules
The filesystem path to the rules XML file.
Alternate themes
A list of definitions of alternate themes and rules files for a different path. Should be of the form 'path|theme|rules' where path may use a regular expression syntax, theme is a file path or URL to the theme template and rule is a file path to the rules file.
XSLT extension file
It is possible to extend XDV with a custom XSLT file. If you have such a file, give its URL here.
Absolute prefix
If given, any relative URL in an <img /><link /><style /> or <script /> in the theme HTML file will be prefixed by this URL snippet when the theme is compiled. This makes it easier to develop theme HTML/CSS on the file system using relative paths that still work on any URL on the server.
Unstyled paths
This is used to give a list of URL patterns (using regular expression syntax) for pages that will not be styled even if XDV is enabled. By default, this includes the 'emptypage' view that is necessary for the Kupu editor to work, and the manage_* pages that make up the ZMI.

Note that when Zope is in debug mode, the theme will be re-compiled on each request. In non-debug mode, it is compiled once on startup, and then only if the control panel values are changed. 

Resources in Python packages

When specifying the rules, theme and/or XSLT extension files, you should normally use a file path. If you are distributing your theme in a Python package that is installed using Distribute/setuptools (e.g. a standard Plone package installed via buildout), you can use the special python URL scheme to reference your files.

For example, if your package is called my.package and it contains a directory mytheme, you could reference the file rules.xml in that file as:

``python://my.package/mytheme/rules.xml``

This will be resolved to an absolute file:// URL by the collective.xdv.

Static Files and CSS

Typically, the theme will reference static resources such as images or stylesheets. It is usually a good idea to keep all of these in a single, top-level directory to minimise the risk of clashes with Plone content paths.

If you are using Zope/Plone standalone, you will need to make your static resources available through Zope, or serve them from a separate (sub-)domain. Here, you have a few options:

  • Create the static resources as File content objects through Plone.
  • Create the resources inside the portal_skins/custom folder in the ZMI.
  • Install the resources through a filesystem product.

The latter is most the appropriate option if you are distributing your theme as a Python package. In this case, you can register a resource directory in ZCML like so:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">

    ...

    <browser:resourceDirectory
        name="my.package"
        directory="mytheme"
        />

    ...

</configure>

The mytheme directory should be in the same directory as the configure.zcml file. You can now put your theme, rules and static resources here.

If you make sure that your theme uses only relative URLs to reference any stylesheets, JavaScript files, or images that it needs (including those referenced from stylesheets), you should now be able to view your static theme by going to a URL like:

http://localhost:8080/Plone/++resource++my.package/theme.html

You can now set the "Absolute prefix" configuration option to be '/++resource++my.package'. XDV will then turn those relative URLs into appropriate absolute URLs with this prefix.

If you have put Apache, nginx or IIS in front of Zope, you may want to serve the static resources from the web server directly instead. 

Using portal_css to manage your CSS

Plone's "resource registries", including the portal_css tool, can be used to manage CSS stylesheets. This offers several advantages over simply linking to your stylesheets in the template, such as:

  • Detailed control over the ordering of stylesheets
  • Merging of stylesheets to reduce the number of downloads required to render your page
  • On-the-fly stylesheet compression (e.g. whitespace removal)
  • The ability to include or exclude a stylesheet based on an expression

It is usually desirable (and sometimes completely necessary) to leave the theme file untouched, but you can still use portal_css to manage your stylesheets. The trick is to drop the theme's styles and then include all styles from Plone. For example, you could add the following rules:

<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

The use of an "or" expression for the content in the <append /> rule means that the precise ordering is maintained.

For an example of how to register stylesheets upon product installation using GenericSetup, see below. In short - use the cssregistry.xml import step in your GenericSetup profile directory.

There is one important caveat, however. Your stylesheet may include relative URL references of the following form:

background-image: url(../images/bg.jpg);

If your stylesheet lives in a resource directory (e.g. it is registered in portal_css with the id++resource++my.package/css/styles.css), this will work fine so long as the registry (and Zope) is in debug mode. The relative URL will be resolved by the browser to ++resource++my.package/images/bg.jpg.

However, you may find that the relative URL breaks when the registry is put into production mode. This is because resource merging also changes the URL of the stylesheet to be something like:

/plone-site/portal_css/Suburst+Theme/merged-cachekey-1234.css

To correct for this, you have a few options:

  1. Replace your static stylesheet with something dynamic so that you can calculate it relative an absolute path on the fly. This obviously will not work if you want to be able to view the theme standalone.
  2. Change your URLs to use an absolute path, e.g. /++resource++my.theme/images/bg.jpg. Again, this will break the original stylesheet. However, you can perhaps create a Plone-only override stylesheet that overrides each CSS property that uses a url().
  3. Avoid using portal_css for your static stylesheets.
  4. Use Plone 4. :-) In Plone 4 (b3 and later), the portal_css tool has an option to parse a stylesheet for relative URLs and apply an absolute prefix based on the stylesheet's debug-mode URL. The option is called applyPrefix in the cssregistry.xml syntax.

Controlling Plone's default CSS

It is sometimes useful to show some of Plone's CSS in the styled site. You can achieve this by using an XDV<append /> rule or similar to copy the CSS from Plone's generated <head /> into the theme. You can use the portal_css tool to turn off the style sheets you do not want.

However, if you also want the site to be usable in non-themed mode (e.g. on a separate URL), you may want to have a larger set of styles enabled when XDV is not used. To make this easier, you can use the following expressions as conditions in the portal_css tool (and portal_javascripts, portal_kss), in portal_actions, in page templates, and other places that use TAL expression syntax:

request/HTTP_X_XDV | nothing

This expression will return True if XDV is currently enabled, in which case an HTTP header "X-XDV" will be set. By default, this will check both the 'enabled' flag in the XDV control panel, and the current domain. If you later deploy the theme to a fronting web server such as nginx, you can set the same request header there to get the same effect, even if collective.xdv is uninstalled.

Use:

not: request/HTTP_X_XDV | nothing

to 'hide' a style sheet from the themed site.

A worked example

There are many ways to set up an XDV theme. For example, you could upload the theme and rules as content in Plone use absolute paths to configure them. You could also serve them from a separate static web server, or even load them from the filesystem.

To create a deployable theme, however, it is often best to create a simple Python package. This also provides a natural home for theme-related customisations such as template overrides.

Although a detailed tutorial is beyond the scope of this help file, a brief, worked example is shown below.

  1. Create a package and install it in your buildout:

    $ cd src
    $ paster create -t plone my.theme

See the buildout manual for details

If you have a recent ZopeSkel installed, this should work. Pick easy mode. Answer "yes" when asked if you want to register a profile.

Then edit buildout.cfg to add your new package (my.theme above) to the develop and eggs lists.

  1. Edit setup.py inside the newly created package

The install_requires list should be:

install_requires=[
      'setuptools',
      'collective.xdv',
  ],

Re-run buildout:

$ bin/buildout
  1. Edit configure.zcml inside the newly created package.

Add a resource directory inside the <configure /> tag. Note that you may need to add the browser namespace, as shown.

<configure

xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:i18n="http://namespaces.zope.org/i18n" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="my.theme">

<genericsetup:registerProfile
name="default" title="my.theme" directory="profiles/default" description="Installs the my.theme package" provides="Products.GenericSetup.interfaces.EXTENSION" />
<browser:resourceDirectory
name="my.theme" directory="static" />

</configure>

Here, we have used the package name, my.theme, for the resource directory name. Adjust as appropriate.

  1. Add a static directory next to configure.zcml.
  2. Put your theme and rules files into this directory.

For example, you may have a theme.html that references images in a sub-directory images/ and stylesheets in a sub-directory css/. Place this file and the two directories inside the newly created static directory.

Make sure the theme uses relative URLs (e.g. <img src="images/foo.jpg" />) to reference its resources. This means you can open theme up from the filesystem and view it in its splendour.

Also place a rules.xml file there. See the XDV documentation for details about its syntax. You can start with some very simple rules if you just want to test:

<?xml version="1.0" encoding="UTF-8"?>
<rules
    xmlns="http://namespaces.plone.org/xdv"
    xmlns:css="http://namespaces.plone.org/xdv+css">

    <!-- Head: title -->
    <replace theme="/html/head/title" content="/html/head/title" />

    <!-- Base tag -->
    <replace theme="/html/head/base" content="/html/head/base" />

    <!-- Drop styles in the head - these are added back by including them from Plone -->
    <drop theme="/html/head/link" />
    <drop theme="/html/head/style" />

    <!-- Pull in Plone CSS -->
    <append theme="/html/head" content="/html/head/link | /html/head/style " />

</rules>

These rules will pull in the <title /> tag (i.e. the browser window's title), the <base /> tag (necessary for certain Plone URLs to work correctly), and Plone's stylesheets.

See below for some more useful rules.

  1. Create the installation profile

The generated code above for the <genericsetup:registerProfile /> tag contains a reference to a directoryprofiles/default. You may need to create this next to configure.zcml if it doesn't exist already, i.e. create a new directory profiles and inside it another directory default.

In this directory, add a file called metadata.xml containing:

<metadata>
    <version>1</version>
    <dependencies>
        <dependency>profile-collective.xdv:default</dependency>
    </dependencies>
</metadata>

This will install collective.xdv into Plone when my.theme is installed via the add-on control panel later.

Also create a file called registry.xml, with the following contents:

<registry>

    <!-- collective.xdv settings -->

    <record interface="collective.xdv.interfaces.ITransformSettings" field="domains">
        <value>
            <element>domain.my:8080</element>
        </value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="rules">
        <value>python://my.theme/static/rules.xml</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="theme">
        <value>python://my.theme/static/theme.html</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="absolute_prefix">
        <value>/++resource++my.theme</value>
    </record>

</registry>

Replace my.theme with your own package name, and rules.xml and theme.html as appropriate.

This file configures the settings behind the XDV control panel.

Hint: If you have played with the control panel and want to export your settings, you can create a snapshot in the portal_setup tool in the ZMI. Examine the registry.xml file this creates, and pick out the records that relate to collective.xdv. You should strip out the <field /> tags in the export, so that you are left with <record />and <value /> tags as shown above.

Also, add a cssregistry.xml in the profiles/default directory to configure the portal_css tool:

<?xml version="1.0"?>
<object name="portal_css">

 <!-- Set conditions on stylesheets we don't want to pull in -->
 <stylesheet
     expression="not:request/HTTP_X_XDV | nothing"
     id="public.css"
     />

 <!-- Add new stylesheets -->
 <!-- Note: applyPrefix is not available in Plone < 4.0b3 -->

 <stylesheet title="" authenticated="False" cacheable="True"
    compression="safe" conditionalcomment="" cookable="True" enabled="on"
    expression="request/HTTP_X_XDV | nothing"
    id="++resource++my.theme/css/styles.css" media="" rel="stylesheet"
    rendering="link"
    applyPrefix="True"
    />

</object>

This shows how to set a condition on an existing stylesheet, as well as registering a brand new one. We've setapplyPrefix to True here, as explained above. This will only work in Plone 4.b3 and later. For earlier versions, simply take this out.

  1. Test

Start up Zope and go to your Plone site. Your new package should show as installable in the add-on product control panel. When installed, it should install collective.xdv as a dependency and pre-configure it to use your theme and rule set. By default, the theme is not enabled, so you will need to go to the control panel to switch it on.

You can now compare your untouched theme, the unstyled Plone site, and the themed site by using the following URLs:

  • http://localhost:8080 (or whatever you have configured as the styled domain) for a styled Plone. If you used the sample rule above, this will look almost exactly like your theme, but with the <title /> tag (normally shown in the title bar of your web browser) taken from Plone.
  • http://127.0.0.1:8080 (presuming this is the port where Plone is running) for an unstyled Plone.
  • http://localhost:8080/++resource++my.theme/theme.html for the pristine theme. This is served as a static resource, almost as if it is being opened on the filesystem.

Common rules

To copy the page title:

<!-- Head: title -->
<replace theme="/html/head/title" content="/html/head/title" />

To copy the <base /> tag (necessary for Plone's links to work):

<!-- Base tag -->
<replace theme="/html/head/base" content="/html/head/base" />

To drop all styles and JavaScript resources from the theme and copy them from Plone's portal_css tool instead:

<!-- Drop styles in the head - these are added back by including them from Plone -->
<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

To copy Plone's JavaScript resources:

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/script" />

To copy the class of the <body /> tag (necessary for certain Plone JavaScript functions and styles to work properly):

<!-- Body -->
<prepend theme="/html/body" content="/html/body/attribute::class" />

Other tips

  • Firebug is an excellent tool for inspecting the theme and content when building rules. It even has an XPath extractor.
  • Read up on XPath. It's not as complex as it looks and very powerful.
  • Run Zope in debug mode whilst developing so that you don't need to restart to see changes to theme, rules or, resources.

Changelog

1.0rc9 - 2010-08-05

  • Use an IBeforeTraverseEvent on the Plone site root instead of an IPubAfterTraversal event to hook in the X-XDV request header. This makes the header work on 404 error pages. [optilude]
  • Add collective.directoryresourcepatch to the Zope2.10 extras. This allows for subdirectories to be traversed by the ResourceRegistries while running Plone 3/Zope 2.10. [dunlapm]
  • Require lxml>=2.2.4. The Zope2 KGS lists lxml=2.2, a version which errors on invalid html. [elro]
  • Fix extra.xsl support. [elro]

1.0rc8 - 2010-05-24

1.0rc7 - 2010-05-23

  • UPGRADE NOTE: Reinstall product in the Add-ons control panel.
  • Switch on XInclude processing always. [elro]
  • Fix Windows install. For running under Plone 4 on Windows, you must specify:
    [versions]
    lxml = 2.2.4
    until a newer lxml Windows binary egg is released. [elro]
  • Instead of the external resolver, let lxml read the network. You must now explicitly enable Read network in the control panel. [elro]

1.0rc6 - 2010-05-21

  • Fix transform caching to account for different virtual hosts of the same site and make cache invalidation work across ZEO clients. [elro]

1.0rc5 - 2010-04-21

  • Fix in-Plone content inclusion via the href mechanism, including the use of relative paths in hrefs. [optilude]
  • Ensured that the absolute prefix would work even in a virtual hosting scenario where the aboslute path of the site root is '/'. [optilude]
  • Added an event handler which will set an HTTP request header 'X-XDV' if XDV is enabled for the incoming domain. This can be used as a check in e.g. portal_css, for example with a TALES expression like 'request/HTTP_X_XDV | nothing'. The @@xdv-check/enabled method now just checks for the existence of this variable too. The idea is that it is easier to replicate this in a pure-XSLT deployment scenario with collective.xdv disabled, for example by setting the same request header in nginx or Apache. [optilude]
  • Made all zope paths resolve relative to the Plone site. [marshalium]
  • Add support for resolving files with http/ftp absolute urls and zope paths. [marshalium]
  • Make absolute_prefix prepend the Plone site path if necessary. This means that an absolute prefix starting with / is always relative to the Plone site root. [optilude]
  • Add support for the python:// pseudo-scheme for the theme, rules and extrauri files. See README.txt for details. [optilude]
  • Improve the wording in the control panel [optilude]
  • Fix a bug whereby the cached transforms (in non-debug-mode) would leak across Plone sites in the same instance. [optilude]
  • Remove the boilerplate parameter. Use extraurl instead. [optilude]
  • Let collective.xdv depend on the new XDV egg, instead of dv.xdvserver. [optilude]
  • Only invoke the transformation if collective.xdv is in fact installed. Note: you may need to re-install the product after upgrading. [optilude]
  • Use plone.transformchain to sequence transformation activities. Among other things, this helps us avoid re-parsing/serialising lxml trees when other things in the chain prefer to work with such representations of the response. It also helps control the sequence of post-publication events. [optilude]
  • Zope 2.12 / Plone 4 compatability. [lrowe]

1.0rc4 - 2009-10-27

  • Style error responses as well as successful responses. [lrowe]
  • Use ZPublisher events instead of plone.postpublicationhook for compatibility with Zope 2.12 / Plone 4. For Zope2.10 / Plone 3.x, you must now specify "collective.xdv [Zope2.10]" in your buildout to bring in the package ZPublisherEventsBackport. [lrowe]
  • Added support for extraurl parameter [mhora]
  • Added alternate themes and modified transform so it can decide by a path regular expression which theme and rules files it will use for transformation [mhora]
  • Add /manage in unstyled paths default list. [encolpe]

1.0a2 - 2009-07-12

  • Catch up with changes in plone.registry's API. [optilude]

1.0a1 - 2009-04-17

  • Initial release

2.1. Adding collective.xdv to your Plone instance

First step is to add collective.xdv to our Plone setup.

We will assume that you have a passing familiarity with Plone's configuration/build system, Buildout  — if not, don't worry, it should be pretty simple to follow along even if you don't. I also assume that you already have Plone and Firefox + Firebug installed.

A note about the setup

Setting up collective.xdv takes some grunt work because it has some slightly unusual dependencies. When this solution ships with Plone itself, this will of course already be handled for you. So bear with us through the install instructions — and rest safe in the knowledge that this will be much easier in the future. It's the price you pay for being on the forefront of technology — but we can promise you that it will be worth it!

The installation process involves the usual steps of

  • adding collective.xdv to buildout
  • running buildout
  • installing collective.xdv in your Plone site

Adding collective.xdv to buildout

As the installation process is not entirely consistent across platforms yet, you'll need to follow the platform specific instructions on the following pages. It is well-worth checking the installation instructions on the collective.xdv page in the Python Package Index for the latest information on recent releases. The following pages go over the same instructions in a little more detail:

Running Buildout

Once you have edited your configuration file according to the platform instructions above, it's time to run Buildout, so the system can add and set up collective.xdv for you. Open a terminal window (or on Windows, start > run > cmd) and on the command line, cd to the the root of your Plone instance (same directory as buildout.cfg is located in) and run Buildout like this:

$ bin/buildout

or, if you're using Windows XP or older:

> bin\buildout

You will see output similar to this:

Getting distribution for 'collective.xdv==1.0'.
Got collective.xdv 1.0.
Getting distribution for 'dv.xdvserver'.
Got dv.xdvserver 1.0b4.
Getting distribution for 'plone.postpublicationhook'.
Got plone.postpublicationhook 1.0rc1.
Getting distribution for 'plone.app.registry'.
Got plone.app.registry 1.0a1.
Getting distribution for 'plone.synchronize'.
Got plone.synchronize 1.0b1.

If everything went according to plan, we now have collective.xdv installed. Time to start Plone and activate it for our site.

Issues and Problems

If you get errors when doing this instead of the above output, please contact the Plone support forums to get more help with your specific setup.

Especially, if you're on Mac OS X, there might be some  issues. We have been working on fixing the Mac situation for a while, and it should be fixed by the time you read this — but if it isn't, let us know!

 

2.2. Windows

collective.xdv configuration requirements for Windows

Installation on Windows is straightforward - the following has been tested with Windows XP (SP3) and Plone 3.3.1, installed using the Windows installer.

Locate the file buildout.cfg in the root of your Plone instance directory on the file system (the default location on Windows is c:\Program Files\Plone), and open it in a text editor. Locate the section that looks like this:

# extends = http://dist.plone.org/release/3.3/versions.cfg
extends = versions.cfg
versions = versions

It may also have a URL in the "extends" section, similar to the commented-out first line, depending on whether you pull the Plone configuration from the network or locally.

To add collective.xdv to our setup, we need some slightly different versions of a couple of the packages, so we extend the base config with a version list from the good-py service, so change this part of the configuration so it looks like this:

extends = versions.cfg
          http://good-py.appspot.com/release/collective.xdv/1.0
versions = versions

What happens here is that the dependency list for collective.xdv specifies some new versions for you via the good-py URL. This way, you don't have to worry about getting the right versions, Buildout will handle it for you.

Next step is to add the actual collective.xdv add-on to the "eggs" section of buildout.cfg. Look for the section that looks like this:

eggs =
    Plone

This section might have additional lines if you have other add-ons already installed. Just add the collective.xdv on a separate line, like this:

eggs =
    Plone
    collective.xdv [Zope2.10]

The [Zope2.10] is an "extra" which, in this case, tells buildout to get some additional functionality to work with Plone 3. If you are using Plone 4 you can omit this.

The additional egg is ZPublisherEventsBackport - if buildout ignores the "extra" command, then simply add this egg to your eggs list.

Now save your buildout.cfg file and go back to the first page of this section to run buildout.

2.3. Mac OS X

The collective.xdv buildout on Mac OS X

Prerequisites

At the moment, on Mac OS X, you will need to compile lxml and for this you'll need gcc. You'll find this in the XCode tools that come with your Mac CDs (but are not pre-installed). If you have upgraded your OS recently, make sure that you have also upgraded XCode.

Note: For the sake of your sanity, only try this on an up-to-date version of Plone (3.3.*) .

The buildout.cfg

Locate the file buildout.cfg in the root of your Plone instance directory on the file system, and open it in a text editor. 

The Dependencies

Locate the section that looks like this:

# extends = http://dist.plone.org/release/3.3/versions.cfg
extends = versions.cfg
versions = versions

It may also have a URL in the "extends" section, similar to the commented-out first line, depending on whether you pull the Plone configuration from the network or locally.

To add collective.xdv to our setup, we need some slightly different versions of a couple of the packages, so we extend the base config with a version list from the good-py service, so change this part of the configuration so it looks like this:

extends = versions.cfg
          http://good-py.appspot.com/release/collective.xdv/1.0
versions = versions

What happens here is that the dependency list for collective.xdv specifies some new versions for you via the good-py URL. This way, you don't have to worry about getting the right versions, Buildout will handle it for you.

The eggs

Next step is to add the actual collective.xdv add-on to the "eggs" section of buildout.cfg. Look for the section that looks like this:

eggs =
    Plone

This section might have additional lines if you have other add-ons already installed. Just add the collective.xdv on a separate line, like this:

eggs =
    Plone
    collective.xdv [Zope2.10]

The [Zope2.10] is an "extra" which, in this case, tells buildout to get some additional functionality to work with Plone 3. If you are using Plone 4 you can omit this.

The additional egg is ZPublisherEventsBackport - if buildout ignores the "extra" command, then simply add this egg to your eggs list.

lxml

Mac OSX has issues with the lxml binary egg (a requirement of collective.xdv) which can make installation of collective.xdv quite problematic. The recommended approach is to add an additional part to your buildout.cfg to specifically deal with lxml.

First of all you add the name of your new part to the parts section of buildout.cfg (by convention we call it "lxml"). If you're using the buildout.cfg that came with the installer versions of Plone, then scroll down until you find the warning about dragons, and you should find the list of parts fairly soon after that. Insert lxml fairly high up the list.

[buildout]

....

# Beyond here there be dragons!
…
parts =
…
lxml
…

Then locate a place where you can create your new part (prefaced with its name in square brackets). A good place to put it is just above your zope2 section:

[lxml]
recipe = z3c.recipe.staticlxml
egg = lxml
force = false

# This section installs the components of Zope 2.
....

 Finally you need to tell Buildout which version of lxml to use and also to get the very latest version of zc.buildout to make sure it runs the staticlxml recipe properly. You may already have a [versions] part. If not, add it to your buildout.cfg - just above your [lxml] part would be a good place.

[versions]
lxml = 2.2.2
zc.buildout =
…

 Now save your buildout.cfg and you're ready to run buildout.

2.4. Linux

Adding collective.xdv to our buildout on Linux

Prerequisites

On Linux, you may find that you need to download and install the libxml2-dev and libxslt-dev packages for your operating system. Use your normal package manager to see if you have these and install them if not.

Note for Ubuntu 8.04 you will need libxslt1-dev.

The buildout.cfg edits

Locate the file buildout.cfg in the root of your Plone instance directory on the file system, and open it in a text editor. 

The Dependencies

Locate the section that looks like this:

# extends = http://dist.plone.org/release/3.3/versions.cfg
extends = versions.cfg
versions = versions

It may also have a URL in the "extends" section, similar to the commented-out first line, depending on whether you pull the Plone configuration from the network or locally.

To add collective.xdv to our setup, we need some slightly different versions of a couple of the packages, so we extend the base config with a version list from the good-py service, so change this part of the configuration so it looks like this:

extends = versions.cfg
          http://good-py.appspot.com/release/collective.xdv/1.0
versions = versions

What happens here is that the dependency list for collective.xdv specifies some new versions for you via the good-py URL. This way, you don't have to worry about getting the right versions, Buildout will handle it for you.

The Egg(s)

Next step is to add the actual collective.xdv add-on to the "eggs" section of buildout.cfg. Look for the section that looks like this:

eggs =
    Plone

This section might have additional lines if you have other add-ons already installed. Just add the collective.xdv on a separate line, like this:

eggs =
    Plone
    collective.xdv [Zope2.10]

The [Zope2.10] is an "extra" which, in this case, tells buildout to get some additional functionality to ensure that everything works with Plone 3. If you are using Plone 4 you can omit this.

The additional egg is ZPublisherEventsBackport - if buildout ignores the "extra" command, then simply add this egg to your eggs list.

You should now be ready to run buildout, so hop back to the overview page of this section.

 

3. Quick Start

Activating collective.xdv and your first theme.

Project moved
XDV has been renamed to Diazo, and this integration package has been replaced by plone.app.theming. Visit the Diazo website and the plone.app.theming PyPI page for further information.

This package offers a simple way to develop and deploy Plone themes using the XDV engine. If you are not familiar with XDV or rules-based theming, check out the XDV documentation.

Installation

collective.xdv depends on:

These will all be pulled in automatically if you are using zc.buildout and follow the installation instructions.

To install collective.xdv into your Plone instance, locate the file buildout.cfg in the root of your Plone instance directory on the file system, and open it in a text editor. Locate the section that looks like this:

# extends = http://dist.plone.org/release/3.3/versions.cfg
extends = versions.cfg
versions = versions

It may also have a URL in the "extends" section, similar to the commented-out first line, depending on whether you pull the Plone configuration from the network or locally.

To add collective.xdv to our setup, we need some slightly different versions of a couple of the packages, so we extend the base config with a version list from the good-py service, so change this part of the configuration so it looks like this:

extends =
    versions.cfg
    http://good-py.appspot.com/release/collective.xdv/1.0?plone=3.3.5
versions = versions

Note that the last part of the URL above before the ? is the xdv version number. There may be a newer version by the time you read this, so check out the overview page for the known good set.

Replace ?plone=3.3.5 with the version of Plone you are using. This dependency versions appropriate to your Plone.

What happens here is that the dependency list for collective.xdv specifies some new versions for you via the good-py URL. This way, you don't have to worry about getting the right versions, Buildout will handle it for you.

Next step is to add the actual collective.xdv add-on to the "eggs" section of buildout.cfg. Look for the section that looks like this:

eggs =
    Plone

This section might have additional lines if you have other add-ons already installed. Just add the collective.xdv on a separate line, like this:

eggs =
    Plone
    collective.xdv [Zope2.10]

Note the use of the [Zope2.10] extra, which brings in the ZPublisherEventsBackport package for forward compatibility with Zope 2.12 / Plone 4. If you are using Zope 2.12 or later (e.g. with Plone 4), you should do:

eggs =
    Plone
    collective.xdv

Note that there is no need to add a ZCML slug as collective.xdv uses z3c.autoinclude to configure itself automatically.

Once you have added these lines to your configuration file, it's time to run buildout, so the system can add and set up collective.xdv for you. Go to the command line, and from the root of your Plone instance (same directory as buildout.cfg is located in), run buildout like this:

$ bin/buildout

You will see output similar to this:

Getting distribution for 'collective.xdv==1.0'.
Got collective.xdv 1.0.
Getting distribution for 'plone.app.registry'.
Got plone.app.registry 1.0a1.
Getting distribution for 'plone.synchronize'.
Got plone.synchronize 1.0b1.
...

If everything went according to plan, you now have collective.xdv installed in your Zope instance.

Next, start up Zope, e.g with:

$ bin/instance fg

Then go to the "Add-ons" control panel in Plone as an administrator, and install the "XDV theme support" product. You should then notice a new "XDV Theme" control panel in Plone's site setup. 

Usage

In the "XDV Theme" control panel, you can set the following options:

Enabled yes/no
Whether or not the transform is enabled.
Domains
A list of domains (including ports) that will be matched against the HOST header to determine if the theme should be applied. Note that 127.0.0.1 is never styled, to ensure there's always a way back into Plone to change these very settings. However, 'localhost' should work just fine.
Theme
A file path or URL pointing to the theme file. This is just a static HTML file.
Rules
The filesystem path to the rules XML file.
Alternate themes
A list of definitions of alternate themes and rules files for a different path. Should be of the form 'path|theme|rules' where path may use a regular expression syntax, theme is a file path or URL to the theme template and rule is a file path to the rules file.
XSLT extension file
It is possible to extend XDV with a custom XSLT file. If you have such a file, give its URL here.
Absolute prefix
If given, any relative URL in an <img /><link /><style /> or <script /> in the theme HTML file will be prefixed by this URL snippet when the theme is compiled. This makes it easier to develop theme HTML/CSS on the file system using relative paths that still work on any URL on the server.
Unstyled paths
This is used to give a list of URL patterns (using regular expression syntax) for pages that will not be styled even if XDV is enabled. By default, this includes the 'emptypage' view that is necessary for the Kupu editor to work, and the manage_* pages that make up the ZMI.

Note that when Zope is in debug mode, the theme will be re-compiled on each request. In non-debug mode, it is compiled once on startup, and then only if the control panel values are changed. 

Resources in Python packages

When specifying the rules, theme and/or XSLT extension files, you should normally use a file path. If you are distributing your theme in a Python package that is installed using Distribute/setuptools (e.g. a standard Plone package installed via buildout), you can use the special python URL scheme to reference your files.

For example, if your package is called my.package and it contains a directory mytheme, you could reference the file rules.xml in that file as:

``python://my.package/mytheme/rules.xml``

This will be resolved to an absolute file:// URL by the collective.xdv.

Static Files and CSS

Typically, the theme will reference static resources such as images or stylesheets. It is usually a good idea to keep all of these in a single, top-level directory to minimise the risk of clashes with Plone content paths.

If you are using Zope/Plone standalone, you will need to make your static resources available through Zope, or serve them from a separate (sub-)domain. Here, you have a few options:

  • Create the static resources as File content objects through Plone.
  • Create the resources inside the portal_skins/custom folder in the ZMI.
  • Install the resources through a filesystem product.

The latter is most the appropriate option if you are distributing your theme as a Python package. In this case, you can register a resource directory in ZCML like so:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">

    ...

    <browser:resourceDirectory
        name="my.package"
        directory="mytheme"
        />

    ...

</configure>

The mytheme directory should be in the same directory as the configure.zcml file. You can now put your theme, rules and static resources here.

If you make sure that your theme uses only relative URLs to reference any stylesheets, JavaScript files, or images that it needs (including those referenced from stylesheets), you should now be able to view your static theme by going to a URL like:

http://localhost:8080/Plone/++resource++my.package/theme.html

You can now set the "Absolute prefix" configuration option to be '/++resource++my.package'. XDV will then turn those relative URLs into appropriate absolute URLs with this prefix.

If you have put Apache, nginx or IIS in front of Zope, you may want to serve the static resources from the web server directly instead. 

Using portal_css to manage your CSS

Plone's "resource registries", including the portal_css tool, can be used to manage CSS stylesheets. This offers several advantages over simply linking to your stylesheets in the template, such as:

  • Detailed control over the ordering of stylesheets
  • Merging of stylesheets to reduce the number of downloads required to render your page
  • On-the-fly stylesheet compression (e.g. whitespace removal)
  • The ability to include or exclude a stylesheet based on an expression

It is usually desirable (and sometimes completely necessary) to leave the theme file untouched, but you can still use portal_css to manage your stylesheets. The trick is to drop the theme's styles and then include all styles from Plone. For example, you could add the following rules:

<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

The use of an "or" expression for the content in the <append /> rule means that the precise ordering is maintained.

For an example of how to register stylesheets upon product installation using GenericSetup, see below. In short - use the cssregistry.xml import step in your GenericSetup profile directory.

There is one important caveat, however. Your stylesheet may include relative URL references of the following form:

background-image: url(../images/bg.jpg);

If your stylesheet lives in a resource directory (e.g. it is registered in portal_css with the id++resource++my.package/css/styles.css), this will work fine so long as the registry (and Zope) is in debug mode. The relative URL will be resolved by the browser to ++resource++my.package/images/bg.jpg.

However, you may find that the relative URL breaks when the registry is put into production mode. This is because resource merging also changes the URL of the stylesheet to be something like:

/plone-site/portal_css/Suburst+Theme/merged-cachekey-1234.css

To correct for this, you have a few options:

  1. Replace your static stylesheet with something dynamic so that you can calculate it relative an absolute path on the fly. This obviously will not work if you want to be able to view the theme standalone.
  2. Change your URLs to use an absolute path, e.g. /++resource++my.theme/images/bg.jpg. Again, this will break the original stylesheet. However, you can perhaps create a Plone-only override stylesheet that overrides each CSS property that uses a url().
  3. Avoid using portal_css for your static stylesheets.
  4. Use Plone 4. :-) In Plone 4 (b3 and later), the portal_css tool has an option to parse a stylesheet for relative URLs and apply an absolute prefix based on the stylesheet's debug-mode URL. The option is called applyPrefix in the cssregistry.xml syntax.

Controlling Plone's default CSS

It is sometimes useful to show some of Plone's CSS in the styled site. You can achieve this by using an XDV<append /> rule or similar to copy the CSS from Plone's generated <head /> into the theme. You can use the portal_css tool to turn off the style sheets you do not want.

However, if you also want the site to be usable in non-themed mode (e.g. on a separate URL), you may want to have a larger set of styles enabled when XDV is not used. To make this easier, you can use the following expressions as conditions in the portal_css tool (and portal_javascripts, portal_kss), in portal_actions, in page templates, and other places that use TAL expression syntax:

request/HTTP_X_XDV | nothing

This expression will return True if XDV is currently enabled, in which case an HTTP header "X-XDV" will be set. By default, this will check both the 'enabled' flag in the XDV control panel, and the current domain. If you later deploy the theme to a fronting web server such as nginx, you can set the same request header there to get the same effect, even if collective.xdv is uninstalled.

Use:

not: request/HTTP_X_XDV | nothing

to 'hide' a style sheet from the themed site.

A worked example

There are many ways to set up an XDV theme. For example, you could upload the theme and rules as content in Plone use absolute paths to configure them. You could also serve them from a separate static web server, or even load them from the filesystem.

To create a deployable theme, however, it is often best to create a simple Python package. This also provides a natural home for theme-related customisations such as template overrides.

Although a detailed tutorial is beyond the scope of this help file, a brief, worked example is shown below.

  1. Create a package and install it in your buildout:

    $ cd src
    $ paster create -t plone my.theme

See the buildout manual for details

If you have a recent ZopeSkel installed, this should work. Pick easy mode. Answer "yes" when asked if you want to register a profile.

Then edit buildout.cfg to add your new package (my.theme above) to the develop and eggs lists.

  1. Edit setup.py inside the newly created package

The install_requires list should be:

install_requires=[
      'setuptools',
      'collective.xdv',
  ],

Re-run buildout:

$ bin/buildout
  1. Edit configure.zcml inside the newly created package.

Add a resource directory inside the <configure /> tag. Note that you may need to add the browser namespace, as shown.

<configure

xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:i18n="http://namespaces.zope.org/i18n" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="my.theme">

<genericsetup:registerProfile
name="default" title="my.theme" directory="profiles/default" description="Installs the my.theme package" provides="Products.GenericSetup.interfaces.EXTENSION" />
<browser:resourceDirectory
name="my.theme" directory="static" />

</configure>

Here, we have used the package name, my.theme, for the resource directory name. Adjust as appropriate.

  1. Add a static directory next to configure.zcml.
  2. Put your theme and rules files into this directory.

For example, you may have a theme.html that references images in a sub-directory images/ and stylesheets in a sub-directory css/. Place this file and the two directories inside the newly created static directory.

Make sure the theme uses relative URLs (e.g. <img src="images/foo.jpg" />) to reference its resources. This means you can open theme up from the filesystem and view it in its splendour.

Also place a rules.xml file there. See the XDV documentation for details about its syntax. You can start with some very simple rules if you just want to test:

<?xml version="1.0" encoding="UTF-8"?>
<rules
    xmlns="http://namespaces.plone.org/xdv"
    xmlns:css="http://namespaces.plone.org/xdv+css">

    <!-- Head: title -->
    <replace theme="/html/head/title" content="/html/head/title" />

    <!-- Base tag -->
    <replace theme="/html/head/base" content="/html/head/base" />

    <!-- Drop styles in the head - these are added back by including them from Plone -->
    <drop theme="/html/head/link" />
    <drop theme="/html/head/style" />

    <!-- Pull in Plone CSS -->
    <append theme="/html/head" content="/html/head/link | /html/head/style " />

</rules>

These rules will pull in the <title /> tag (i.e. the browser window's title), the <base /> tag (necessary for certain Plone URLs to work correctly), and Plone's stylesheets.

See below for some more useful rules.

  1. Create the installation profile

The generated code above for the <genericsetup:registerProfile /> tag contains a reference to a directoryprofiles/default. You may need to create this next to configure.zcml if it doesn't exist already, i.e. create a new directory profiles and inside it another directory default.

In this directory, add a file called metadata.xml containing:

<metadata>
    <version>1</version>
    <dependencies>
        <dependency>profile-collective.xdv:default</dependency>
    </dependencies>
</metadata>

This will install collective.xdv into Plone when my.theme is installed via the add-on control panel later.

Also create a file called registry.xml, with the following contents:

<registry>

    <!-- collective.xdv settings -->

    <record interface="collective.xdv.interfaces.ITransformSettings" field="domains">
        <value>
            <element>domain.my:8080</element>
        </value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="rules">
        <value>python://my.theme/static/rules.xml</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="theme">
        <value>python://my.theme/static/theme.html</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="absolute_prefix">
        <value>/++resource++my.theme</value>
    </record>

</registry>

Replace my.theme with your own package name, and rules.xml and theme.html as appropriate.

This file configures the settings behind the XDV control panel.

Hint: If you have played with the control panel and want to export your settings, you can create a snapshot in the portal_setup tool in the ZMI. Examine the registry.xml file this creates, and pick out the records that relate to collective.xdv. You should strip out the <field /> tags in the export, so that you are left with <record />and <value /> tags as shown above.

Also, add a cssregistry.xml in the profiles/default directory to configure the portal_css tool:

<?xml version="1.0"?>
<object name="portal_css">

 <!-- Set conditions on stylesheets we don't want to pull in -->
 <stylesheet
     expression="not:request/HTTP_X_XDV | nothing"
     id="public.css"
     />

 <!-- Add new stylesheets -->
 <!-- Note: applyPrefix is not available in Plone < 4.0b3 -->

 <stylesheet title="" authenticated="False" cacheable="True"
    compression="safe" conditionalcomment="" cookable="True" enabled="on"
    expression="request/HTTP_X_XDV | nothing"
    id="++resource++my.theme/css/styles.css" media="" rel="stylesheet"
    rendering="link"
    applyPrefix="True"
    />

</object>

This shows how to set a condition on an existing stylesheet, as well as registering a brand new one. We've setapplyPrefix to True here, as explained above. This will only work in Plone 4.b3 and later. For earlier versions, simply take this out.

  1. Test

Start up Zope and go to your Plone site. Your new package should show as installable in the add-on product control panel. When installed, it should install collective.xdv as a dependency and pre-configure it to use your theme and rule set. By default, the theme is not enabled, so you will need to go to the control panel to switch it on.

You can now compare your untouched theme, the unstyled Plone site, and the themed site by using the following URLs:

  • http://localhost:8080 (or whatever you have configured as the styled domain) for a styled Plone. If you used the sample rule above, this will look almost exactly like your theme, but with the <title /> tag (normally shown in the title bar of your web browser) taken from Plone.
  • http://127.0.0.1:8080 (presuming this is the port where Plone is running) for an unstyled Plone.
  • http://localhost:8080/++resource++my.theme/theme.html for the pristine theme. This is served as a static resource, almost as if it is being opened on the filesystem.

Common rules

To copy the page title:

<!-- Head: title -->
<replace theme="/html/head/title" content="/html/head/title" />

To copy the <base /> tag (necessary for Plone's links to work):

<!-- Base tag -->
<replace theme="/html/head/base" content="/html/head/base" />

To drop all styles and JavaScript resources from the theme and copy them from Plone's portal_css tool instead:

<!-- Drop styles in the head - these are added back by including them from Plone -->
<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

To copy Plone's JavaScript resources:

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/script" />

To copy the class of the <body /> tag (necessary for certain Plone JavaScript functions and styles to work properly):

<!-- Body -->
<prepend theme="/html/body" content="/html/body/attribute::class" />

Other tips

  • Firebug is an excellent tool for inspecting the theme and content when building rules. It even has an XPath extractor.
  • Read up on XPath. It's not as complex as it looks and very powerful.
  • Run Zope in debug mode whilst developing so that you don't need to restart to see changes to theme, rules or, resources.

Changelog

1.0rc9 - 2010-08-05

  • Use an IBeforeTraverseEvent on the Plone site root instead of an IPubAfterTraversal event to hook in the X-XDV request header. This makes the header work on 404 error pages. [optilude]
  • Add collective.directoryresourcepatch to the Zope2.10 extras. This allows for subdirectories to be traversed by the ResourceRegistries while running Plone 3/Zope 2.10. [dunlapm]
  • Require lxml>=2.2.4. The Zope2 KGS lists lxml=2.2, a version which errors on invalid html. [elro]
  • Fix extra.xsl support. [elro]

1.0rc8 - 2010-05-24

1.0rc7 - 2010-05-23

  • UPGRADE NOTE: Reinstall product in the Add-ons control panel.
  • Switch on XInclude processing always. [elro]
  • Fix Windows install. For running under Plone 4 on Windows, you must specify:
    [versions]
    lxml = 2.2.4
    until a newer lxml Windows binary egg is released. [elro]
  • Instead of the external resolver, let lxml read the network. You must now explicitly enable Read network in the control panel. [elro]

1.0rc6 - 2010-05-21

  • Fix transform caching to account for different virtual hosts of the same site and make cache invalidation work across ZEO clients. [elro]

1.0rc5 - 2010-04-21

  • Fix in-Plone content inclusion via the href mechanism, including the use of relative paths in hrefs. [optilude]
  • Ensured that the absolute prefix would work even in a virtual hosting scenario where the aboslute path of the site root is '/'. [optilude]
  • Added an event handler which will set an HTTP request header 'X-XDV' if XDV is enabled for the incoming domain. This can be used as a check in e.g. portal_css, for example with a TALES expression like 'request/HTTP_X_XDV | nothing'. The @@xdv-check/enabled method now just checks for the existence of this variable too. The idea is that it is easier to replicate this in a pure-XSLT deployment scenario with collective.xdv disabled, for example by setting the same request header in nginx or Apache. [optilude]
  • Made all zope paths resolve relative to the Plone site. [marshalium]
  • Add support for resolving files with http/ftp absolute urls and zope paths. [marshalium]
  • Make absolute_prefix prepend the Plone site path if necessary. This means that an absolute prefix starting with / is always relative to the Plone site root. [optilude]
  • Add support for the python:// pseudo-scheme for the theme, rules and extrauri files. See README.txt for details. [optilude]
  • Improve the wording in the control panel [optilude]
  • Fix a bug whereby the cached transforms (in non-debug-mode) would leak across Plone sites in the same instance. [optilude]
  • Remove the boilerplate parameter. Use extraurl instead. [optilude]
  • Let collective.xdv depend on the new XDV egg, instead of dv.xdvserver. [optilude]
  • Only invoke the transformation if collective.xdv is in fact installed. Note: you may need to re-install the product after upgrading. [optilude]
  • Use plone.transformchain to sequence transformation activities. Among other things, this helps us avoid re-parsing/serialising lxml trees when other things in the chain prefer to work with such representations of the response. It also helps control the sequence of post-publication events. [optilude]
  • Zope 2.12 / Plone 4 compatability. [lrowe]

1.0rc4 - 2009-10-27

  • Style error responses as well as successful responses. [lrowe]
  • Use ZPublisher events instead of plone.postpublicationhook for compatibility with Zope 2.12 / Plone 4. For Zope2.10 / Plone 3.x, you must now specify "collective.xdv [Zope2.10]" in your buildout to bring in the package ZPublisherEventsBackport. [lrowe]
  • Added support for extraurl parameter [mhora]
  • Added alternate themes and modified transform so it can decide by a path regular expression which theme and rules files it will use for transformation [mhora]
  • Add /manage in unstyled paths default list. [encolpe]

1.0a2 - 2009-07-12

  • Catch up with changes in plone.registry's API. [optilude]

1.0a1 - 2009-04-17

  • Initial release

3.1. Activating xdv

We have added xdv to our setup, it’s time to activate it in our Plone site.

Make sure Plone was (re)started, and log in as an administrator. Go to Site Setup, and make sure it contains a new control panel called "Theme Transform". If you can't see it, double check that the setup instructions were followed, and that you got no errors in the previous setup procedure.

The Theme Transform control panel has a number of settings which we'll make use of in a minute — but first we will create a simple HTML file and some transform rules to get us started.

Adding the HTML and rule files

Let's create a dedicated directory in your instance where you can keep your theme files:

  • Navigate to your buildout's instance directory
  • Create a directory called "themes"

Note: Never put anything in the parts, eggs or develop-eggs directories, as Buildout considers these private, and may potentially wipe them when updating your setup.

Inside the themes/ directory, create the following two files using your text editor:

theme.html:

<html>
<head>
  <title>xdv example</title>
</head>
<body>

  <h1>The simplest possible example of xdv transforms</h1>
  <p id="my-content-area">This body text will be replaced.</p>

</body>
</html>

rules.xml:

<rules xmlns="http://openplans.org/deliverance">

    <!-- Copy over the contents of the page body -->
    <replace content='//*[@id="content"]'    
               theme='//*[@id="my-content-area"]' />

</rules>

Believe it or not, that's a complete — although very basic — Plone theme using xdv!

We now have an HTML file which forms the base of our design, as well as a rules file that does the transform. All we have left to do is tell Plone about the the paths for the theme and rule files, and enable the transform.

Enabling the theme transform

Let's go back to the "Theme Transform" Plone control panel:

The settings you need to care about right now are:

Enabled
Turns the xdv theme transformation on or off. Select "yes".
Domains
Which domains get the theme transforms applied. One thing to note here is that 127.0.0.1 will never have a theme applied as a safety net, so you can always get back to your site even if an error while developing your theme transform makes it unusable. There is a default value of "localhost:8080" here, adjust if your setup is different — but usually the default value is fine.

The next two values are where it gets interesting:

Theme template
A file path pointing to the theme file. This is just a static HTML file. Add themes/theme.html here. It's relative to your instance directory, no need for the full path.
Rules file
The filesystem path to the rules XML file. Add theme/rules.xml here.

Ignore the rest of the form values for now, and press Save.

Testing that everything works

Now, let's go to the front page of your Plone site and see what happened:

  • Go to http://localhost:8080/Plone
  • Admire your beautiful, unstyled HTML page with the content from Plone inserted into it, it should look something like this:

Not particularly visually exciting, is it? But what you have just set up is a very, very powerful way to theme Plone sites, that makes it possible to use any pre-existing design with a Plone back-end. The reason this is exciting is that you're using your own HTML and CSS, not modifying Plone's HTML and CSS.

Let's take a step back and explain how it all fits together.

3.2. How it works

A high-level overview of what is going on in xdv (and Deliverance).

The way xdv works is simple, but since it might take a little adjustment of the mental model you're used to if you have done theming in Plone (or any other system), it's worth an explanation:

The main difference is that you're not touching the templates and HTML from Plone itself at all. Instead, you create the layout and design you want in standalone HTML and CSS files — and then map parts of the content that comes out of Plone into your existing HTML.

This means that you can create as complex (or simple!) designs as you want, and let Plone supply the content.

This also means that you can write your own from-scratch HTML and CSS, but also map various Plone elements to wherever you want in your own design. Plone knows nothing about what happens "on the way out", it just renders a page as it usually does.

A more visual way to look at it, here you can see how the Plone output is mapped into a totally different template and design:

mapping-theme-plone.png

This makes for a much more robust approach to theming, because as long as Plone keeps its HTML classes and IDs the same from one version to the next, your theme will automatically work even in a new version of Plone. And if it has changed, it's a relatively simple operation to update the theme — just locate the new name, and replace it in the rule file.

Let's take a closer look at the rule file.

3.3. The rule file

Central to the way content makes its way from your Plone site into your theme is the rule file.

Let's look at your simple rule file again:

rules.xml:

<rules xmlns="http://openplans.org/deliverance">

    <!-- Copy over the contents of the page body -->
    <replace content='//*[@id="content"]'    
               theme='//*[@id="my-content-area"]' />

</rules>

Ignoring the preamble <rules> and the comment, there's one single instruction here:

<replace content='//*[@id="content"]'    
           theme='//*[@id="my-content-area"]' />

So what does it do?

  • It looks at the Plone side of things ("content"), and locates the part of the HTML that has id="content".
  • It then replaces the part of your theme's HTML that has id="my-content-area" with the content it got from Plone.

The syntax (inside the content and theme attributes) can be a bit intimidating — luckily we have great tools to make it very easy to get this right. The syntax is called XPath, and is a standard for addressing nodes in the DOM, and it's also directly supported in Firebug. A full treatment of how Firebug works is out of scope for this tutorial, but if you have done any web design in the past, you have probably used it. If not, head over to the Firebug web site to learn more — they have documentation and screencasts showing you how to use it.

I will show you a screenshot of the part we're interested in, however — when you're looking at the Plone source code using Firebug, locate the content area as shown below, and right-click the node:

firebug-xpath.png 

As you can see, there's a way to copy any HTML node, and get its XPath expression. When you paste what's now on your clipboard, you will see:

//*[@id="content"]

…and that's the XPath expression that uniquely identifies that part of the page! You probably recognize this from our original rules.xml file. That's the node you're looking for in the Plone source, and you use the same approach to find the node you want to replace in your theme.html.

You don't have to teach yourself XPath — just arm yourself with Firebug, and make use of its built-in support for these expressions.

As a sidenote, Deliverance has implemented support for CSS-like syntax — that is, #content instead of //*[@id="content"] — which is certainly easier to remember. The XPath-based syntax works equally well in both xdv and Deliverance, though.

Let's go over the available rules next.

4. Rules

Project moved
XDV has been renamed to Diazo, and this integration package has been replaced by plone.app.theming. Visit the Diazo website and the plone.app.theming PyPI page for further information.

This package offers a simple way to develop and deploy Plone themes using the XDV engine. If you are not familiar with XDV or rules-based theming, check out the XDV documentation.

Installation

collective.xdv depends on:

These will all be pulled in automatically if you are using zc.buildout and follow the installation instructions.

To install collective.xdv into your Plone instance, locate the file buildout.cfg in the root of your Plone instance directory on the file system, and open it in a text editor. Locate the section that looks like this:

# extends = http://dist.plone.org/release/3.3/versions.cfg
extends = versions.cfg
versions = versions

It may also have a URL in the "extends" section, similar to the commented-out first line, depending on whether you pull the Plone configuration from the network or locally.

To add collective.xdv to our setup, we need some slightly different versions of a couple of the packages, so we extend the base config with a version list from the good-py service, so change this part of the configuration so it looks like this:

extends =
    versions.cfg
    http://good-py.appspot.com/release/collective.xdv/1.0?plone=3.3.5
versions = versions

Note that the last part of the URL above before the ? is the xdv version number. There may be a newer version by the time you read this, so check out the overview page for the known good set.

Replace ?plone=3.3.5 with the version of Plone you are using. This dependency versions appropriate to your Plone.

What happens here is that the dependency list for collective.xdv specifies some new versions for you via the good-py URL. This way, you don't have to worry about getting the right versions, Buildout will handle it for you.

Next step is to add the actual collective.xdv add-on to the "eggs" section of buildout.cfg. Look for the section that looks like this:

eggs =
    Plone

This section might have additional lines if you have other add-ons already installed. Just add the collective.xdv on a separate line, like this:

eggs =
    Plone
    collective.xdv [Zope2.10]

Note the use of the [Zope2.10] extra, which brings in the ZPublisherEventsBackport package for forward compatibility with Zope 2.12 / Plone 4. If you are using Zope 2.12 or later (e.g. with Plone 4), you should do:

eggs =
    Plone
    collective.xdv

Note that there is no need to add a ZCML slug as collective.xdv uses z3c.autoinclude to configure itself automatically.

Once you have added these lines to your configuration file, it's time to run buildout, so the system can add and set up collective.xdv for you. Go to the command line, and from the root of your Plone instance (same directory as buildout.cfg is located in), run buildout like this:

$ bin/buildout

You will see output similar to this:

Getting distribution for 'collective.xdv==1.0'.
Got collective.xdv 1.0.
Getting distribution for 'plone.app.registry'.
Got plone.app.registry 1.0a1.
Getting distribution for 'plone.synchronize'.
Got plone.synchronize 1.0b1.
...

If everything went according to plan, you now have collective.xdv installed in your Zope instance.

Next, start up Zope, e.g with:

$ bin/instance fg

Then go to the "Add-ons" control panel in Plone as an administrator, and install the "XDV theme support" product. You should then notice a new "XDV Theme" control panel in Plone's site setup. 

Usage

In the "XDV Theme" control panel, you can set the following options:

Enabled yes/no
Whether or not the transform is enabled.
Domains
A list of domains (including ports) that will be matched against the HOST header to determine if the theme should be applied. Note that 127.0.0.1 is never styled, to ensure there's always a way back into Plone to change these very settings. However, 'localhost' should work just fine.
Theme
A file path or URL pointing to the theme file. This is just a static HTML file.
Rules
The filesystem path to the rules XML file.
Alternate themes
A list of definitions of alternate themes and rules files for a different path. Should be of the form 'path|theme|rules' where path may use a regular expression syntax, theme is a file path or URL to the theme template and rule is a file path to the rules file.
XSLT extension file
It is possible to extend XDV with a custom XSLT file. If you have such a file, give its URL here.
Absolute prefix
If given, any relative URL in an <img /><link /><style /> or <script /> in the theme HTML file will be prefixed by this URL snippet when the theme is compiled. This makes it easier to develop theme HTML/CSS on the file system using relative paths that still work on any URL on the server.
Unstyled paths
This is used to give a list of URL patterns (using regular expression syntax) for pages that will not be styled even if XDV is enabled. By default, this includes the 'emptypage' view that is necessary for the Kupu editor to work, and the manage_* pages that make up the ZMI.

Note that when Zope is in debug mode, the theme will be re-compiled on each request. In non-debug mode, it is compiled once on startup, and then only if the control panel values are changed. 

Resources in Python packages

When specifying the rules, theme and/or XSLT extension files, you should normally use a file path. If you are distributing your theme in a Python package that is installed using Distribute/setuptools (e.g. a standard Plone package installed via buildout), you can use the special python URL scheme to reference your files.

For example, if your package is called my.package and it contains a directory mytheme, you could reference the file rules.xml in that file as:

``python://my.package/mytheme/rules.xml``

This will be resolved to an absolute file:// URL by the collective.xdv.

Static Files and CSS

Typically, the theme will reference static resources such as images or stylesheets. It is usually a good idea to keep all of these in a single, top-level directory to minimise the risk of clashes with Plone content paths.

If you are using Zope/Plone standalone, you will need to make your static resources available through Zope, or serve them from a separate (sub-)domain. Here, you have a few options:

  • Create the static resources as File content objects through Plone.
  • Create the resources inside the portal_skins/custom folder in the ZMI.
  • Install the resources through a filesystem product.

The latter is most the appropriate option if you are distributing your theme as a Python package. In this case, you can register a resource directory in ZCML like so:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">

    ...

    <browser:resourceDirectory
        name="my.package"
        directory="mytheme"
        />

    ...

</configure>

The mytheme directory should be in the same directory as the configure.zcml file. You can now put your theme, rules and static resources here.

If you make sure that your theme uses only relative URLs to reference any stylesheets, JavaScript files, or images that it needs (including those referenced from stylesheets), you should now be able to view your static theme by going to a URL like:

http://localhost:8080/Plone/++resource++my.package/theme.html

You can now set the "Absolute prefix" configuration option to be '/++resource++my.package'. XDV will then turn those relative URLs into appropriate absolute URLs with this prefix.

If you have put Apache, nginx or IIS in front of Zope, you may want to serve the static resources from the web server directly instead. 

Using portal_css to manage your CSS

Plone's "resource registries", including the portal_css tool, can be used to manage CSS stylesheets. This offers several advantages over simply linking to your stylesheets in the template, such as:

  • Detailed control over the ordering of stylesheets
  • Merging of stylesheets to reduce the number of downloads required to render your page
  • On-the-fly stylesheet compression (e.g. whitespace removal)
  • The ability to include or exclude a stylesheet based on an expression

It is usually desirable (and sometimes completely necessary) to leave the theme file untouched, but you can still use portal_css to manage your stylesheets. The trick is to drop the theme's styles and then include all styles from Plone. For example, you could add the following rules:

<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

The use of an "or" expression for the content in the <append /> rule means that the precise ordering is maintained.

For an example of how to register stylesheets upon product installation using GenericSetup, see below. In short - use the cssregistry.xml import step in your GenericSetup profile directory.

There is one important caveat, however. Your stylesheet may include relative URL references of the following form:

background-image: url(../images/bg.jpg);

If your stylesheet lives in a resource directory (e.g. it is registered in portal_css with the id++resource++my.package/css/styles.css), this will work fine so long as the registry (and Zope) is in debug mode. The relative URL will be resolved by the browser to ++resource++my.package/images/bg.jpg.

However, you may find that the relative URL breaks when the registry is put into production mode. This is because resource merging also changes the URL of the stylesheet to be something like:

/plone-site/portal_css/Suburst+Theme/merged-cachekey-1234.css

To correct for this, you have a few options:

  1. Replace your static stylesheet with something dynamic so that you can calculate it relative an absolute path on the fly. This obviously will not work if you want to be able to view the theme standalone.
  2. Change your URLs to use an absolute path, e.g. /++resource++my.theme/images/bg.jpg. Again, this will break the original stylesheet. However, you can perhaps create a Plone-only override stylesheet that overrides each CSS property that uses a url().
  3. Avoid using portal_css for your static stylesheets.
  4. Use Plone 4. :-) In Plone 4 (b3 and later), the portal_css tool has an option to parse a stylesheet for relative URLs and apply an absolute prefix based on the stylesheet's debug-mode URL. The option is called applyPrefix in the cssregistry.xml syntax.

Controlling Plone's default CSS

It is sometimes useful to show some of Plone's CSS in the styled site. You can achieve this by using an XDV<append /> rule or similar to copy the CSS from Plone's generated <head /> into the theme. You can use the portal_css tool to turn off the style sheets you do not want.

However, if you also want the site to be usable in non-themed mode (e.g. on a separate URL), you may want to have a larger set of styles enabled when XDV is not used. To make this easier, you can use the following expressions as conditions in the portal_css tool (and portal_javascripts, portal_kss), in portal_actions, in page templates, and other places that use TAL expression syntax:

request/HTTP_X_XDV | nothing

This expression will return True if XDV is currently enabled, in which case an HTTP header "X-XDV" will be set. By default, this will check both the 'enabled' flag in the XDV control panel, and the current domain. If you later deploy the theme to a fronting web server such as nginx, you can set the same request header there to get the same effect, even if collective.xdv is uninstalled.

Use:

not: request/HTTP_X_XDV | nothing

to 'hide' a style sheet from the themed site.

A worked example

There are many ways to set up an XDV theme. For example, you could upload the theme and rules as content in Plone use absolute paths to configure them. You could also serve them from a separate static web server, or even load them from the filesystem.

To create a deployable theme, however, it is often best to create a simple Python package. This also provides a natural home for theme-related customisations such as template overrides.

Although a detailed tutorial is beyond the scope of this help file, a brief, worked example is shown below.

  1. Create a package and install it in your buildout:

    $ cd src
    $ paster create -t plone my.theme

See the buildout manual for details

If you have a recent ZopeSkel installed, this should work. Pick easy mode. Answer "yes" when asked if you want to register a profile.

Then edit buildout.cfg to add your new package (my.theme above) to the develop and eggs lists.

  1. Edit setup.py inside the newly created package

The install_requires list should be:

install_requires=[
      'setuptools',
      'collective.xdv',
  ],

Re-run buildout:

$ bin/buildout
  1. Edit configure.zcml inside the newly created package.

Add a resource directory inside the <configure /> tag. Note that you may need to add the browser namespace, as shown.

<configure

xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:i18n="http://namespaces.zope.org/i18n" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="my.theme">

<genericsetup:registerProfile
name="default" title="my.theme" directory="profiles/default" description="Installs the my.theme package" provides="Products.GenericSetup.interfaces.EXTENSION" />
<browser:resourceDirectory
name="my.theme" directory="static" />

</configure>

Here, we have used the package name, my.theme, for the resource directory name. Adjust as appropriate.

  1. Add a static directory next to configure.zcml.
  2. Put your theme and rules files into this directory.

For example, you may have a theme.html that references images in a sub-directory images/ and stylesheets in a sub-directory css/. Place this file and the two directories inside the newly created static directory.

Make sure the theme uses relative URLs (e.g. <img src="images/foo.jpg" />) to reference its resources. This means you can open theme up from the filesystem and view it in its splendour.

Also place a rules.xml file there. See the XDV documentation for details about its syntax. You can start with some very simple rules if you just want to test:

<?xml version="1.0" encoding="UTF-8"?>
<rules
    xmlns="http://namespaces.plone.org/xdv"
    xmlns:css="http://namespaces.plone.org/xdv+css">

    <!-- Head: title -->
    <replace theme="/html/head/title" content="/html/head/title" />

    <!-- Base tag -->
    <replace theme="/html/head/base" content="/html/head/base" />

    <!-- Drop styles in the head - these are added back by including them from Plone -->
    <drop theme="/html/head/link" />
    <drop theme="/html/head/style" />

    <!-- Pull in Plone CSS -->
    <append theme="/html/head" content="/html/head/link | /html/head/style " />

</rules>

These rules will pull in the <title /> tag (i.e. the browser window's title), the <base /> tag (necessary for certain Plone URLs to work correctly), and Plone's stylesheets.

See below for some more useful rules.

  1. Create the installation profile

The generated code above for the <genericsetup:registerProfile /> tag contains a reference to a directoryprofiles/default. You may need to create this next to configure.zcml if it doesn't exist already, i.e. create a new directory profiles and inside it another directory default.

In this directory, add a file called metadata.xml containing:

<metadata>
    <version>1</version>
    <dependencies>
        <dependency>profile-collective.xdv:default</dependency>
    </dependencies>
</metadata>

This will install collective.xdv into Plone when my.theme is installed via the add-on control panel later.

Also create a file called registry.xml, with the following contents:

<registry>

    <!-- collective.xdv settings -->

    <record interface="collective.xdv.interfaces.ITransformSettings" field="domains">
        <value>
            <element>domain.my:8080</element>
        </value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="rules">
        <value>python://my.theme/static/rules.xml</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="theme">
        <value>python://my.theme/static/theme.html</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="absolute_prefix">
        <value>/++resource++my.theme</value>
    </record>

</registry>

Replace my.theme with your own package name, and rules.xml and theme.html as appropriate.

This file configures the settings behind the XDV control panel.

Hint: If you have played with the control panel and want to export your settings, you can create a snapshot in the portal_setup tool in the ZMI. Examine the registry.xml file this creates, and pick out the records that relate to collective.xdv. You should strip out the <field /> tags in the export, so that you are left with <record />and <value /> tags as shown above.

Also, add a cssregistry.xml in the profiles/default directory to configure the portal_css tool:

<?xml version="1.0"?>
<object name="portal_css">

 <!-- Set conditions on stylesheets we don't want to pull in -->
 <stylesheet
     expression="not:request/HTTP_X_XDV | nothing"
     id="public.css"
     />

 <!-- Add new stylesheets -->
 <!-- Note: applyPrefix is not available in Plone < 4.0b3 -->

 <stylesheet title="" authenticated="False" cacheable="True"
    compression="safe" conditionalcomment="" cookable="True" enabled="on"
    expression="request/HTTP_X_XDV | nothing"
    id="++resource++my.theme/css/styles.css" media="" rel="stylesheet"
    rendering="link"
    applyPrefix="True"
    />

</object>

This shows how to set a condition on an existing stylesheet, as well as registering a brand new one. We've setapplyPrefix to True here, as explained above. This will only work in Plone 4.b3 and later. For earlier versions, simply take this out.

  1. Test

Start up Zope and go to your Plone site. Your new package should show as installable in the add-on product control panel. When installed, it should install collective.xdv as a dependency and pre-configure it to use your theme and rule set. By default, the theme is not enabled, so you will need to go to the control panel to switch it on.

You can now compare your untouched theme, the unstyled Plone site, and the themed site by using the following URLs:

  • http://localhost:8080 (or whatever you have configured as the styled domain) for a styled Plone. If you used the sample rule above, this will look almost exactly like your theme, but with the <title /> tag (normally shown in the title bar of your web browser) taken from Plone.
  • http://127.0.0.1:8080 (presuming this is the port where Plone is running) for an unstyled Plone.
  • http://localhost:8080/++resource++my.theme/theme.html for the pristine theme. This is served as a static resource, almost as if it is being opened on the filesystem.

Common rules

To copy the page title:

<!-- Head: title -->
<replace theme="/html/head/title" content="/html/head/title" />

To copy the <base /> tag (necessary for Plone's links to work):

<!-- Base tag -->
<replace theme="/html/head/base" content="/html/head/base" />

To drop all styles and JavaScript resources from the theme and copy them from Plone's portal_css tool instead:

<!-- Drop styles in the head - these are added back by including them from Plone -->
<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

To copy Plone's JavaScript resources:

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/script" />

To copy the class of the <body /> tag (necessary for certain Plone JavaScript functions and styles to work properly):

<!-- Body -->
<prepend theme="/html/body" content="/html/body/attribute::class" />

Other tips

  • Firebug is an excellent tool for inspecting the theme and content when building rules. It even has an XPath extractor.
  • Read up on XPath. It's not as complex as it looks and very powerful.
  • Run Zope in debug mode whilst developing so that you don't need to restart to see changes to theme, rules or, resources.

Changelog

1.0rc9 - 2010-08-05

  • Use an IBeforeTraverseEvent on the Plone site root instead of an IPubAfterTraversal event to hook in the X-XDV request header. This makes the header work on 404 error pages. [optilude]
  • Add collective.directoryresourcepatch to the Zope2.10 extras. This allows for subdirectories to be traversed by the ResourceRegistries while running Plone 3/Zope 2.10. [dunlapm]
  • Require lxml>=2.2.4. The Zope2 KGS lists lxml=2.2, a version which errors on invalid html. [elro]
  • Fix extra.xsl support. [elro]

1.0rc8 - 2010-05-24

1.0rc7 - 2010-05-23

  • UPGRADE NOTE: Reinstall product in the Add-ons control panel.
  • Switch on XInclude processing always. [elro]
  • Fix Windows install. For running under Plone 4 on Windows, you must specify:
    [versions]
    lxml = 2.2.4
    until a newer lxml Windows binary egg is released. [elro]
  • Instead of the external resolver, let lxml read the network. You must now explicitly enable Read network in the control panel. [elro]

1.0rc6 - 2010-05-21

  • Fix transform caching to account for different virtual hosts of the same site and make cache invalidation work across ZEO clients. [elro]

1.0rc5 - 2010-04-21

  • Fix in-Plone content inclusion via the href mechanism, including the use of relative paths in hrefs. [optilude]
  • Ensured that the absolute prefix would work even in a virtual hosting scenario where the aboslute path of the site root is '/'. [optilude]
  • Added an event handler which will set an HTTP request header 'X-XDV' if XDV is enabled for the incoming domain. This can be used as a check in e.g. portal_css, for example with a TALES expression like 'request/HTTP_X_XDV | nothing'. The @@xdv-check/enabled method now just checks for the existence of this variable too. The idea is that it is easier to replicate this in a pure-XSLT deployment scenario with collective.xdv disabled, for example by setting the same request header in nginx or Apache. [optilude]
  • Made all zope paths resolve relative to the Plone site. [marshalium]
  • Add support for resolving files with http/ftp absolute urls and zope paths. [marshalium]
  • Make absolute_prefix prepend the Plone site path if necessary. This means that an absolute prefix starting with / is always relative to the Plone site root. [optilude]
  • Add support for the python:// pseudo-scheme for the theme, rules and extrauri files. See README.txt for details. [optilude]
  • Improve the wording in the control panel [optilude]
  • Fix a bug whereby the cached transforms (in non-debug-mode) would leak across Plone sites in the same instance. [optilude]
  • Remove the boilerplate parameter. Use extraurl instead. [optilude]
  • Let collective.xdv depend on the new XDV egg, instead of dv.xdvserver. [optilude]
  • Only invoke the transformation if collective.xdv is in fact installed. Note: you may need to re-install the product after upgrading. [optilude]
  • Use plone.transformchain to sequence transformation activities. Among other things, this helps us avoid re-parsing/serialising lxml trees when other things in the chain prefer to work with such representations of the response. It also helps control the sequence of post-publication events. [optilude]
  • Zope 2.12 / Plone 4 compatability. [lrowe]

1.0rc4 - 2009-10-27

  • Style error responses as well as successful responses. [lrowe]
  • Use ZPublisher events instead of plone.postpublicationhook for compatibility with Zope 2.12 / Plone 4. For Zope2.10 / Plone 3.x, you must now specify "collective.xdv [Zope2.10]" in your buildout to bring in the package ZPublisherEventsBackport. [lrowe]
  • Added support for extraurl parameter [mhora]
  • Added alternate themes and modified transform so it can decide by a path regular expression which theme and rules files it will use for transformation [mhora]
  • Add /manage in unstyled paths default list. [encolpe]

1.0a2 - 2009-07-12

  • Catch up with changes in plone.registry's API. [optilude]

1.0a1 - 2009-04-17

  • Initial release

4.1. Rules overview

Four rules are all you need to remember theme a site.

Luckily, what goes in the rule file is very simple — there are only four types of rules, and you'll get the hang of them quickly. The rules are:

  • replace
  • append/prepend
  • copy
  • drop

Let's look at what they do, and show some real-world examples of how they are used.

<replace>

Replaces an element in the theme with content from the site.

Real-world examples:

A useful thing to do is to carry over the <title> and <base> tags from Plone, so the theme will have the right page titles and work correctly when you do operations on folders:

<replace content='/html/head/title' 
           theme='/html/head/title' />

<replace content='/html/head/base' 
           theme='/html/head/base' />

The XPath expressions are actually pretty straightforward when you know exactly where the elements are. You'll get used to the most common variations after using Firebug's "Copy XPath" a few times.

Also note how the entire specified tag is replaced, nothing from the theme file remains.

Another common example is making the content of a page from Plone appear in the theme:

<replace content='//*[@id="content"]'
           theme='//*[@id="my-content"]' />

<append> & <prepend>

Adds the content from the site to the theme, either before or after the specified element.

Real-world examples:

Adding the Plone-created CSS and JS in addition to the ones already in the theme:

<append content='/html/head/style' 
          theme='/html/head' />

<append content='/html/head/script' 
          theme='/html/head' />

Notice how we take the all the <script> and <style> tags from Plone, and append them after the current content of the head tag in the theme. This way, you can let Plone manage some of your CSS and JS if you want — useful for conditional includes.

Another examples is carrying over the id and class attributes on the body tag, since these are useful for styling on a per-page basis — and the visual editor uses them too:

<prepend content="/html/body/@class"
           theme="/html/body" />

<prepend content="/html/body/@id"
           theme="/html/body"  />

A final example that illustrates the <append> usage; imagine that we only have one sidebar, but have two columns in the Plone site that we both want to appear inside the sidebar:

<append content='//*[@id="portal-column-one"]/div'
          theme='//*[@id="sidebar"]' />

<append content='//*[@id="portal-column-two"]/div'
          theme='//*[@id="sidebar"]' />

This way, the second rule doesn't overwrite the first — it appends the second column, so both appear inside the id="sidebar" node.

<copy>

Copies HTML nodes from the Plone side of things and inserts it inside a tag on the theme side:

<copy content='//*[@id="portal-globalnav"]/li' 
        theme='//*[@id="main-nav"]' />

Notice how this one gets every <li> element inside the node with id="portal-globalnav" in Plone, and makes a copy inside the node that has id="main-nav" in the theme.

<drop>

Removes the specified element if it exists. This one is a bit different than the others, since it only has a content= value — since it only makes sense to drop an element from the Plone side.

Real-world example:

Getting rid of the icon inside the "user-name" node:

<drop content='//*[@id="user-name"]/img' />

Since there is no ID directly on this image, we just drop any <img> inside the "user-name" ID.

That's actually everything you need to know, now the next steps are up to you!

5. Static Resources

How to incorporate your own CSS and JavaScript files, and your theme images.

Project moved
XDV has been renamed to Diazo, and this integration package has been replaced by plone.app.theming. Visit the Diazo website and the plone.app.theming PyPI page for further information.

This package offers a simple way to develop and deploy Plone themes using the XDV engine. If you are not familiar with XDV or rules-based theming, check out the XDV documentation.

Installation

collective.xdv depends on:

These will all be pulled in automatically if you are using zc.buildout and follow the installation instructions.

To install collective.xdv into your Plone instance, locate the file buildout.cfg in the root of your Plone instance directory on the file system, and open it in a text editor. Locate the section that looks like this:

# extends = http://dist.plone.org/release/3.3/versions.cfg
extends = versions.cfg
versions = versions

It may also have a URL in the "extends" section, similar to the commented-out first line, depending on whether you pull the Plone configuration from the network or locally.

To add collective.xdv to our setup, we need some slightly different versions of a couple of the packages, so we extend the base config with a version list from the good-py service, so change this part of the configuration so it looks like this:

extends =
    versions.cfg
    http://good-py.appspot.com/release/collective.xdv/1.0?plone=3.3.5
versions = versions

Note that the last part of the URL above before the ? is the xdv version number. There may be a newer version by the time you read this, so check out the overview page for the known good set.

Replace ?plone=3.3.5 with the version of Plone you are using. This dependency versions appropriate to your Plone.

What happens here is that the dependency list for collective.xdv specifies some new versions for you via the good-py URL. This way, you don't have to worry about getting the right versions, Buildout will handle it for you.

Next step is to add the actual collective.xdv add-on to the "eggs" section of buildout.cfg. Look for the section that looks like this:

eggs =
    Plone

This section might have additional lines if you have other add-ons already installed. Just add the collective.xdv on a separate line, like this:

eggs =
    Plone
    collective.xdv [Zope2.10]

Note the use of the [Zope2.10] extra, which brings in the ZPublisherEventsBackport package for forward compatibility with Zope 2.12 / Plone 4. If you are using Zope 2.12 or later (e.g. with Plone 4), you should do:

eggs =
    Plone
    collective.xdv

Note that there is no need to add a ZCML slug as collective.xdv uses z3c.autoinclude to configure itself automatically.

Once you have added these lines to your configuration file, it's time to run buildout, so the system can add and set up collective.xdv for you. Go to the command line, and from the root of your Plone instance (same directory as buildout.cfg is located in), run buildout like this:

$ bin/buildout

You will see output similar to this:

Getting distribution for 'collective.xdv==1.0'.
Got collective.xdv 1.0.
Getting distribution for 'plone.app.registry'.
Got plone.app.registry 1.0a1.
Getting distribution for 'plone.synchronize'.
Got plone.synchronize 1.0b1.
...

If everything went according to plan, you now have collective.xdv installed in your Zope instance.

Next, start up Zope, e.g with:

$ bin/instance fg

Then go to the "Add-ons" control panel in Plone as an administrator, and install the "XDV theme support" product. You should then notice a new "XDV Theme" control panel in Plone's site setup. 

Usage

In the "XDV Theme" control panel, you can set the following options:

Enabled yes/no
Whether or not the transform is enabled.
Domains
A list of domains (including ports) that will be matched against the HOST header to determine if the theme should be applied. Note that 127.0.0.1 is never styled, to ensure there's always a way back into Plone to change these very settings. However, 'localhost' should work just fine.
Theme
A file path or URL pointing to the theme file. This is just a static HTML file.
Rules
The filesystem path to the rules XML file.
Alternate themes
A list of definitions of alternate themes and rules files for a different path. Should be of the form 'path|theme|rules' where path may use a regular expression syntax, theme is a file path or URL to the theme template and rule is a file path to the rules file.
XSLT extension file
It is possible to extend XDV with a custom XSLT file. If you have such a file, give its URL here.
Absolute prefix
If given, any relative URL in an <img /><link /><style /> or <script /> in the theme HTML file will be prefixed by this URL snippet when the theme is compiled. This makes it easier to develop theme HTML/CSS on the file system using relative paths that still work on any URL on the server.
Unstyled paths
This is used to give a list of URL patterns (using regular expression syntax) for pages that will not be styled even if XDV is enabled. By default, this includes the 'emptypage' view that is necessary for the Kupu editor to work, and the manage_* pages that make up the ZMI.

Note that when Zope is in debug mode, the theme will be re-compiled on each request. In non-debug mode, it is compiled once on startup, and then only if the control panel values are changed. 

Resources in Python packages

When specifying the rules, theme and/or XSLT extension files, you should normally use a file path. If you are distributing your theme in a Python package that is installed using Distribute/setuptools (e.g. a standard Plone package installed via buildout), you can use the special python URL scheme to reference your files.

For example, if your package is called my.package and it contains a directory mytheme, you could reference the file rules.xml in that file as:

``python://my.package/mytheme/rules.xml``

This will be resolved to an absolute file:// URL by the collective.xdv.

Static Files and CSS

Typically, the theme will reference static resources such as images or stylesheets. It is usually a good idea to keep all of these in a single, top-level directory to minimise the risk of clashes with Plone content paths.

If you are using Zope/Plone standalone, you will need to make your static resources available through Zope, or serve them from a separate (sub-)domain. Here, you have a few options:

  • Create the static resources as File content objects through Plone.
  • Create the resources inside the portal_skins/custom folder in the ZMI.
  • Install the resources through a filesystem product.

The latter is most the appropriate option if you are distributing your theme as a Python package. In this case, you can register a resource directory in ZCML like so:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">

    ...

    <browser:resourceDirectory
        name="my.package"
        directory="mytheme"
        />

    ...

</configure>

The mytheme directory should be in the same directory as the configure.zcml file. You can now put your theme, rules and static resources here.

If you make sure that your theme uses only relative URLs to reference any stylesheets, JavaScript files, or images that it needs (including those referenced from stylesheets), you should now be able to view your static theme by going to a URL like:

http://localhost:8080/Plone/++resource++my.package/theme.html

You can now set the "Absolute prefix" configuration option to be '/++resource++my.package'. XDV will then turn those relative URLs into appropriate absolute URLs with this prefix.

If you have put Apache, nginx or IIS in front of Zope, you may want to serve the static resources from the web server directly instead. 

Using portal_css to manage your CSS

Plone's "resource registries", including the portal_css tool, can be used to manage CSS stylesheets. This offers several advantages over simply linking to your stylesheets in the template, such as:

  • Detailed control over the ordering of stylesheets
  • Merging of stylesheets to reduce the number of downloads required to render your page
  • On-the-fly stylesheet compression (e.g. whitespace removal)
  • The ability to include or exclude a stylesheet based on an expression

It is usually desirable (and sometimes completely necessary) to leave the theme file untouched, but you can still use portal_css to manage your stylesheets. The trick is to drop the theme's styles and then include all styles from Plone. For example, you could add the following rules:

<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

The use of an "or" expression for the content in the <append /> rule means that the precise ordering is maintained.

For an example of how to register stylesheets upon product installation using GenericSetup, see below. In short - use the cssregistry.xml import step in your GenericSetup profile directory.

There is one important caveat, however. Your stylesheet may include relative URL references of the following form:

background-image: url(../images/bg.jpg);

If your stylesheet lives in a resource directory (e.g. it is registered in portal_css with the id++resource++my.package/css/styles.css), this will work fine so long as the registry (and Zope) is in debug mode. The relative URL will be resolved by the browser to ++resource++my.package/images/bg.jpg.

However, you may find that the relative URL breaks when the registry is put into production mode. This is because resource merging also changes the URL of the stylesheet to be something like:

/plone-site/portal_css/Suburst+Theme/merged-cachekey-1234.css

To correct for this, you have a few options:

  1. Replace your static stylesheet with something dynamic so that you can calculate it relative an absolute path on the fly. This obviously will not work if you want to be able to view the theme standalone.
  2. Change your URLs to use an absolute path, e.g. /++resource++my.theme/images/bg.jpg. Again, this will break the original stylesheet. However, you can perhaps create a Plone-only override stylesheet that overrides each CSS property that uses a url().
  3. Avoid using portal_css for your static stylesheets.
  4. Use Plone 4. :-) In Plone 4 (b3 and later), the portal_css tool has an option to parse a stylesheet for relative URLs and apply an absolute prefix based on the stylesheet's debug-mode URL. The option is called applyPrefix in the cssregistry.xml syntax.

Controlling Plone's default CSS

It is sometimes useful to show some of Plone's CSS in the styled site. You can achieve this by using an XDV<append /> rule or similar to copy the CSS from Plone's generated <head /> into the theme. You can use the portal_css tool to turn off the style sheets you do not want.

However, if you also want the site to be usable in non-themed mode (e.g. on a separate URL), you may want to have a larger set of styles enabled when XDV is not used. To make this easier, you can use the following expressions as conditions in the portal_css tool (and portal_javascripts, portal_kss), in portal_actions, in page templates, and other places that use TAL expression syntax:

request/HTTP_X_XDV | nothing

This expression will return True if XDV is currently enabled, in which case an HTTP header "X-XDV" will be set. By default, this will check both the 'enabled' flag in the XDV control panel, and the current domain. If you later deploy the theme to a fronting web server such as nginx, you can set the same request header there to get the same effect, even if collective.xdv is uninstalled.

Use:

not: request/HTTP_X_XDV | nothing

to 'hide' a style sheet from the themed site.

A worked example

There are many ways to set up an XDV theme. For example, you could upload the theme and rules as content in Plone use absolute paths to configure them. You could also serve them from a separate static web server, or even load them from the filesystem.

To create a deployable theme, however, it is often best to create a simple Python package. This also provides a natural home for theme-related customisations such as template overrides.

Although a detailed tutorial is beyond the scope of this help file, a brief, worked example is shown below.

  1. Create a package and install it in your buildout:

    $ cd src
    $ paster create -t plone my.theme

See the buildout manual for details

If you have a recent ZopeSkel installed, this should work. Pick easy mode. Answer "yes" when asked if you want to register a profile.

Then edit buildout.cfg to add your new package (my.theme above) to the develop and eggs lists.

  1. Edit setup.py inside the newly created package

The install_requires list should be:

install_requires=[
      'setuptools',
      'collective.xdv',
  ],

Re-run buildout:

$ bin/buildout
  1. Edit configure.zcml inside the newly created package.

Add a resource directory inside the <configure /> tag. Note that you may need to add the browser namespace, as shown.

<configure

xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:i18n="http://namespaces.zope.org/i18n" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="my.theme">

<genericsetup:registerProfile
name="default" title="my.theme" directory="profiles/default" description="Installs the my.theme package" provides="Products.GenericSetup.interfaces.EXTENSION" />
<browser:resourceDirectory
name="my.theme" directory="static" />

</configure>

Here, we have used the package name, my.theme, for the resource directory name. Adjust as appropriate.

  1. Add a static directory next to configure.zcml.
  2. Put your theme and rules files into this directory.

For example, you may have a theme.html that references images in a sub-directory images/ and stylesheets in a sub-directory css/. Place this file and the two directories inside the newly created static directory.

Make sure the theme uses relative URLs (e.g. <img src="images/foo.jpg" />) to reference its resources. This means you can open theme up from the filesystem and view it in its splendour.

Also place a rules.xml file there. See the XDV documentation for details about its syntax. You can start with some very simple rules if you just want to test:

<?xml version="1.0" encoding="UTF-8"?>
<rules
    xmlns="http://namespaces.plone.org/xdv"
    xmlns:css="http://namespaces.plone.org/xdv+css">

    <!-- Head: title -->
    <replace theme="/html/head/title" content="/html/head/title" />

    <!-- Base tag -->
    <replace theme="/html/head/base" content="/html/head/base" />

    <!-- Drop styles in the head - these are added back by including them from Plone -->
    <drop theme="/html/head/link" />
    <drop theme="/html/head/style" />

    <!-- Pull in Plone CSS -->
    <append theme="/html/head" content="/html/head/link | /html/head/style " />

</rules>

These rules will pull in the <title /> tag (i.e. the browser window's title), the <base /> tag (necessary for certain Plone URLs to work correctly), and Plone's stylesheets.

See below for some more useful rules.

  1. Create the installation profile

The generated code above for the <genericsetup:registerProfile /> tag contains a reference to a directoryprofiles/default. You may need to create this next to configure.zcml if it doesn't exist already, i.e. create a new directory profiles and inside it another directory default.

In this directory, add a file called metadata.xml containing:

<metadata>
    <version>1</version>
    <dependencies>
        <dependency>profile-collective.xdv:default</dependency>
    </dependencies>
</metadata>

This will install collective.xdv into Plone when my.theme is installed via the add-on control panel later.

Also create a file called registry.xml, with the following contents:

<registry>

    <!-- collective.xdv settings -->

    <record interface="collective.xdv.interfaces.ITransformSettings" field="domains">
        <value>
            <element>domain.my:8080</element>
        </value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="rules">
        <value>python://my.theme/static/rules.xml</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="theme">
        <value>python://my.theme/static/theme.html</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="absolute_prefix">
        <value>/++resource++my.theme</value>
    </record>

</registry>

Replace my.theme with your own package name, and rules.xml and theme.html as appropriate.

This file configures the settings behind the XDV control panel.

Hint: If you have played with the control panel and want to export your settings, you can create a snapshot in the portal_setup tool in the ZMI. Examine the registry.xml file this creates, and pick out the records that relate to collective.xdv. You should strip out the <field /> tags in the export, so that you are left with <record />and <value /> tags as shown above.

Also, add a cssregistry.xml in the profiles/default directory to configure the portal_css tool:

<?xml version="1.0"?>
<object name="portal_css">

 <!-- Set conditions on stylesheets we don't want to pull in -->
 <stylesheet
     expression="not:request/HTTP_X_XDV | nothing"
     id="public.css"
     />

 <!-- Add new stylesheets -->
 <!-- Note: applyPrefix is not available in Plone < 4.0b3 -->

 <stylesheet title="" authenticated="False" cacheable="True"
    compression="safe" conditionalcomment="" cookable="True" enabled="on"
    expression="request/HTTP_X_XDV | nothing"
    id="++resource++my.theme/css/styles.css" media="" rel="stylesheet"
    rendering="link"
    applyPrefix="True"
    />

</object>

This shows how to set a condition on an existing stylesheet, as well as registering a brand new one. We've setapplyPrefix to True here, as explained above. This will only work in Plone 4.b3 and later. For earlier versions, simply take this out.

  1. Test

Start up Zope and go to your Plone site. Your new package should show as installable in the add-on product control panel. When installed, it should install collective.xdv as a dependency and pre-configure it to use your theme and rule set. By default, the theme is not enabled, so you will need to go to the control panel to switch it on.

You can now compare your untouched theme, the unstyled Plone site, and the themed site by using the following URLs:

  • http://localhost:8080 (or whatever you have configured as the styled domain) for a styled Plone. If you used the sample rule above, this will look almost exactly like your theme, but with the <title /> tag (normally shown in the title bar of your web browser) taken from Plone.
  • http://127.0.0.1:8080 (presuming this is the port where Plone is running) for an unstyled Plone.
  • http://localhost:8080/++resource++my.theme/theme.html for the pristine theme. This is served as a static resource, almost as if it is being opened on the filesystem.

Common rules

To copy the page title:

<!-- Head: title -->
<replace theme="/html/head/title" content="/html/head/title" />

To copy the <base /> tag (necessary for Plone's links to work):

<!-- Base tag -->
<replace theme="/html/head/base" content="/html/head/base" />

To drop all styles and JavaScript resources from the theme and copy them from Plone's portal_css tool instead:

<!-- Drop styles in the head - these are added back by including them from Plone -->
<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

To copy Plone's JavaScript resources:

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/script" />

To copy the class of the <body /> tag (necessary for certain Plone JavaScript functions and styles to work properly):

<!-- Body -->
<prepend theme="/html/body" content="/html/body/attribute::class" />

Other tips

  • Firebug is an excellent tool for inspecting the theme and content when building rules. It even has an XPath extractor.
  • Read up on XPath. It's not as complex as it looks and very powerful.
  • Run Zope in debug mode whilst developing so that you don't need to restart to see changes to theme, rules or, resources.

Changelog

1.0rc9 - 2010-08-05

  • Use an IBeforeTraverseEvent on the Plone site root instead of an IPubAfterTraversal event to hook in the X-XDV request header. This makes the header work on 404 error pages. [optilude]
  • Add collective.directoryresourcepatch to the Zope2.10 extras. This allows for subdirectories to be traversed by the ResourceRegistries while running Plone 3/Zope 2.10. [dunlapm]
  • Require lxml>=2.2.4. The Zope2 KGS lists lxml=2.2, a version which errors on invalid html. [elro]
  • Fix extra.xsl support. [elro]

1.0rc8 - 2010-05-24

1.0rc7 - 2010-05-23

  • UPGRADE NOTE: Reinstall product in the Add-ons control panel.
  • Switch on XInclude processing always. [elro]
  • Fix Windows install. For running under Plone 4 on Windows, you must specify:
    [versions]
    lxml = 2.2.4
    until a newer lxml Windows binary egg is released. [elro]
  • Instead of the external resolver, let lxml read the network. You must now explicitly enable Read network in the control panel. [elro]

1.0rc6 - 2010-05-21

  • Fix transform caching to account for different virtual hosts of the same site and make cache invalidation work across ZEO clients. [elro]

1.0rc5 - 2010-04-21

  • Fix in-Plone content inclusion via the href mechanism, including the use of relative paths in hrefs. [optilude]
  • Ensured that the absolute prefix would work even in a virtual hosting scenario where the aboslute path of the site root is '/'. [optilude]
  • Added an event handler which will set an HTTP request header 'X-XDV' if XDV is enabled for the incoming domain. This can be used as a check in e.g. portal_css, for example with a TALES expression like 'request/HTTP_X_XDV | nothing'. The @@xdv-check/enabled method now just checks for the existence of this variable too. The idea is that it is easier to replicate this in a pure-XSLT deployment scenario with collective.xdv disabled, for example by setting the same request header in nginx or Apache. [optilude]
  • Made all zope paths resolve relative to the Plone site. [marshalium]
  • Add support for resolving files with http/ftp absolute urls and zope paths. [marshalium]
  • Make absolute_prefix prepend the Plone site path if necessary. This means that an absolute prefix starting with / is always relative to the Plone site root. [optilude]
  • Add support for the python:// pseudo-scheme for the theme, rules and extrauri files. See README.txt for details. [optilude]
  • Improve the wording in the control panel [optilude]
  • Fix a bug whereby the cached transforms (in non-debug-mode) would leak across Plone sites in the same instance. [optilude]
  • Remove the boilerplate parameter. Use extraurl instead. [optilude]
  • Let collective.xdv depend on the new XDV egg, instead of dv.xdvserver. [optilude]
  • Only invoke the transformation if collective.xdv is in fact installed. Note: you may need to re-install the product after upgrading. [optilude]
  • Use plone.transformchain to sequence transformation activities. Among other things, this helps us avoid re-parsing/serialising lxml trees when other things in the chain prefer to work with such representations of the response. It also helps control the sequence of post-publication events. [optilude]
  • Zope 2.12 / Plone 4 compatability. [lrowe]

1.0rc4 - 2009-10-27

  • Style error responses as well as successful responses. [lrowe]
  • Use ZPublisher events instead of plone.postpublicationhook for compatibility with Zope 2.12 / Plone 4. For Zope2.10 / Plone 3.x, you must now specify "collective.xdv [Zope2.10]" in your buildout to bring in the package ZPublisherEventsBackport. [lrowe]
  • Added support for extraurl parameter [mhora]
  • Added alternate themes and modified transform so it can decide by a path regular expression which theme and rules files it will use for transformation [mhora]
  • Add /manage in unstyled paths default list. [encolpe]

1.0a2 - 2009-07-12

  • Catch up with changes in plone.registry's API. [optilude]

1.0a1 - 2009-04-17

  • Initial release

5.1. Techniques

Methods to incorporate your own images, style sheets and JavaScript files into your theme.

Collective.xdv works by compiling the information from your theme.html template, your rules file and your Plone content into a brand new page.  Static files forming part of your theme, such as CSS, JavaScript and images, get pulled into the page after this compilation has taken place. As a result, your web server needs to know where to find these resources - it isn't automatically aware of the theme directory you set up to hold your template and rules.

In a production environment, you'll probably have Apache, Nginx or some other web server sitting in front of Plone and Zope, and, with the correct configuration, this will take care of making the static resources available to your compiled page. However, on a development computer - perhaps your own PC or laptop - you're probably more accustomed to just use Zope itself as the web server. In this case you've got several options for making your static resources available to your compiled page.

1. The Quick and Dirty

Your simplest option is to drop your resources into the Custom folder in portal_skins. Your Plone site will then find these resources through a simple relative link (such as href="test.css"). The disadvantage to this of course is that it's much harder to move these resources from site to site - and there's always the risk of losing them if you accidentally delete a site.

2. The Reflecto Method

Products.reflecto is a third-party add-on product which allows you to expose a section of your file system as if it were Plone content. In this case then, you can drop your static resources into a directory alongside your theme.html template, and work with them on the file-system. Once you've installed reflecto and set up a reflector content item to point at your static resources directory, your images and other files will be served just like any other piece of content in your Plone site.

3. The Slightly More Complicated

A more daunting option - particularly for the average web designer - is to set up a web server in front of Zope and Plone on your development instance and serve the static resources through these. In fact, it actually isn't as hard as you think to run a web server for development purposes. If you have a Mac running OS X.5, then the next page, Apache on your Mac, will take you through the basics. If you are running Windows, or a flavour of Linux, then many of the configuration details will be the same, but you will probably need to install Apache first.

4. The Hybrid Approach

This approach combines a traditional Theme Product Package with collective.xdv. It may well appeal to those designers schooled and seasoned in the old Plone 3 theming methods. Indeed, as an advanced technique, it combines the best of both worlds, in that Plone's registries can be used to cache resources or set conditions on when they are used. Other configuration details which might be required can also be embedded in this product. Denys Mishunov's Advanced XDV Theming tutorial will walk you through this technique.

5.2. Apache on your Mac

Easy steps to running Apache on your Mac to serve your own images, CSS files and JavaScript for collective.xdv.

Apache comes ready installed on a Mac, so it is reasonably quick and easy to set it up to serve up static resources for your collective.xdv theme. This configuration is intended for development purposes only and simply configures Apache to deliver your Plone site via http://localhost/ (i.e. through port 80) and to deliver your images, style sheets and JavaScript via http://localhost/staticresources. You'll still be able to get to plain vanilla Zope and Plone through http://localhost:8080 and http://127.0.0.1:8080.

Configuration

First we need to locate and edit Apache's configuration file - httpd.conf. If you're going to do this via the easy route using Finder, you'll first need to reveal invisible files. Ironically, the quickest way to do this is to use a terminal window:

defaults write com.apple.finder AppleShowAllFiles Yes

Relaunch Finder - Apple Menu > Force Quit > Finder and you should then be able to see a folder at the root of your Mac's hard disk /private/etc/apache2 containing the main Apache configuration file - httpd.conf.

To be on the safe side, make a back-up of httpd.conf and then open the original httpd.conf with a text editor. First, we're going to make Apache only respond when you type http://localhost in your browser address bar. Scroll down a few lines until you see

Listen 80

replace this with

Listen localhost:80

We're making this change on the assumption that you're setting the configuration up for development purposes only. For a deployment server you would leave this line as it is.

Next, we're going to tell Apache to deliver your Plone site and your static resources via http://localhost. Head down to the section marked #main server configuration and add these three lines:

RewriteEngine On
RewriteRule ^/staticresources/?(.*) - [L]
RewriteRule ^/(.*)$ http://localhost:8080/VirtualHostBase/http/localhost:80/[your plone site name]/VirtualHostRoot/$1 [L,P]
  • it doesn't matter much where you add this, except make sure you don't insert it inside another directive. I slipped mine in just before the #Default type section.
  • [your plone site name] is the name of the plone site you're working on. If you're working with the site created by the default installation, then this will be Plone.

Save the file, you'll probably have to type your password.

Now add your staticresources folder. Using Finder, go to /Library/Webserver/Documents on your Mac hard disk and add a folder there with the name staticresources.

Note this is the Library at the root of your Mac hard disk, not the Library in your own user folder

Running the Server

Start Zope/Plone in the usual way. Then open a terminal window to check your configuration file:

sudo apachectl checkconfig

or

sudo apachectl -t

will check that the configuration file you've just altered is OK, you can then start the server:

sudo apachectl start

will start Apache.

Note sudo apachectl stop will stop the server. If you make a change to your httpd.conf file, you'll need to restart Apache (sudo apachectl restart) to reload the configuration.

Now go to http://localhost/ in your browser - if everything is working you should see your Plone site. Next go to http://localhost/staticresources/ - you should see a directory listing of your staticresources folder.

Engaging your static theme

The next job is to tell collective.xdv to deliver your Plone site wrapped in your static theme through the domain http://localhost (rather than localhost:8080). Access your site via http://127.0.0.1:8080 to get the plain vanilla version and go to Site Setup - Theme transform. In the Domains box, add:

locahost

Once you've made sure that your theme transform is switched on and saved your changes, refreshing http://localhost/ in your browser should now deliver your site wrapped in your static theme.

Finally test out your staticresources folder by dropping a css file into it. Remember this folder can be found in /Library/Webserver/Documents on your hard disk. Add a link to this CSS file in the head of your theme file, the href attribute should look something like this:

href="/staticresources/test.css"

Make sure that your compiled theme will pick this up by double-checking that, in your rules file, you aren't completely replacing the head tag with the head tag generated by Plone (you'll need a prepend or an append rule to combine the Plone head tag with your theme head tag).

Variations

The above is the quick and dirty approach - designed to get you up and running as quickly as possible with minimum changes to the Apache configuration provided with your Mac - but there are other things you can do.

Relocating your Static Resources

You might want to change the physical location of your static resources on your hard disk. In that case, look for the DocumentRoot directive (and its corresponding Directory directive) in httpd.conf and edit these entries to point to your chosen folder.

Multiple Sites

If you want to see more than one Plone site through Apache, then try replacing the VirtualHost rewrite rule in your httpd.conf with this:

RewriteRule  ^(.*)  http://localhost:8080/VirtualHostBase/http/localhost:80/VirtualHostRoot/$1 [L,P]

You'll then need to type http://localhost/[your Plone site name]/ to see your site, but there will be no need to change the links to your static resources in your theme file. One important thing to note: if you use collective.xdv while running Zope in debug mode, you can have as many rule and theme files as you do sites on localhost  (the rules and theme are compiled with every request). This is very convenient for development purposes, if, for instance, you have two projects running at once. However, this doesn't translate into production mode. With debug turned off, you are limited to one set of rules and theme files per domain. This is because the rules and theme are compiled once, when the Theme transform is switched to 'On', and then cached.

Starting and Stopping Apache

It's possible to start Apache via your System Preferences panel, go to Sharing and then turn on Web Sharing. Your Mac will tell you that users of other computers can view web pages in the Sites folder on your computer, however, this won't be the case because we've set Apache to listen for localhost:80 only. Set your httpd.conf file back to Listen 80, if you want other people to access your pages.

 

5.3. Same Theme, Different Resources

You can use the same theme.html file for more than one site, but make changes to the images, css and other static resources by using the Absolute URL Prefix Box.

Once you have your static resources set up to be delivered by a webserver like Apache, you have the power to apply variations to the same theme for different Plone sites.

Set up your Sites

To try this out, add a second Plone site to the root of your Zope Management Interface. Install the Theme Transforms Add On and, via Site Setup - Theme transform, add the same domain, theme and rules as your first Plone site.

Next, assuming that your static resources live in a folder on your hard disk called staticresources, edit the entry in the Absolute URL Prefix box in Site Setup - Theme transform to read:

http://localhost/staticresources/greensite/

Finally, go to your original Plone site and edit the Absolute URL Prefix box on that site to read:

http://localhost/staticresources/redsite/

Set  up your Theme

Now, make the links to your static resources in your theme file relative rather than absolute. 

href="test.css"

Turning to the staticresources folder on your hard disk, add a folder called greensite and drop a test.css file into it. Then add a folder called redsite and drop a slightly different version of test.css into that one.

Tweak Apache

You'll need to tweak Apache so that both sites can be seen through http://localhost. Follow the instructions in the variations section of Apache on your Mac to make a slight change to the rewrite rules in the httpd.conf file. Don't forget to restart!

5.4. Using Plone's CSS and JavaScript Registries

If you are making your static resources available through Plone you can use Plone's resource registries to deliver them. You may also use conditions to do this selectively.

It is sometimes useful to show some of Plone's CSS in the styled site. You can achieve this by using an xdv <append/> rule or similar to copy the CSS from Plone's generated <head/> into the theme. You can use the portal_css tool to turn off the style sheets you do not want.

However, if you also want the site to be usable in non-themed mode (e.g. on a separate URL), you may want to have a larger set of styles enabled when xdv is not used. To make this easier, you can use the following expressions as conditions in portal_css (and portal_javascripts, portal_kss, portal_actions, in page templates, and other places that use TAL expression syntax):

portal/@@xdv-check/enabled
Will return True if xdv is currently enabled. This will check both the 'enabled' flag in the Theme Transform settings, and the current domain. Use 'not: portal/@@xdv-check/enabled' to 'hide' a style sheet from the themed site.
portal/@@xdv-check/domain_enabled
Similar to the 'enabled' check, but only checks the domain, not the global 'enabled' flag in the control panel. This is useful if you want to apply the xdv transform outside Plone, but still need a way to control which domains get which styles.

6. Other Options in the Theme Transform Control Panel

What are the other options for?

Project moved
XDV has been renamed to Diazo, and this integration package has been replaced by plone.app.theming. Visit the Diazo website and the plone.app.theming PyPI page for further information.

This package offers a simple way to develop and deploy Plone themes using the XDV engine. If you are not familiar with XDV or rules-based theming, check out the XDV documentation.

Installation

collective.xdv depends on:

These will all be pulled in automatically if you are using zc.buildout and follow the installation instructions.

To install collective.xdv into your Plone instance, locate the file buildout.cfg in the root of your Plone instance directory on the file system, and open it in a text editor. Locate the section that looks like this:

# extends = http://dist.plone.org/release/3.3/versions.cfg
extends = versions.cfg
versions = versions

It may also have a URL in the "extends" section, similar to the commented-out first line, depending on whether you pull the Plone configuration from the network or locally.

To add collective.xdv to our setup, we need some slightly different versions of a couple of the packages, so we extend the base config with a version list from the good-py service, so change this part of the configuration so it looks like this:

extends =
    versions.cfg
    http://good-py.appspot.com/release/collective.xdv/1.0?plone=3.3.5
versions = versions

Note that the last part of the URL above before the ? is the xdv version number. There may be a newer version by the time you read this, so check out the overview page for the known good set.

Replace ?plone=3.3.5 with the version of Plone you are using. This dependency versions appropriate to your Plone.

What happens here is that the dependency list for collective.xdv specifies some new versions for you via the good-py URL. This way, you don't have to worry about getting the right versions, Buildout will handle it for you.

Next step is to add the actual collective.xdv add-on to the "eggs" section of buildout.cfg. Look for the section that looks like this:

eggs =
    Plone

This section might have additional lines if you have other add-ons already installed. Just add the collective.xdv on a separate line, like this:

eggs =
    Plone
    collective.xdv [Zope2.10]

Note the use of the [Zope2.10] extra, which brings in the ZPublisherEventsBackport package for forward compatibility with Zope 2.12 / Plone 4. If you are using Zope 2.12 or later (e.g. with Plone 4), you should do:

eggs =
    Plone
    collective.xdv

Note that there is no need to add a ZCML slug as collective.xdv uses z3c.autoinclude to configure itself automatically.

Once you have added these lines to your configuration file, it's time to run buildout, so the system can add and set up collective.xdv for you. Go to the command line, and from the root of your Plone instance (same directory as buildout.cfg is located in), run buildout like this:

$ bin/buildout

You will see output similar to this:

Getting distribution for 'collective.xdv==1.0'.
Got collective.xdv 1.0.
Getting distribution for 'plone.app.registry'.
Got plone.app.registry 1.0a1.
Getting distribution for 'plone.synchronize'.
Got plone.synchronize 1.0b1.
...

If everything went according to plan, you now have collective.xdv installed in your Zope instance.

Next, start up Zope, e.g with:

$ bin/instance fg

Then go to the "Add-ons" control panel in Plone as an administrator, and install the "XDV theme support" product. You should then notice a new "XDV Theme" control panel in Plone's site setup. 

Usage

In the "XDV Theme" control panel, you can set the following options:

Enabled yes/no
Whether or not the transform is enabled.
Domains
A list of domains (including ports) that will be matched against the HOST header to determine if the theme should be applied. Note that 127.0.0.1 is never styled, to ensure there's always a way back into Plone to change these very settings. However, 'localhost' should work just fine.
Theme
A file path or URL pointing to the theme file. This is just a static HTML file.
Rules
The filesystem path to the rules XML file.
Alternate themes
A list of definitions of alternate themes and rules files for a different path. Should be of the form 'path|theme|rules' where path may use a regular expression syntax, theme is a file path or URL to the theme template and rule is a file path to the rules file.
XSLT extension file
It is possible to extend XDV with a custom XSLT file. If you have such a file, give its URL here.
Absolute prefix
If given, any relative URL in an <img /><link /><style /> or <script /> in the theme HTML file will be prefixed by this URL snippet when the theme is compiled. This makes it easier to develop theme HTML/CSS on the file system using relative paths that still work on any URL on the server.
Unstyled paths
This is used to give a list of URL patterns (using regular expression syntax) for pages that will not be styled even if XDV is enabled. By default, this includes the 'emptypage' view that is necessary for the Kupu editor to work, and the manage_* pages that make up the ZMI.

Note that when Zope is in debug mode, the theme will be re-compiled on each request. In non-debug mode, it is compiled once on startup, and then only if the control panel values are changed. 

Resources in Python packages

When specifying the rules, theme and/or XSLT extension files, you should normally use a file path. If you are distributing your theme in a Python package that is installed using Distribute/setuptools (e.g. a standard Plone package installed via buildout), you can use the special python URL scheme to reference your files.

For example, if your package is called my.package and it contains a directory mytheme, you could reference the file rules.xml in that file as:

``python://my.package/mytheme/rules.xml``

This will be resolved to an absolute file:// URL by the collective.xdv.

Static Files and CSS

Typically, the theme will reference static resources such as images or stylesheets. It is usually a good idea to keep all of these in a single, top-level directory to minimise the risk of clashes with Plone content paths.

If you are using Zope/Plone standalone, you will need to make your static resources available through Zope, or serve them from a separate (sub-)domain. Here, you have a few options:

  • Create the static resources as File content objects through Plone.
  • Create the resources inside the portal_skins/custom folder in the ZMI.
  • Install the resources through a filesystem product.

The latter is most the appropriate option if you are distributing your theme as a Python package. In this case, you can register a resource directory in ZCML like so:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">

    ...

    <browser:resourceDirectory
        name="my.package"
        directory="mytheme"
        />

    ...

</configure>

The mytheme directory should be in the same directory as the configure.zcml file. You can now put your theme, rules and static resources here.

If you make sure that your theme uses only relative URLs to reference any stylesheets, JavaScript files, or images that it needs (including those referenced from stylesheets), you should now be able to view your static theme by going to a URL like:

http://localhost:8080/Plone/++resource++my.package/theme.html

You can now set the "Absolute prefix" configuration option to be '/++resource++my.package'. XDV will then turn those relative URLs into appropriate absolute URLs with this prefix.

If you have put Apache, nginx or IIS in front of Zope, you may want to serve the static resources from the web server directly instead. 

Using portal_css to manage your CSS

Plone's "resource registries", including the portal_css tool, can be used to manage CSS stylesheets. This offers several advantages over simply linking to your stylesheets in the template, such as:

  • Detailed control over the ordering of stylesheets
  • Merging of stylesheets to reduce the number of downloads required to render your page
  • On-the-fly stylesheet compression (e.g. whitespace removal)
  • The ability to include or exclude a stylesheet based on an expression

It is usually desirable (and sometimes completely necessary) to leave the theme file untouched, but you can still use portal_css to manage your stylesheets. The trick is to drop the theme's styles and then include all styles from Plone. For example, you could add the following rules:

<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

The use of an "or" expression for the content in the <append /> rule means that the precise ordering is maintained.

For an example of how to register stylesheets upon product installation using GenericSetup, see below. In short - use the cssregistry.xml import step in your GenericSetup profile directory.

There is one important caveat, however. Your stylesheet may include relative URL references of the following form:

background-image: url(../images/bg.jpg);

If your stylesheet lives in a resource directory (e.g. it is registered in portal_css with the id++resource++my.package/css/styles.css), this will work fine so long as the registry (and Zope) is in debug mode. The relative URL will be resolved by the browser to ++resource++my.package/images/bg.jpg.

However, you may find that the relative URL breaks when the registry is put into production mode. This is because resource merging also changes the URL of the stylesheet to be something like:

/plone-site/portal_css/Suburst+Theme/merged-cachekey-1234.css

To correct for this, you have a few options:

  1. Replace your static stylesheet with something dynamic so that you can calculate it relative an absolute path on the fly. This obviously will not work if you want to be able to view the theme standalone.
  2. Change your URLs to use an absolute path, e.g. /++resource++my.theme/images/bg.jpg. Again, this will break the original stylesheet. However, you can perhaps create a Plone-only override stylesheet that overrides each CSS property that uses a url().
  3. Avoid using portal_css for your static stylesheets.
  4. Use Plone 4. :-) In Plone 4 (b3 and later), the portal_css tool has an option to parse a stylesheet for relative URLs and apply an absolute prefix based on the stylesheet's debug-mode URL. The option is called applyPrefix in the cssregistry.xml syntax.

Controlling Plone's default CSS

It is sometimes useful to show some of Plone's CSS in the styled site. You can achieve this by using an XDV<append /> rule or similar to copy the CSS from Plone's generated <head /> into the theme. You can use the portal_css tool to turn off the style sheets you do not want.

However, if you also want the site to be usable in non-themed mode (e.g. on a separate URL), you may want to have a larger set of styles enabled when XDV is not used. To make this easier, you can use the following expressions as conditions in the portal_css tool (and portal_javascripts, portal_kss), in portal_actions, in page templates, and other places that use TAL expression syntax:

request/HTTP_X_XDV | nothing

This expression will return True if XDV is currently enabled, in which case an HTTP header "X-XDV" will be set. By default, this will check both the 'enabled' flag in the XDV control panel, and the current domain. If you later deploy the theme to a fronting web server such as nginx, you can set the same request header there to get the same effect, even if collective.xdv is uninstalled.

Use:

not: request/HTTP_X_XDV | nothing

to 'hide' a style sheet from the themed site.

A worked example

There are many ways to set up an XDV theme. For example, you could upload the theme and rules as content in Plone use absolute paths to configure them. You could also serve them from a separate static web server, or even load them from the filesystem.

To create a deployable theme, however, it is often best to create a simple Python package. This also provides a natural home for theme-related customisations such as template overrides.

Although a detailed tutorial is beyond the scope of this help file, a brief, worked example is shown below.

  1. Create a package and install it in your buildout:

    $ cd src
    $ paster create -t plone my.theme

See the buildout manual for details

If you have a recent ZopeSkel installed, this should work. Pick easy mode. Answer "yes" when asked if you want to register a profile.

Then edit buildout.cfg to add your new package (my.theme above) to the develop and eggs lists.

  1. Edit setup.py inside the newly created package

The install_requires list should be:

install_requires=[
      'setuptools',
      'collective.xdv',
  ],

Re-run buildout:

$ bin/buildout
  1. Edit configure.zcml inside the newly created package.

Add a resource directory inside the <configure /> tag. Note that you may need to add the browser namespace, as shown.

<configure

xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:i18n="http://namespaces.zope.org/i18n" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="my.theme">

<genericsetup:registerProfile
name="default" title="my.theme" directory="profiles/default" description="Installs the my.theme package" provides="Products.GenericSetup.interfaces.EXTENSION" />
<browser:resourceDirectory
name="my.theme" directory="static" />

</configure>

Here, we have used the package name, my.theme, for the resource directory name. Adjust as appropriate.

  1. Add a static directory next to configure.zcml.
  2. Put your theme and rules files into this directory.

For example, you may have a theme.html that references images in a sub-directory images/ and stylesheets in a sub-directory css/. Place this file and the two directories inside the newly created static directory.

Make sure the theme uses relative URLs (e.g. <img src="images/foo.jpg" />) to reference its resources. This means you can open theme up from the filesystem and view it in its splendour.

Also place a rules.xml file there. See the XDV documentation for details about its syntax. You can start with some very simple rules if you just want to test:

<?xml version="1.0" encoding="UTF-8"?>
<rules
    xmlns="http://namespaces.plone.org/xdv"
    xmlns:css="http://namespaces.plone.org/xdv+css">

    <!-- Head: title -->
    <replace theme="/html/head/title" content="/html/head/title" />

    <!-- Base tag -->
    <replace theme="/html/head/base" content="/html/head/base" />

    <!-- Drop styles in the head - these are added back by including them from Plone -->
    <drop theme="/html/head/link" />
    <drop theme="/html/head/style" />

    <!-- Pull in Plone CSS -->
    <append theme="/html/head" content="/html/head/link | /html/head/style " />

</rules>

These rules will pull in the <title /> tag (i.e. the browser window's title), the <base /> tag (necessary for certain Plone URLs to work correctly), and Plone's stylesheets.

See below for some more useful rules.

  1. Create the installation profile

The generated code above for the <genericsetup:registerProfile /> tag contains a reference to a directoryprofiles/default. You may need to create this next to configure.zcml if it doesn't exist already, i.e. create a new directory profiles and inside it another directory default.

In this directory, add a file called metadata.xml containing:

<metadata>
    <version>1</version>
    <dependencies>
        <dependency>profile-collective.xdv:default</dependency>
    </dependencies>
</metadata>

This will install collective.xdv into Plone when my.theme is installed via the add-on control panel later.

Also create a file called registry.xml, with the following contents:

<registry>

    <!-- collective.xdv settings -->

    <record interface="collective.xdv.interfaces.ITransformSettings" field="domains">
        <value>
            <element>domain.my:8080</element>
        </value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="rules">
        <value>python://my.theme/static/rules.xml</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="theme">
        <value>python://my.theme/static/theme.html</value>
    </record>

    <record interface="collective.xdv.interfaces.ITransformSettings" field="absolute_prefix">
        <value>/++resource++my.theme</value>
    </record>

</registry>

Replace my.theme with your own package name, and rules.xml and theme.html as appropriate.

This file configures the settings behind the XDV control panel.

Hint: If you have played with the control panel and want to export your settings, you can create a snapshot in the portal_setup tool in the ZMI. Examine the registry.xml file this creates, and pick out the records that relate to collective.xdv. You should strip out the <field /> tags in the export, so that you are left with <record />and <value /> tags as shown above.

Also, add a cssregistry.xml in the profiles/default directory to configure the portal_css tool:

<?xml version="1.0"?>
<object name="portal_css">

 <!-- Set conditions on stylesheets we don't want to pull in -->
 <stylesheet
     expression="not:request/HTTP_X_XDV | nothing"
     id="public.css"
     />

 <!-- Add new stylesheets -->
 <!-- Note: applyPrefix is not available in Plone < 4.0b3 -->

 <stylesheet title="" authenticated="False" cacheable="True"
    compression="safe" conditionalcomment="" cookable="True" enabled="on"
    expression="request/HTTP_X_XDV | nothing"
    id="++resource++my.theme/css/styles.css" media="" rel="stylesheet"
    rendering="link"
    applyPrefix="True"
    />

</object>

This shows how to set a condition on an existing stylesheet, as well as registering a brand new one. We've setapplyPrefix to True here, as explained above. This will only work in Plone 4.b3 and later. For earlier versions, simply take this out.

  1. Test

Start up Zope and go to your Plone site. Your new package should show as installable in the add-on product control panel. When installed, it should install collective.xdv as a dependency and pre-configure it to use your theme and rule set. By default, the theme is not enabled, so you will need to go to the control panel to switch it on.

You can now compare your untouched theme, the unstyled Plone site, and the themed site by using the following URLs:

  • http://localhost:8080 (or whatever you have configured as the styled domain) for a styled Plone. If you used the sample rule above, this will look almost exactly like your theme, but with the <title /> tag (normally shown in the title bar of your web browser) taken from Plone.
  • http://127.0.0.1:8080 (presuming this is the port where Plone is running) for an unstyled Plone.
  • http://localhost:8080/++resource++my.theme/theme.html for the pristine theme. This is served as a static resource, almost as if it is being opened on the filesystem.

Common rules

To copy the page title:

<!-- Head: title -->
<replace theme="/html/head/title" content="/html/head/title" />

To copy the <base /> tag (necessary for Plone's links to work):

<!-- Base tag -->
<replace theme="/html/head/base" content="/html/head/base" />

To drop all styles and JavaScript resources from the theme and copy them from Plone's portal_css tool instead:

<!-- Drop styles in the head - these are added back by including them from Plone -->
<drop theme="/html/head/link" />
<drop theme="/html/head/style" />

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/link | /html/head/style" />

To copy Plone's JavaScript resources:

<!-- Pull in Plone CSS -->
<append theme="/html/head" content="/html/head/script" />

To copy the class of the <body /> tag (necessary for certain Plone JavaScript functions and styles to work properly):

<!-- Body -->
<prepend theme="/html/body" content="/html/body/attribute::class" />

Other tips

  • Firebug is an excellent tool for inspecting the theme and content when building rules. It even has an XPath extractor.
  • Read up on XPath. It's not as complex as it looks and very powerful.
  • Run Zope in debug mode whilst developing so that you don't need to restart to see changes to theme, rules or, resources.

Changelog

1.0rc9 - 2010-08-05

  • Use an IBeforeTraverseEvent on the Plone site root instead of an IPubAfterTraversal event to hook in the X-XDV request header. This makes the header work on 404 error pages. [optilude]
  • Add collective.directoryresourcepatch to the Zope2.10 extras. This allows for subdirectories to be traversed by the ResourceRegistries while running Plone 3/Zope 2.10. [dunlapm]
  • Require lxml>=2.2.4. The Zope2 KGS lists lxml=2.2, a version which errors on invalid html. [elro]
  • Fix extra.xsl support. [elro]

1.0rc8 - 2010-05-24

1.0rc7 - 2010-05-23

  • UPGRADE NOTE: Reinstall product in the Add-ons control panel.
  • Switch on XInclude processing always. [elro]
  • Fix Windows install. For running under Plone 4 on Windows, you must specify:
    [versions]
    lxml = 2.2.4
    until a newer lxml Windows binary egg is released. [elro]
  • Instead of the external resolver, let lxml read the network. You must now explicitly enable Read network in the control panel. [elro]

1.0rc6 - 2010-05-21

  • Fix transform caching to account for different virtual hosts of the same site and make cache invalidation work across ZEO clients. [elro]

1.0rc5 - 2010-04-21

  • Fix in-Plone content inclusion via the href mechanism, including the use of relative paths in hrefs. [optilude]
  • Ensured that the absolute prefix would work even in a virtual hosting scenario where the aboslute path of the site root is '/'. [optilude]
  • Added an event handler which will set an HTTP request header 'X-XDV' if XDV is enabled for the incoming domain. This can be used as a check in e.g. portal_css, for example with a TALES expression like 'request/HTTP_X_XDV | nothing'. The @@xdv-check/enabled method now just checks for the existence of this variable too. The idea is that it is easier to replicate this in a pure-XSLT deployment scenario with collective.xdv disabled, for example by setting the same request header in nginx or Apache. [optilude]
  • Made all zope paths resolve relative to the Plone site. [marshalium]
  • Add support for resolving files with http/ftp absolute urls and zope paths. [marshalium]
  • Make absolute_prefix prepend the Plone site path if necessary. This means that an absolute prefix starting with / is always relative to the Plone site root. [optilude]
  • Add support for the python:// pseudo-scheme for the theme, rules and extrauri files. See README.txt for details. [optilude]
  • Improve the wording in the control panel [optilude]
  • Fix a bug whereby the cached transforms (in non-debug-mode) would leak across Plone sites in the same instance. [optilude]
  • Remove the boilerplate parameter. Use extraurl instead. [optilude]
  • Let collective.xdv depend on the new XDV egg, instead of dv.xdvserver. [optilude]
  • Only invoke the transformation if collective.xdv is in fact installed. Note: you may need to re-install the product after upgrading. [optilude]
  • Use plone.transformchain to sequence transformation activities. Among other things, this helps us avoid re-parsing/serialising lxml trees when other things in the chain prefer to work with such representations of the response. It also helps control the sequence of post-publication events. [optilude]
  • Zope 2.12 / Plone 4 compatability. [lrowe]

1.0rc4 - 2009-10-27

  • Style error responses as well as successful responses. [lrowe]
  • Use ZPublisher events instead of plone.postpublicationhook for compatibility with Zope 2.12 / Plone 4. For Zope2.10 / Plone 3.x, you must now specify "collective.xdv [Zope2.10]" in your buildout to bring in the package ZPublisherEventsBackport. [lrowe]
  • Added support for extraurl parameter [mhora]
  • Added alternate themes and modified transform so it can decide by a path regular expression which theme and rules files it will use for transformation [mhora]
  • Add /manage in unstyled paths default list. [encolpe]

1.0a2 - 2009-07-12

  • Catch up with changes in plone.registry's API. [optilude]

1.0a1 - 2009-04-17

  • Initial release

6.1. XSLT extension file

This is used to extend the boilerplate XSLT file used to create your theme transformation from your rules and template files.

In his advanced XDV tutorial, Denys Mishunov gives a good example of how to extend the template to override the HTML Doctype for your theme:

http://plone.org/documentation/kb/advanced-xdv-theming/tips-tricks-2

6.2. Alternate themes

You can specify alternative rules and theme template files for different parts of the site.

The following will style up a news folder at the root of your site

^/news(/.*)?$ | [path to theme file] | [path to rules file]

The following will style up any news folder at any point in your site

^.*/news(/.*)?$ | [path to theme file] | [path to rules file]

6.3. Absolute URL prefix

The absolute URL prefix will convert any relative URL in your theme template.

An example of how to use this is described in the Static Resources section of this manual. You can use this to vary a theme by pointing to different locations containing alternative versions of your images (or CSS, if you are not using Plone and the CSS registry to deliver these).

7. Wrapping up

You now have a new way to make themes for Plone, where do you go from here?

The best way to learn is by doing — so your next step should be to take one of your existing designs, and map Plone into it.

If you don't have any themes yourself, a great site for free designs is OSWD.org (Open Source Web Design). Here are some great themes you can download to get you started:

Remember that if any of these themes are missing IDs for easy mapping in the rule file — just add them in the HTML.

Have fun with your new-found theming superpowers!

8. Revision history

Changes to this document over time.

v1 — June 2009
Initial version, this is also the version printed in Veda Williams' "Theming Plone 3" book from Packt Publishing.
v2 - February 2010
Expanded to Manual format, with separate sections for installation, rules etc, and moved to the collective.xdv product space.