Advanced usage

For most themes, the basic rules will suffice. There are times when you need a little more power, however, for example when working with a complex design or a content source that does not have well-defined, semantic markup.

Conditional rules

Sometimes, it is useful to apply a rule only if a given element appears or does not appear in the markup. The if, if-content and if-path attributes can be used with any rule, as well as the <theme /> and <notheme /> directives.

Conditions based on content nodes

if-content should be set to an XPath expression. You can also use css:if-content with a CSS3 expression. If the expression matches a node in the content, the rule will be applied:

<replace css:theme-children="#portlets" css:content=".portlet"/>
<drop css:theme="#portlet-wrapper" css:if-content="#content.wide"/>

This will copy all elements with class portlet into the portlets element. If there are no matching elements in the content we drop the portlet-wrapper element, which is presumably superfluous.

Here is another example using CSS selectors:

<replace css:theme-children="#header" css:content-children="#header-box"
      css:if-content="#personal-bar"/>

This will copy the children of the element with id header-box in the content into the element with id header in the theme, so long as an element with id personal-bar also appears somewhere in the content.

An empty if-content (or css:if-content) is a shortcut meaning "use the expression in the content or css:content` attribute as the condition". Hence the following two rules are equivalent:

<replace css:theme-children="#header" css:content="#header-box"
      css:if-content="#header-box"/>
<copy css:theme-children="#header" css:content="#header-box"
      css:if-content=""/>

If multiple rules of the same type match the same theme node but have different if-content expressions, they will be combined as an if..else if...else block:

<replace theme-children="/html/body/h1" content="/html/body/h1/text()"
      if-content="/html/body/h1"/>
<replace theme-children="/html/body/h1" content="//h1[@id='first-heading']/text()"
      if-content="//h1[@id='first-heading']"/>
<replace theme-children="/html/body/h1" content="/html/head/title/text()" />

These rules all attempt to fill the text in the <h1 /> inside the body. The first rule looks for a similar <h1 /> tag and uses its text. If that doesn't match, the second rule looks for any <h1 /> with id first-heading, and uses its text. If that doesn't match either, the final rule will be used as a fallback (since it has no if-content), taking the contents of the <title /> tag in the head of the content document.

A content condition may be negated with if-not-content or css:if-not-content, for example:

<drop css:theme="#portlet-wrapper" css:if-not-content=".portlet"/>

Conditions based on paths

Provided the live transform is correctly configured to pass the relevant parameter (the $path parameter), it is possible to create conditions based on URL path segments in the incoming request. This uses the if-path attribute.

A leading / indicates that a path should be matched at the start of the url:

<drop css:theme="#info-box" if-path="/news"/>

matches pages with urls /news, /news/ and /news/page1.html but not /newspapers - only complete path segments are matched.

A trailing / indicates that a path should be matched at the end of the url:

<drop css:theme="#info-box" if-path="news/"/>

matches /mysite/news and /mysite/news/.

To match an exact url, use both leading and trailing /:

<drop css:theme="#info-box" if-path="/news/"/>

matches /news and /news/.

Without a leading or trailing / the path segment(s) may match anywhere in the url:

<drop css:theme="#info-box" if-path="news/space"/>

matches /mysite/news/space/page1.html.

Multiple alternative path conditions may be included in the if-path attribute as whitespace separated list:

<drop css:theme="#info-box" if-path="/ /index.html/"/>

matches / and /index.html. if-path="/" is considered an exact match condition

A path condition may be negated with if-not-path, for example:

<drop css:theme="#info-box" if-not-path="/news"/>

Conditions based on arbitrary parameters

The if attribute can be used to make a rule or theme conditional on any valid XPath expression.

For example, if the transform is set up to receive a string parameter $mode, you could write:

<drop css:theme=".test-site-warning" if="$mode = 'live'" />

Use the if-not attribute to negate the conditon, for example:

<drop css:theme=".test-site-warning" if-not="$mode = 'live'" />

Condition grouping and nesting

A condition may be applied to multiple rules by placing it on a <rules> tag:

<rules
    xmlns="http://namespaces.plone.org/diazo"
    xmlns:css="http://namespaces.plone.org/diazo/css"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <rules css:if-content="#personal-bar">
        <after css:theme-children="#header-box" css:content="#user-prefs"/>
        <after css:theme-children="#header-box" css:content="#logout"/>
    </rules>

    ...

</rules>

Conditions may also be nested, so:

<rules if="condition1">
    <rules if="condition2">
        <copy if="condition3" css:theme="#a" css:content="#b"/>
    </rules>
</rules>

Is equivalent to:

<copy if="(condition1) and (condition2) and (condition3)" css:theme="#a" css:content="#b"/>

Multiple, conditional themes

It's possible to specify multiple themes using conditions. For instance:

<theme href="theme.html"/>
<theme href="news.html" css:if-content="body.section-news"/>
<theme href="members.html" css:if-content="body.section-members"/>

Potential themes are tested in the order specified. The first one to match is used.

The unconditional theme is used as a fallback when no other theme's condition is satisfied. If no unconditional theme is specified, the document is passed through without theming.

It is also possible to conditionally disable theming, using <notheme />:

<theme href="theme.html"/>
<notheme if-path="/assets" />

The theme is disabled if there is a matching <notheme />, regardless of any conditional <theme /> directives.

All rules are applied to all themes. To have a rule apply to only a single theme, use the condition grouping syntax:

<rules css:if-content="body.section-news">
    <theme href="news.html"/>
    <copy css:content="h2.articleheading" css:theme="h1"/>
</rules>

Modifying the theme on the fly

Sometimes, the theme is almost perfect, but cannot be modified, for example because it is being served from a remote location that you do not have access to, or because it is shared with other applications.

Diazo allows you to modify the theme using "inline" markup in the rules file. You can think of this as a rule where the matched content is explicitly stated in the rules file, rather than pulled from the response being styled.

For example:

<after theme-children="/html/head">
    <style type="text/css">
        /* From the rules */
        body > h1 { color: red; }
    </style>
</after>

In the example above, the <after /> rule will copy the <style /> attribute and its contents into the <head /> of the theme. Similar rules can be constructed for <before /> and <replace />.

It is even possible to insert XSLT instructions into the compiled theme in this manner:

<replace css:theme="#details">
    <dl id="details">
        <xsl:for-each css:select="table#details > tr">
            <dt><xsl:copy-of select="td[1]/text()"/></dt>
            <dd><xsl:copy-of select="td[2]/node()"/></dd>
        </xsl:for-each>
    </dl>
</replace>

Here, the XSL context is the root node of the content.

Notice how we used css:select to select a node to operate on in the <xsl:for-each /> directive. In fact, you can use the css: namespace for anything that specifies an XPath expression, and the Diazo pre-processor will turn it into the equivalent XPath for you.

Inline markup and XSLT may be combined with conditions:

<before css:theme"#content-wrapper" css:if-content="body.blog-page">
    <div class="notice">Welcome to our new blog</div>
</before>

Modifying the content on the fly

It is possible to modify the included content using <replace />, <before />, or <after />.

For example:

<replace css:content="div#portal-searchbox input.searchButton">
    <button type="submit">
        <img src="images/search.png" alt="Search" />
    </button>
</replace>

<before css:content="#content-core">
    <a href="mailto:contact@diazo.org">Ask for help</a>
</before>

The content can be inline HTML or it can be a piece of content from the document itself retrieved using the <include /> tag. For instance:

<before css:content-children="#main">
    <include css:content="#breadcrumbs" />
</before>

The <include /> tag accepts a href attribute, so it can retrieve a piece of content from another page. For instance:

<after css:content="#main">
    <include css:content="form" href="contact.html" />
</after>

This may also be combined with conditions and inline XSLT.

Warning: it is not possible to both modify the content children and put them in the theme, for instance:

<before css:content-children="#one">
    <span>Uno</span>
</before>

<before
    css:theme="#alpha"
    css:content-children="#one"
    />

would not work. But:

<before css:content-children="#one">
    <span>Uno</span>
</before>

<before
    css:theme="#alpha"
    css:content="#one"
    />

