Rules and Media

CSS Within introduces css:rule and css:media elements into XSLT stylesheets. We will begin by looking at css:rule since it is the fundamental building block both of CSS Within and, indirectly, of CSS itself.

Consider an XSLT template as follows:

<xsl:template match="products-found">
    <!--* match search results, if any *-->
    <div class="productlist">
        <xsl:on-non-empty>
            <h3>Products</h3>
        </xsl:on-non-empty>
        <xsl:apply-templates/>
        <xsl:on-empty>
            <p class="no-products">
               No products matched your irritating query. Go away.
            </p>
        </xsl:on-empty>
    <div>
</xsl:template>

Styling the Products heading might require CSS like the following:

div.productlist {
    border: 1px solid grey;
    padding: 1rem;
}
div.productlist>h3 {
    font-family: "Bland Sans", sans;
    border-top: 1px dotted grey;
}

This CSS fragment consists of two rules, each consisting of a selector followed by a group of rules contained within { curly brackets }. The first rule has a selector that says the rule applies to any divelement whose class attribute contains the token productlist. The second rule has a selector that applies to every h3 element whose immediate parent is such a div element. Assuming no other more specific or subsequent rule overrides these, the rules assign values to various CSS properties such as padding and font-family. Beyond this, the details of CSS are not important to CSS Within. However, it is worth noting that CSS uses a text-based non-XML syntax with which people who work with Web development are very familiar.

It is clear from this example that the link between the stylesheets and the HTML being constructed is fragile: if the generated element structure is changed, or the class names are updated, the original CSS selectors will no longer match. One way to mitigate this is to put the CSS rules right next to the place where the elements they govern are generated. To put the CSS within the template, we might combine them as follows:

<xsl:template match="products-found">
    <!--* match search results, if any *-->
    <div class="productlist">
        <css:rule match="div.productlist">
            border: 1px solid grey;
            padding: 1rem;
        </css:rule>
        <xsl:on-non-empty>
            <css:rule match="div.productlist>h3">
                font-family: "Bland Sans", sans;
                border-top: 1px dotted grey;
            </css:rule>
            <h3>Products</h3>
        </xsl:on-non-empty>
        <xsl:apply-templates/>
        <xsl:on-empty>
            <p class="no-products">
               No products matched your irritating query. Go away.
            </p>
        </xsl:on-empty>
    <div>
</xsl:template>

The CSS Within tool set includes XSLT that will read the stylesheet itself, extract all of the css:rule elements and write a CSS stylesheet file. In this case the CSS will be identical to that shown above.

What have we gained? First, we no longer have a conflict of syntax: there are no more curly braces and everything is in XML. Second, the CSS definitions are right next to the elements they style. It is easy to imagine changing the h3 to an h4 but forgetting to change the CSS file; even if we remember, we then have to open the CCSS file and find the right rule to change. But now the styles and the markup are in the same place we are likely to remember and, remembering, will of course find it easy to locate the style rule to update.

Sometimes you may generate the same structure from multiple places in your stylesheet, but of course you don't want to repeat the CSS rules in the generated stylesheet. In this case you can use an empty css:rule element and give it a ref attribute whose value matches the name attribute of another css:rule element. The name attribute on the css:rule element also serves as a reminder that the style might be used elsewhere, helping to avoid the situation where you accidentally delete a rule you thought you no longer needed.

If you are using CSS for both print and screen, or if you have stylesheets loaded only conditionally based on a media query (for example, containing extra rules for wide screens or overriding defaults set for circular displays such as on some wristwatches), you may well need to write out more than one CSS file with XSLT. In this case you can give css:rule elements a stream attribute, and the contents will only be included in the CSS ruleset you name. That way all the styles to do with a given output element are together and as easy as possible to update together, even if they are written out separately.

The way the CSS is written to files is described in a subsequent section in this paper.

If you have media queries in your stylesheet, you can use css:media elements to generate them; these elements contain css:rule elements:

<css:media when="min-width: 600px">
    <css:rule match="ul.letterindex">
        column-count: 2;
    </css:rule>
</css:media>
<css:media when="min-width: 800px">
    <css:rule match="ul.letterindex">
        column-count: 3;
    </css:rule>
</css:media>