5.2. CSS and XSLT

WAF uses two technologies together, CSS and XSLT, to add style to logical (i.e. all form, no style) markup.

5.2.1. Integrating XSLT with WAF

WAF integrates XSL stylesheets in the following way:

NoteNote
 

WAF infrastructure depends on the Xalan-J XSLT engine from the Apache XML project. In turn, this engine depends on the Xerces XML parser, also from Apache. Although it is theoretically possible to use a different XML parser with Xalan, this process hasn't been thoroughly tested. It is also possible to use a different XSLT engine, such as Saxon. WAF contains no explicit dependency on any vendor's XSLT engine.

This flexibility comes from the PresentationManager interface, whose one method servePage obtains a transformer for the current request, applies it to an input document, and serves the transformed output to the response output stream. A WAF application's dispatcher can use the provided BasePresentationManager or swap it out with its own to use a different algorithm for choosing a stylesheet for the current request.

XSLT integration into WAF is accomplished by associating stylesheet documents with both WAF packages and site nodes. A default stylesheet is associated with each WAF package, and the package defaults can be overridden within a particular URL prefix's scope. A site node represents a node in the webapp's URL tree; each site node, combined with its ancestors, generates a URL prefix. For the purposes of this discussion, subsite is defined to mean any site node that contains child site nodes — that is, a directory that contains subdirectories.

Because XSLT templates are also XML documents, they can be manipulated and composed dynamically. A PresentationManager could compute a new XSLT stylesheet on the fly, taking fragments from files on disk or in the database.

For more information about using XSLT with WAF, see Section 12.1 Calling XSLT from a WAF Application.

5.2.2. WAF Templating Package

The WAF Templating package (com.arsdigita.templating) serves as a repository for all classes relating to the XSL templating.

5.2.2.1. Template Resolution

This section discusses the process of resolving the primary, top level stylesheet for transforming an application's XML DOM into an HTML page.

5.2.2.1.1. Background

The original WAF method for resolving top level stylesheets required the application programmer to register a default com.arsdigita.kernel.StyleSheet object against its com.arsdigita.kernel.PackageType object. Project integrators could override this default application stylesheet by registering a custom stylesheet against a com.arsdigita.kernel.SiteNode object.

These mappings were maintained in the database and the rules for querying them to discover the top level XSL template for an application were written into the standard presentation manager class (com.arsdigita.sitenode.BasePresentationManager). This resulted in an XSL templating architecture that was spread across multiple Java packages and left little scope for extending or altering the template resolution algorithms, without massive code replacement / duplication.

5.2.2.1.2. Stylesheet Resolver

A quick analysis of XSLT usage across projects and applications shows that there are a large number of variables that can come into play when deciding which stylesheet to apply to an application's DOM. Rather than attempt to standardize on a particular algorithm for resolving stylesheets, the WAF templating package introduces the com.arsdigita.templating.StylesheetResolver interface as a means to plug in an arbitrary template resolution algorithm. This interface contains a single method:

public URL resolve(HttpServletRequest sreq);

