How to cite this paper

Quin, Liam. “CSS Within: An application of the principle of locality of reference to CSS and XSLT.” Presented at Balisage: The Markup Conference 2021, Washington, DC, August 2 - 6, 2021. In Proceedings of Balisage: The Markup Conference 2021. Balisage Series on Markup Technologies, vol. 26 (2021). https://doi.org/10.4242/BalisageVol26.Quin01.

Balisage: The Markup Conference 2021
August 2 - 6, 2021

Balisage Paper: CSS Within: An application of the principle of locality of reference to CSS and XSLT

Liam Quin

Liam Quin runs an information design and XML consulting company, Delightful Computing, and previously was XML Activity Lead at the World Wide Web Consortium; before that he was involved in the creation of XML itself and in SGML, most notably at SoftQuad Inc. in Toronto. His backgrounds are in digital typography and computer science.

Copyright © Liam Quin 2021

Abstract

An important principle of writing, and of programming in particular, is that one should be able to understand any particular passage without having to look elsewhere. Of course, there may be concepts that one needs, but in literature having to consult a dictionary several times in every sentence is tedious; in software engineering, having to read function definitions before understanding the code that calls them can be dangerous.

This paper describes experiments with CSS Within (a method of embedding CSS style rules into XSLT transformations) and discusses how the proximity of the rules to the corresponding element generation affects maintenance.

Table of Contents

Introduction
Locality of Reference in Software
Literate Programming
Embedded Documentation
Docstrings
Sad and Dismal Failures
CSS Within
CSS Four Ways
CSS Within in More Detail
Writing Out CSS Styles
Alternate Designs
Future Work
CSS Within In Practice; An Unwarranted Conclusion
Appendix A. Implementing “CSS Within” With XSLT
Appendix B. A Slightly Longer Example

Introduction

In computer hardware architecture, the principle of Locality of Reference (attributed to Peter Denning) is that the processor tends to access the same memory locations, and others nearby, repeatedly over any given period of time. In an article revisiting the discovery of this principle, Peter Denning wrote [Denning 2006],

The locality principle flows from human cognitive and coordinative behavior. The mind focuses on a small part of the sensory field and can work most quickly on the objects of its attention. People gather the most useful objects close around them to minimize the time and work of using them. These behaviors are transferred into the computational systems we design.

The locality principle will be useful wherever there is an advantage in reducing the apparent distance from a process to the objects it accesses

This paper explores an application of the principle of Locality of Reference to computer programs and scripts, and in particular to XSLT templates and functions. It discusses some ways this principle has been applied with various degrees of success in software engineering; the paper then describes one particular application, that of generating both HTML and corresponding CSS in parallel using XSLT. After describing experience with this parallel generation the author will be in a position to describe why they consider it to be a success.

Locality of Reference in Software

An early problem in software, and one that continues to be a problem today, is the task of ensuring that documentation for a library is up to date with respect to the code. If you are using a library of programming functions (for example, the EXPath File module in XPath and XSLT), you rely on the documentation to tell you what arguments you must supply to each function, to list the error conditions, and to explain what the function returns. If the documentation says, file:open takes a filename as its argument and returns a sequence of lines as individual strings but in fact it takes a URI Reference (informally, a URL) and returns a single, possibly empty, string, your program may seem to be working correctly until you have a Microsoft Windows filename with \ in it, or until you supply an input file containing more than one line of text. The problem, then, is this: How can such discrepancies between code and documentation arise? And how can they be avoided?

In the early days of computing, program libraries were documented in printed books; the first of these was produced in 1951 in Cambridge Wilkes, Wheeler, Gill, 1951. This first book, like many after it, was typewritten, not prepared by computer. Later on, the Unix programmer’s manual was, unusually for the time, prepared on the same system it described: a PDP-11 computer running Unix. By the time the Seventh Edition of the manual was produced, it was a mixture of technical reports andman pages, with each library function having its own manual page, produced from a separate file using the troff typesetting software.

Producing the documentation for a library separately from the code implementing the library meant there was a duplication of information: the function signatures, listing the function names, parameters, and return types, were kept both in the software itself and in the external documentation. This created the possibility that the documentation and code could differ.

Literate Programming

In the 1970s and early 1980s the mathematician and computer scientist Donald Knuth saw this problem and designed a system in which the program and its documentation, both internal and external, could be interwoven. In effect the programmer was expected to write a text-book or extended essay about a problem being addressed by software, and to incorporate the text of the program into that work.