would work (because the theme rule targets the #one content, not its children).

Inline XSL directives

You may supply inline XSL directives in the rules to tweak the final output. For instance to strip space from the output document use:

<xsl:strip-space elements="*" />

(Note: this may effect the rendering of the page on the browser.)

Inline XSL directives must be placed directly inside the root <rules> tag and are applied unconditionally.

Doctypes

By default, Diazo transforms output pages with the XHTML 1.0 Transitional doctype. To use a strict doctype include this inline XSL:

<xsl:output
    doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
    doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"/>

It's important to note that only the XHTML 1.0 Strict and XHTML 1.0 Transitional doctypes trigger the special XHTML compatibility mode of libxml2's XML serializer. This ensures <br/> is rendered as <br /> and <div/> as <div></div>, which is necessary for browsers to correctly parse the document as HTML.

It's not possible to set the HTML5 doctype from XSLT, so plone.app.theming and the included WSGI middleware include a doctype option which may be set to "<!DOCTYPE html>".

XInclude

You may wish to re-use elements of your rules file across multiple themes. This is particularly useful if you have multiple variations on the same theme used to style different pages on a particular website.

Rules files may be included using the XInclude protocol.

Inclusions use standard XInclude syntax. For example:

<rules
    xmlns="http://namespaces.plone.org/diazo"
    xmlns:css="http://namespaces.plone.org/diazo/css"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xi="http://www.w3.org/2001/XInclude">

    <xi:include href="standard-rules.xml" />

</rules>

Including external content

Normally, the content attribute of any rule selects nodes from the response being returned by the underlying dynamic web server. However, it is possible to include content from a different URL using the href attribute on any rule (other than <drop />). For example:

<after css:theme-content="#left-column" css:content="#portlet" href="/extra.html"/>

This will resolve the URL /extra.html, look for an element with id portlet and then append to to the element with id left-column in the theme.

The inclusion can happen in one of three ways:

Using the XSLT document() function.

This is the default, but it can be explicitly specified by adding an attribute method="document" to the rule element. Whether this is able to resolve the URL depends on how and where the compiled XSLT is being executed:

<after css:theme-children="#left-column" css:content="#portlet"
        href="/extra.html" method="document" />

Using a Server Side Include directive

This can be specified by setting the method attribute to ssi:

<after css:theme-children="#left-column" css:content="#portlet"
        href="/extra.html" method="ssi"/>

The output will render like this:

<!--#include virtual="/extra.html?;filter_xpath=descendant-or-self::*[@id%20=%20'portlet']"-->

This SSI instruction would need to be processed by a fronting web server such as Apache or Nginx. Also note the ;filter_xpath query string parameter. Since we are deferring resolution of the referenced document until SSI processing takes place (i.e. after the compiled Diazo XSLT transform has executed), we need to ask the SSI processor to filter out elements in the included file that we are not interested in. This requires specific configuration. An example for Nginx is included below.

For simple SSI includes of a whole document, you may omit the content selector from the rule:

<append css:theme="#left-column" href="/extra.html" method="ssi"/>

The output then renders like this:

<!--#include virtual="/extra.html"-->

Some versions of Nginx have required the wait="yes" ssi option to be stable. This can be specified by setting the method attribute to ssiwait.

Using an Edge Side Includes directive

This can be specified by setting the method attribute to esi:

<after css:theme-content="#left-column" css:content="#portlet"
        href="/extra.html" method="esi"/>

The output is similar to that for the SSI mode:

<esi:include src="/extra.html?;filter_xpath=descendant-or-self::*[@id%20=%20'portlet']"></esi:include>

Again, the directive would need to be processed by a fronting server, such as Varnish. Chances are an ESI-aware cache server would not support arbitrary XPath filtering. If the referenced file is served by a dynamic web server, it may be able to inspect the ;filter_xpath parameter and return a tailored response. Otherwise, if a server that can be made aware of this is placed in-between the cache server and the underlying web server, that server can perform the necessary filtering.

For simple ESI includes of a whole document, you may omit the content selector from the rule:

<append css:theme="#left-column" href="/extra.html" method="esi"/>

The output then renders like this:

<esi:include src="/extra.html"></esi:include>