This method may use any available state information to locate a stylesheet, which transforms XML that has been generated during this supplied request object. The returned object must represent an absolute URL under any protocol supported by the java.net.URL class (typically, either file:// or http://). The com.arsdigita.templating.LegacyStylesheetResolver class provides a resolver compatible with the resolution method used in WAF releases older than 6.0. The com.arsdigita.templating.PatternStylesheetResolver class is the new preferred resolver and now the default setting.

5.2.2.2. Pattern Based Resolution

The primary implementation of the stylesheet resolver interface is the class com.arsdigita.templating.PatternStylesheetResolver. The core idea behind this approach is that there is a list of abstract paths containing placeholders, of the form ::key::. For example, a simplified version of the default list of paths looks like:

/__ccm__/apps/::application::/xsl/::url::-::locale::.xsl
/__ccm__/apps/::application::/xsl/::url::.xsl

When resolving which stylesheet to apply, the placeholders are expanded based on the request state, to generate a list of real paths. Since a placeholder potentially has multiple valid values for a request, a single abstract path could expand to multiple real paths. Placeholder expansion proceeds left-to-right, to ensure a deterministic ordering of the list of real paths.

If we consider an example request for the content section admin pages at /content/admin/index.jsp, the placeholder expansions might be:

  • ::application:: -> (content-section)

  • ::url:: -> (admin, index)

  • ::locale:: -> (en_US, en)

If we proceed left-to-right with this expansion, the first expansion is for ::application::, leading to:

/__ccm__/apps/content-section/xsl/::url::-::locale::.xsl
/__ccm__/apps/content-section/xsl/::url::.xsl

Next, the ::url:: placeholder is expanded:

/__ccm__/apps/content-section/xsl/admin-::locale::.xsl
/__ccm__/apps/content-section/xsl/admin.xsl

/__ccm__/apps/content-section/xsl/index-::locale::.xsl
/__ccm__/apps/content-section/xsl/index.xsl

Finally, the ::locale:: placeholder is expanded:

/__ccm__/apps/content-section/xsl/admin-en_US.xsl
/__ccm__/apps/content-section/xsl/admin-en.xsl
/__ccm__/apps/content-section/xsl/admin.xsl

/__ccm__/apps/content-section/xsl/index-en_US.xsl
/__ccm__/apps/content-section/xsl/index-en.xsl
/__ccm__/apps/content-section/xsl/index.xsl

Once all the placeholders have been expanded, these paths will be verified in order and the first that exists returned as the primary stylesheet for the request. In this example the first match will be /__ccm__/apps/content-section/xsl/admin.xsl.

5.2.2.3. Pattern Generators

The com.arsdigita.templating.PatternGenerator interface provides the mechanism for introducing new placeholders in the pattern based stylesheet resolver. This interface contains the single method:

  public String[] generateValues(String name,
                                 HttpServletRequest request);

This method may use any available state information to generate a list of values for the placeholder. The elements in the array should be ordered in decreasing specificity. If there are no possible values for a placeholder, then an empty array can be returned, causing the entire abstract path to be thrown away. As a hypothetical example, consider a pattern generator for switching based on the user's browser:

public class BrowserPatternGenerator implements PatternGenerator {
    public final static String GENERIC = "generic";

    public String[] generateValues(String key,
                                   HttpServletRequest req) {
        String useragent = req.getHeader("user-agent");
        if (useragent != null &&
            useragent.indexOf("MSIE") > -1) {
            return new String[] { "ie", GENERIC };
        } elseif (useragent != null &&
                  useragent.indexOf("Mozilla") > -1) {
            return new String[] { "mozilla", GENERIC };
        } else {
            return new String[] { GENERIC };
        }
    }
}

Example 5-1. Web Browser Triggered Pattern Generator

Once the new pattern generator class has been implemented, it needs to be registered with the resolver by calling the static registerPatternGenerator method in com.arsdigita.templating.PatternStylesheetResolver, supplying the name of the placeholder key. This registration is best done in the static initializer block of a core class, such as a servlet.

static {
  PatternStylesheetResolver.registerPatternGenerator(
       "browser",
       new BrowserPatternGenerator()
  );
}

Available Patterns

application

This pattern generator expands to the current application package key. ie, the value returned by a call to com.arsdigita.web.Web.getConfig().getApplication().getKey().

host

This pattern generator expands to the hostname and port number for the current servlet container, as returned by com.arsdigita.web.Web.getConfig().getHost().

locale

This pattern generator expands to the current kernel execution context locale. ie the value returned by a call to com.arsdigita.kernel.Kernel.getContext().getLocale();.

outputtype

If the outputType request parameter is set, this expands to one of text-plain, text-javascript, or text-html if the parameter value is text/plain, text/javascript or text/html respectively. In all other cases no values are generated.

prefix

If the current request has passed through an instance of the com.arsdigita.web.InternalRedirectServlet, then this pattern generator expands to the value of the prefix init parameter in the web.xml servlet declaration. In all other cases no values are generated.

url

This pattern generator expands to multiple values based on the URL fragment remaining after the application's mount point. The set of values are generated by progressively stripping off trailing path components. The values are then normalized as follows:

  1. The file extension is removed.

  2. Trailing occurrences of /index are replaced with /.

  3. If the path is the empty string, it is replaces with index.

  4. Occurrences of / are replaced with -.

Some examples are:

  • /index.jsp -> { "index" }

  • / -> { "index" }

  • /admin/index.jsp -> { "admin", "index" }

  • /admin/ -> { "admin", "index" }

  • /admin/item.jsp -> { "admin-item", "admin", "index" }

5.2.2.4. Configuration

The choice of which stylesheet resolver to use is controlled by the waf.templating.stylesheet_resolver property, and defaults to com.arsdigita.templating.PatternStylesheetResolver.

The location of the paths file to configure the pattern based stylesheet resolver is controlled by the waf.templating.stylesheet_paths property, defaulting to /WEB-INF/resources/stylesheet-paths.txt

To enable an application to operate with the default pattern stylesheet resolver configuration, its primary top level stylesheet should be named /__ccm__/apps/[application key]/xsl/index.xsl.