It may be useful to bear in mind that a great many people (although not all) have gone into computer science having barely ever written an essay, and certainly not enjoying the process. The author of this paper had to write exactly one essay as a computer science undergraduate, and it was for an elective course outside the computer science department. Turning the problem of writing a code library and some documentation into the task of writing a book is not universally seen as a simplification, nor as an inviting change, by such programmers. Perhaps this is part of why Dr. Knuth’s literate programming has not caught on en masse.

In the case that the requirements for a program seem stable, however, and the program will change only slowly over a long time (decades, perhaps), Knuth’s literate programming is an unparalleled tour de force.

A more serious problem with literate programming is that the methodology assumes a software life-cycle in which programs are designed and then remain largely static: they do not undergo significant reorganization. After all, if writing an essay is unappealing, rewriting the essay must be even less appealing. Since the program is interleaved with the documentation, refactoring means reworking the documentation even before the software design has become stable again. But any barrier to refactoring, however small, may lead to programs being rewritten from scratch, with new sets of bugs, rather than being reorganized.

Embedded Documentation

While Knuth was building complex cathedrals, others were kneeling in animal-hides and worshipping at hedge-altars. The author of this paper devised a system in the 1980s for personal use in the C programming language in which each function was preceded by a fragment of SGML describing it, itself contained in a comment so that the library would compile:

Figure 1

/* <Function>
 *   <Name>NXU_fReadLine
 *   <Class>Utilities/Files
 *   <Purpose>
 *      <P>Reads the next input line from the given file into a static buffer.
 *      The buffer is allocated with malloc and resized dynamically, but
 *      is owned by NXU_fReadLine and should not be free'd or overwritten.</P>
 *      <P>The NXU_StealReadLineBuffer function can be used to obtain the
 *      buffer; NXU_fReadLine will allocate a new one the next time it
 *      is called.</P>
 *      <P>The given Flags are treated as for
 *      NXU_fReadFile, which currently calls this routine directly.
 *      Note that, as for NXU_fReadFile, blank lines are skipped if the
 *      corresponding flag is given.  In this case, NXU_fReadLine will never
 *      return a pointer to a blank line, but will continue reading lines
 *      from the file until a non-blank one is found.</P>
 *   <Returns>
 *      A pointer to the line, in Line, and also the number of bytes in
 *      the line; -1 is returned on EOF, in which case the Line pointer should
 *      not be used.
 * </Function>
 */
API int
LQU_fReadLine(f, Linep, Flags)
    FILE *f;
    char **Linep;
    int Flags;
{
. . .
}

The approach of embedding the documentation in the program seems a much better fit with the relationship between a programmer and extended written prose than the reverse, embedding the program in the documentation. More importantly, where literate programming seemingly throws up barriers to refactoring and reorganizing code, embedded documentation does not. It is no surprise that similar systems are now widespread, from systems like doxygen for C or C++ to javadoc for Java.

Note that the early documentation method shown here mentions other functions by name, but there was no automated check that they had not been renamed.

Consider a programmer who, at some late hour, discovers the source of a bug. They make a correction to the code; they recompile, they run tests; they smile; they go to sleep. But in examining the code they did not need to look outside the function body they were repairing. As a result, it’s easy for necessary changes to the documentation to be overlooked. This is sure to be a factor why in JavaDoc, doxygen and other documentation systems (as well as the SGML-based system shown above) the documentation does often stray from the code. However, this style of documentation considerably reduces the problems of entirely separate program documentation, such as Unix manual page, that might go for decades and many major software revisions of the program described, without being updated.

Docstrings

The maintainers of the Python language were able to build on the work of others; both on systems such as doxygen or javadoc and on a much simpler system in some versions of the emacs editor. In this latter system, if the first item in a function body was a literal string it was taken to be a short (one-line) description of the purpose of the function.

Python uses a similar method with its docstrings: a single string placed after the start of the function serves as minimal documentation:

def schema_validate(xml, xsd):
    """Perform W3C XML Schema validation"""
    print("Validation failed: duplicate element found.")

