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>