Notice how the documentation, although much less complete in this example, appears after the start of the function. Since anyone working on a function is very likely to need to look at the function signature and parameter names, they are going to be reminded to update the documentation. It is this reminder that gives a clear example of the principle of locality of reference: because the documentation is in view of the programmer changing the code, it is easy to keep up to date. Such a simple change turns out to make a large difference in practice Unsub, 2021.

It should be noted that Python docstrings can be many lines long and for a class are expected to document all public methods.

Sad and Dismal Failures

Project failures have been attributed to programmers assuming they understood what a function did when it had perhaps a misleading name. This should, perhaps, be no surprise, if people become programmers because they dislike menial repetitive tasks and want the computers to do them instead.

If the principle of locality of reference says that we tend to look nearby for things we need, it follows that we should write code that can be understood as much as possible without having to look elsewhere. A well-known source of software defect comes from calling a function incorrectly, for example with an argument expressed in feet instead of metres. Another example includes languages such as Pascal and C++ in which function parameters can be passed implicitly by reference, so that f(x) can result in a change to the value of a local variable x even where x is not a pointer or reference.

Early programs had to use short identifiers because of memory limitations. C programs, for example, could use variables of any reasonable length but only the first six characters were significant for global symbols shared between files. This is why Unix used creat() and mknod() (the loader prepends an underscore to globals, using all six characters). This gave rise to a culture of using short variable names and function names. The author of this paper was astonished to encounter, in the 1980s, a program with function names such as addToTableOfContents(item, pageNumber) but then saw the advantage: you could read code that called addToTableOfContents without having to look at its definition to guess what it did. The mixed case function name would be treated as AddToT on systems that still had the six-character limit, a prefix slightly more likely to be unique than add_to.

This property of being able to read a fragment of a program and understand it without needing to read the definitions of all the functions that it calls is another example of the principle of locality of reference. That is, where the previous examples have featured parallel information such as documentation that must be updated at the same time as code, this example features names that we must understand, ideally without having to leave the neighbourhood.

CSS Within

This section introduces a method of using CSS that is informed by the principle of locality. One use of XSLT is to transform a document represented in XML into two groups of files: one group, in HTML, intended as inputs to a Web browser or to a formatter to make PDF; the other group, consisting of images, CSS and JavaScript, to be referenced by the HTML files in the first group.

A problem arises that is analogous to that of software documentation: the CSS and JavaScript must be kept up to date with any changes in the HTML structure, and changes to the HTML structure may necessitate in turn changes to the CSS.

It might seem that this should be trivial to manage, but a CSS file as short as only six or seven thousand lines is already larger than the Version Six Unix kernel source. Worse, the cascading nature of CSS means that multiple CSS rules might apply to any given element, and might appear anywhere in the CSS stylesheet.

CSS Within is an attempt to apply the principle of locality of reference to the problem of keeping CSS and HTML and XST all synchronized.

CSS Four Ways

When the author of this paper was first faced with the problem of generating a static HTML Web site with CSS styling, they simply made an external CSS file. Unfortunately as the XSLT was modified independently of the CSS, the CSS gradually grew longer and is no longer feasible to maintain at all.

The next approach was to generate a CSS file from within a single XSLT make-css template. This at least meant that when editing a template, the corresponding CSS selector could often be found quickly. It was an improvement but not a solution, though ,as it was easy to forget to update one or another. Removing an element constructor in CSS might or might not remove the need for a particular group of CSS style rules – and those rules might or might not be near one another in the CSS, again violating our locality of reference principle.

The second approach was to use non-XSLT elements. It turns out, as many readers will already know, that you can include elements from non-XSL namespaces at the top level in XSLT stylesheets. So it was possible to have, for example, a css:styles element just before each template. In practice there were some difficulties with doing this. The first was remembering to look before the start of the template. Another difficulty (shared with other approaches already mentioned) was that curly braces in text nodes have become special in XSLT 3: if the expand-text attribute is set to yes on the stylesheet element, then you need to turn it off again for each such CSS element so the curly braces do not introduce embedded XPath expressions. A third difficulty was that tie granularity did not match the problem: CSS styles are applied to individual elements in their context, but an XSLT template might generate many different elements.

Applying the principle that we need to have everything relevant to hand, then, CSS Within was born. Consider the following fragment taken from inside a template in a production XSLT stylesheet:

<xsl:template name="make-breadcrumb-links">
   <div class="index" role="navigation">
    <css:rule match="div.index">
      padding: 1rem;
      margin: 0;
      max-width: 20rem;
    </css:rule>
    <ul  class="breadcrumb">
      <css:rule  match="ul.breadcrumb">
         list-style: none; /* turn off  the bullets */
      </css:rule>
      . . .
   </ul>
</xsl:template> 

In the listing, the direct element constructor creating the HTML div element is immediately followed by a CSS fragment to match it. There could be multiple CSS rule elements as needed. The generated CSS would look like this:

div.index {
  padding: 1rem;
  margin: 0;
  max-width: 20rem;
}
ul.breadcrumb {
  list-style: none; /* turn off  the bullets */
}

In the example, the HTML div element and the HTML ul element each have their own separate styles, directly associated with them. This has resolved or at least greatly reduced the problem of granularity. Since the CSS is now embedded within the direct element constructor, rather than separated from it and outside the template, this has also resolved the difficulty of the person maintaining the XSLT having two places to update in parallel. Finally, moving the selector into an attribute means that curly braces are not needed, and now the CSS syntax can happily co-exist with XSLT 3.

This approach needs more support than simply putting the CSS before the template. The css:rule elements are considered by an XSLT processor to be “direct element constructors”, so they and their contents appear in the output. To deal with this, the author has used two different approaches. The first, shown in the appendix to this paper, is to use the XPath fn:transform() function to apply a stylesheet and then remove extraneous CSS elements before creating the final output.

It may seem that the CSS could be bundled up and written to the CSS file, perhaps with an XSLT 3 accumulator, as the input is processed, but in fact what is needed is all of the CSS elements, including ones not triggered by templates, for example for server-generated dynamic content or for static HTML pages sharing the same CSS stylesheet. Therefore, the XSLT separately processes the XSLT stylesheet as XML, gathers the css:rule elements, and writes the stylesheet.

The author has also written very simple a Java extension class for Saxon so that the CSS elements do not generate any content; this is available (both source and compiled) on the gitlab page for CSS Within.

Putting the CSS inside XSLT element constructors in templates has the effect of interleaving the XSLT source and the CSS source. Evaluating the XSLT entails separating out the two streams. The CSS is removed when the XSLT stylesheet is compiled (either by XSLT pre-processing of the stylesheet itself, or using a Java implementation to do this). Since the CSS is no longer present when the XSLT is evaluated, property values cannot refer directly to XSLT variables; a mechanism to include dynamically-generated content in the CSS sylesheet is described in the next section.

CSS Within in More Detail

This paper does not attempt a complete definition of CSS Within, but illustrates enough of it for the purpose of discussing the application of the principle of locality of reference to software maintenance. The full documentation is available on the gitlab page for CSS Within.

css:rule

This element has a match attribute, and generates a single CSS rule. The content should be CSS properties.

It’s also possible to reuse fragments of CSS with a ref attribute to point to a css:rule element with a corresponding name attribute. This helps to address the problem of not knowing whether it is safe to remove a definition: if the same CSS rule is used in multiple places, this can be indicated with a css:rule element pointing back to (for example) the first instance.

A stream attribute allows for generation of multiple CSS outputs; one might have values of print, screen, and #all.

                                    <!ELEMENT css:rule (#PCDATA)*>
<!ATTLIST css:rule
    match CDATA #IMPLIED
    stream CDATA #IMPLIED
    name ID #IMPLIED
    ref ID #IMPLIED
>

css:media

This element models CSS media queries (specific in the W3C CSS Conditional Text module). It contains any mix of css:rule and css:media elements.

<!ELEMENT css:media (css:rule|css:media)*>
<!ATTLIST media-query
    when#CDATA  #IMPLIED
    stream CDATA #IMPLIED
>

For example, the following input:

<div class="navbar">
    <css:rule  match="div.navbar">
        font-size: 80%;
    </css:rule>
    <css:media when="max-width: 600px">
        <css:rule match="div.navbar a">
            display: inline-block;
            min-height: 48px;
            min-width: 48px;
        </css:rule>
    </css:media>
    <xsl:apply-templates />
</div>

produces the following CSS result:

div.navbar {
    font-size: 80%;
}
@media (max-width: 600px) {
    div.navbar a {
            display: inline-block;
            min-height: 48px;
            min-width: 48px;
    }
}

The effect of the media query is that if the viewport (the screen or page) is less than six hundred pixels wide, a elements inside the navigation bar are rendered with minimum height and width of 48 pixels; since a CSS pixel is defined to be one ninety-sixth of an inch, 48 pixels is half an inch, plenty large enough for a mobile phone or tablet user to hit with a thumb.

There are also constructs for at-rules, a CSS header and footer, and ways to get at the generated stylesheet.

In all cases the curly braces are generated automatically, and the CSS rules can be placed near the code generating the elements to which they will apply, or, as in the media query example, in the parent element's XSLT template.

Writing Out CSS Styles

Regardless of how many times any particular XSLT template was used in a transformation, the CSS stylesheet contains each css:rule element exactly once. The rules appear in the order in which they occur in the stylesheet.

If you need to put rules in a different order, you can put them between templates or in a template not otherwise used, and give them names; then refer to them with name/ref pairs from empty css:rule elements where they are needed, to remind your later self, and others, where they apply.

The stylesheet is constructed as a string; it is possible to call fn:replace() on it from within XSLT before writing it out. In addition, you can make a header that defines CSS custom properties (CSS variables) and refer to them in the stylesheet.

The implementation of CSS Within currently on gitlab uses fn:transform() to run a modified version of the stylesheet with the CSS rules removed, or removes any errant CSS instructions from the output; it then uses a namespaced XSLT mode to gather up the CSS rules and write them out. Some cleverness, described in an appendix to this paper, would be needed to do this in the case that there are multiple stylesheet compilation units (possibly including external precompiled packages) that might be selected at runtime.

Alternate Designs

It's very tempting to want to make the CSS properties be XML attributes instead of text content. There are some problems with this, however.

  1. The attributes must be processed without evaluating the stylesheet. XSLT developers are accustomed to thinking of attributes as first-class objects, passing them around, generating them in functions, using dynamic attribute sets, using attribute value templates to interpolate runtime values, and more. None of these techniques work when the XSLT stylesheet is treated as a passive XML document containing CSS fragments.

  2. It often happens that a CSS property must be repeated in the same rule with different values: different user agents (browsers) will use whichever value they consider valid and reject the others. You cannot have two XML attributes with the same name, so this rules out using the obvious attribute-name/value approach.

  3. Some CSS property values do contain curly braces, but there's no equivalent to expand-text = "no" for attribute values, so you are back to difficulties with curly braces, compounded by the fact that you can't use a runtime variable.

  4. Although the forgoing reasons are surmountable with varying degrees of inconvenience, a far greater problem is the distance between XSLT and CSS cognitive modes for the human reader. It's much easier to read a CSS example in CSS syntax and compare it to online examples, or to copy and paste to and from a Web browser element inspector. The way CSS is written is part of the way people think about CSS, so it's important to support keeping it that way.

    In particular, the CSS cascade works very differently from XSLT template priorities. In XSLT, an entire template is selected by the processor based on priorities. In CSS individual rule/value pairs are selected, and in some cases merged, based on a mixture of ordering in the input file and how specific the selectors on the CSS rule surrounding them are. It’s easy to forget this and to imagine that an entire rule (between open and close curly braces) is to be selected; getting this wrong can lead to white text on a white background, or other styling errors.

None the less, the author is considering using an attribute-based approach to font references, so some experimentation is happening and CSS Within may evolve towards supporting a richer markup for individual CSS property-value pairs in the future, as well as for at-rules such as @font-face or @page which at the moment must be supplied separately, for example in a header included when the CSS is written.

Future Work

CSS Within continues to evolve through experience and contemplation. A current experiment makes the match attribute optional on css:rule elements,: instead, the immediately enclosing direct element constructor is examined. This makes it easier to rename elements or classes, and easier to copy style blocks. The example from earlier in this paper, showing the use of a media query, might become:

<div class="navbar">
    <css:rule >
        font-size: 80%;
    </css:rule>
    <css:media when="max-width: 600px">
        <css:rule match="div.navbar a">
            display: inline-block;
            min-height: 48px;
           mon-width: 48px;
        </css:rule>
    </css:media>
    <xsl:apply-templates />
</div>

Only the second line of the example is changed; in practice, however, this change would likely simplify almost all css:rule elements. The question to be determined is whether the change increases or reduces maintainability.

More support for CSS at-rules is planned, for example for fonts or keyframes; experiments using an attribute on a css:rule element to point to an at-rule for a @font-face have not been promising so far, as the added complexity gave minimal benefit. However, hooking into infrastructure to test that font files are in place may give added motivation. In addition, automatically generating the latest version of the “bullet-proof Web font” syntax from something simpler would be a benefit. The following listing gives an example of a CSS font definition:

@font-face {
    font-family: "IM Fell English PRO";
    font-slant: normal;
    font-weight: regular;
    src: url('fonts/imfellenglishpro.eot');
    src: url('fonts/imfellenglishpro.eot?#iefix') format('embedded-opentype'),
         url('fonts/imfellenglishpro.woff2') format('woff2'),
         url('fonts/imfellenglishpro.woff') format('woff'),
         url('fonts/imfellenglishpro.ttf') format('truetype');
    /* SCG fonts via CSS are deprecated now, see
     * https://www.zachleat.com/web/retire-bulletproof-syntax/
     * url('fonts/imfellenglishpro.svg#imfellenglishpro') format('svg');
     */
}
Notice especially the tricks such as ?#iefix used if Internet Explorer support is desired, along with the need to repeat the first src property-value pair. Complexities such as this, along with the occasional need for actual syntax errors to support incorrect implementations, makes a markup-based representation especially complex. Simple attribute-value pairs do not work because of the repetition. However, simply surrounding the rule with element and using name/ref for validation may be useful in itself:
<cs:font-face name="imfelleiglishpro">
    font-family: "IM Fell English PRO";
    font-slant: normal;
    font-weight: regular;
    src: url('fonts/imfellenglishpro.eot');
    src: url('fonts/imfellenglishpro.eot?#iefix') format('embedded-opentype'),
         url('fonts/imfellenglishpro.woff2') format('woff2'),
         url('fonts/imfellenglishpro.woff') format('woff'),
         url('fonts/imfellenglishpro.ttf') format('truetype');
    /* SCG fonts via CSS are deprecated now, see
     * https://www.zachleat.com/web/retire-bulletproof-syntax/
     * url('fonts/imfellenglishpro.svg#imfellenglishpro') format('svg');
     */
</css:font-face> 
With this simple change, the font-face CSS rule could be included only if needed, and a warning generated on an attempt to use a font-face by reference that was not defined.

For Web use it’s important to include font definitions as early in a stylesheet as possible, so that the browser can start the process of loading the font. CSS is generated by the CSS Within XSLT stylesheet in the order it occurs in the input XSLT you are using, so you can have an otherwise-unmatched template at the start of your stylesheet, or an xsl:variable definition, containing rules to go at the start.

There is also ongoing work with extension elements. The Java class for Saxon that makes css:rule and css:media return an empty sequence at runtime could also help to write out stylesheets, but the author ran into a combination of time constraints and unclear documentation, so this is not yet an active part of the CSS Within distribution.

CSS Within can also be used with systems such as the SASS preprocessor, although some of the SASS benefits, such as nesting, are irrelevant since CSS Within uses the natural nesting of direct element constructors.

CSS Within In Practice; An Unwarranted Conclusion

It took the author an hour or two to convert one standalone CSS file of some 6,500 lines to CSS fragments within an XSLT stylesheet. However, the time has paid for itself in unexpected ways. Not only are the styles now always up to date, but the act of finding the particular style rule has become trivial, which has saved far more time than anticipated.

It is difficult to contemplate going back to keeping CSS in a separate file. The necessity of handling extension elements, or of post-processing the XSLT result, is unfortunate. Preprocessing the XSLT to remove CSS elements before evaluation sounds tempting, but in XSLT 3 stylesheet filenames for inclusion could be supplied as parameters and package selection can depend on system properties or even environment variables, so this is not in general possible.

Some months after implementing CSS Within, the author had occasion to return to the original project. They found that they were easily able to modify and refactor the stylesheet as needed, updating the styles. Previously, returning to the project after some time has always proved difficult.

Moving a dependent resource into the centre of the code, instead of cluttering the code, provides an overall clarity and increase in efficiency. Furthermore, there is a reduced impediment to refactoring compared to an external stylesheet, because the effects of making changes are more readily apparent.

In fairness, one should contrast the possibility, literate-programming style, of embedding XSLT inside CSS stylesheets. However, not only would this discourage refactoring, as already suggested, but it would bring back the difficulties of curly braces, and would add the same impediment to updating: that it would be easiest to modify a template without looking at the corresponding CSS. With CSS Within it is easier to look at both CSS and XSLT than to look at only one, because the CSS is within the XSLT at the point where it is needed, and yet kept subsidiary. Making it easier to do the right thing than the wrong thing leads to an overall improvement in code as well as in quality of life.

The Principle of Locality of Reference is useful in leading us to ways of writing code that is more readable, more reliable, and much easier to maintain.

Appendix A. Implementing “CSS Within” With XSLT

There are three parts to the implementation:

  1. Checking the CSS elements are used correctly, with for example no css:media elements inside css:rule elements;

  2. Handling the unwanted constructed CSS nodes when the stylesheet is executed;

  3. Generating the CSS stylesheet.

In the current implementations, the first item, that of validation, is handled by XSLT that also removes unwanted nodes.

The second item, that of removing the unwanted nodes, as achieved as follows: either with a Java extension element that returns the empty sequence on compilation, so that the nodes are not generated, or using fn:transform()to call XSLT and then post-processing the result with an identity transform that removes all elements in the CSS Within namespace. It is also possible to pre-process the stylesheet to remove the CSS direct element constructors (and their contents) and then use fn:transform() on the result. However, this does not take external packages into account.

Generating the stylesheet is done currently by processing the stylesheet with XSLT; the author also had extensions written in Java to provide a css:get-stream() function so that the stylesheets could be written from the XSLT stylesheet but this did not work with separate compilation of packages, nor with multiple stylesheet files. A newer approach was started: this would augment a css:gather element in each file or compilation unit with the generated text of the style sheet, so that it can be placed in a template that uses xsl:next-match to process all of the stylesheet files at runtime.

An XSLT variable can be define to hold a CSS header containing an encoding declaration, initial comments, @font-face, @page and other at-rules, and this can easily be prepended to the CSS file when it is created from XSLT. Such a header can also define custom properties (CSS variables) which can then be referenced from property values in css:rule elements. This considerably reduces any need to construct XSS property values at runtime inside actual templates, which is just as well since the CSS is gathered before the stylesheets are even execute!.

Appendix B. A Slightly Longer Example

The following is a complete template from a production stylesheet.

                  <xsl:template name="make-navbar">
  <xsl:param name="relative-path-to-letters" select="string('../')" />
  <xsl:param name="node" select="$doc" as="document-node()" />
  <p class="navbar">
    <css:rule match="p.navbar">
      font-size: 80%;
      text-align: center;
      margin-top: 0.5em;
      margin-bottom: 0.5em;
      border-top: 1px dotted;
      border-bottom: 1px dotted;
    </css:rule>
    <xsl:for-each select="distinct-values($node/dictionary/letter/@lc)">
      <xsl:text> </xsl:text>
      <a href="{$relative-path-to-letters}{.}/">
        <css:rule match="p.navbar a">
          text-decoration: none;
        </css:rule>
        <!--* for touch devices: *-->
        <css:media when="max-width: 600px">
          <css:rule match="p.navbar a">
            display: inline-block;
            min-height: 48px;
            min-width: 48px;
          </css:rule>
        </css:media>
        <xsl:value-of select="upper-case(.)"/>
      </a>
      <xsl:text> </xsl:text>
    </xsl:for-each>
  </p>
</xsl:template>

References

[Denning, 2005] Denning, Peter J., The Locality Principle, in Communications of the ACM, Vol 48, No. 7, July 2005. doi:https://doi.org/10.1145/1070838.1070856.

[Unsub, 2021] Unsubstantiated assertion provided without evidence.

[Wilkes, Wheeler, Gill, 1951] Wilkes, N., Wheeler, D., Gill, S., Report on the Preparation of Programmes for the EDSAC and the Use of the Library of Subroutines, University Mathematical Laboratory, Cambridge, 1951.

×

Denning, Peter J., The Locality Principle, in Communications of the ACM, Vol 48, No. 7, July 2005. doi:https://doi.org/10.1145/1070838.1070856.

×

Unsubstantiated assertion provided without evidence.

×

Wilkes, N., Wheeler, D., Gill, S., Report on the Preparation of Programmes for the EDSAC and the Use of the Library of Subroutines, University Mathematical Laboratory, Cambridge, 1951.

Author's keywords for this paper:
Programming; XSLT; CSS