Qwicap Templating Introduction

Chris W. Johnson
Information Technology Services
The University of Texas at Austin
January 27, 2005

Qwicap's templating scheme is really a general-purpose XHTML (XML) slicer/dicer; it does not depend on, or use, any custom markup; there is no special language to describe a "template"; there is no meta information that must be provided in order to use an XHTML document as a template, and so on. It turned out to be the 800 lb. gorilla of the project, because the goal was a simple API, a nearly non-existent learning curve, efficient internals that favor caching, and a syntax for programmers that could, for example, allow a single line of code to locate a particular table in a document, find its first body row, make a copy of that, insert the copy into the table, and set the contents of all of its cells. Another line could mark every even-numbered row of the table to be one color, and every odd row another. (In practice, for the sake of clarity, one might want to do that in more than two lines, but then again, one might not.)

//  Load a document and convert it to a modifiable state.

    MutableMarkup MyPage = Q.getDocument("mypage.xhtml").getMutable();

In order to use the templater to modify a portion of an XHTML document, the document must first be loaded into memory and parsed. The Qwicap class provides the getDocument method for this purpose.1 Given a string specifying a path to a document that is not already present in the cache, getDocument reads the file from disk, parses it and caches it as an instance of the ImmutableMarkup class. Because nothing can modify its contents, ImmutableMarkup allows cached instances of documents to be shared safely among multiple threads, etc.

A modifiable instance of an immutable document is created by invoking its getMutable method, which returns a copy of the document as an instance of the MutableMarkup class. That "copy" initially shares 100% of the backing store, and markup objects, of the original ImmutableMarkup object, so the conversion costs very little in terms of memory or time. Modifiable representations of the elements (tags) in MutableMarkup are created transparently as the document is modified, but strictly on an as-needed basis; the vast majority of the tags in a typical document are unlikely to be modified at all, and those tags continue to be references to the original, cached, immutable elements, and therefore consume no additional memory.

//  By way of example, locate the first row of the body of the table
//  marked with the "my-output-table" class.

    Results FirstRow = MyPage.get("table.my-output-table tbody tr:first-child");

Given a MutableMarkup object, the first step in modifying a portion of the document is to locate that portion. This is done by invoking the get method with an appropriate Cascading Style Sheets level 2 (CSS2) Selector string. CSS2 selectors are, in effect, the query language of the Qwicap templater.2 They were chosen because they were designed for locating elements in XHTML, and because they add nothing to the web application developer's learning curve. (Any web developer has to learn the basics of CSS selectors sooner or later.)

The get method returns a Results object which contains a list of all the elements (tags) in the markup that matched the supplied CSS2 selector string. All the methods that modify documents are provided by the Results class, and almost all such methods return a new instance of Results which lists all of the elements that were acted upon. When no elements are selected or acted-upon, an empty Results object is returned. These behaviors combine to make method invocation chaining possible, safe, and highly convenient. They also help to isolate code from the whims of web page designers/editors; if some element expected by your code is removed from the document, your code doesn't break or throw exceptions, it just does nothing. (This also allows a developer to safely "offer" a document more information than it might want. If the web page designer has a use for a piece of information, they can include markup that matches the particular CSS selector pattern documented by the developer. If the information is unwanted, the relevant markup can be omitted/deleted without fear of breaking the application.)

//  Duplicate the first row of the table, inserting the 
//  duplicate prior to the original first row.

    Results NewFirstRow = FirstRow.duplicateBefore();
    
//  Set the contents of the new first row.
    
    NewFirstRow.setContents("td.first-name", "Alexander");
    NewFirstRow.setContents("td.middle-initial", "T");
    NewFirstRow.setContents("td.last-name", "Great");

The methods of the Results class that modify documents are divided into three groups: those that operate on tags as a whole, those that operate on tag attributes, and those that operate on the contents of tags (the material between a "start tag" and an "end tag", for example between "<p>" and "</p>" tags). Also, because the "class" attributes of tags are more likely to be modified than any other, a set of convenience methods is provided specifically for operating on them.

In general, there are three basic modifications that can be performed: "set", "add" and "delete". "Set" and "add" operations both insert new material, but "set" replaces any existing material in doing so, while "add" appends to any existing material. "Delete" removes the specified material. A group of higher-level methods including duplicate, extract, replace and setContents are provided in order to combine and/or extend the basic functionality.

//  Get a list of all rows in the table body.
	
    Results Rows = MyPage.get("table.my-output-table tbody tr");

//  Add class designations that will permit our style sheet to color 
//  the even- and odd-rows of the table body independently, hopefully 
//  increasing the readability of large tables. Note that we begin  
//  by removing any such pre-existing designations.

    Rows.deleteClass("even-row");
    Rows.deleteClass("odd-row");
    Rows.even().addClass("even-row");
    Rows.odd().addClass("odd-row");

Note that "set" and "delete" operations are idempotent, as is the addClass function (unlike the other "add" operations), so code employing the Qwicap templater generally doesn't have to make itself aware of the state of a body of markup before manipulating it. This is an aid to both coding convenience and robustness.

Results objects also include methods for filtering the matches they contain in ways that can't be replicated using CSS2 selectors. Examples include: first which returns only the first match, even which returns only the even numbered matches, nth which returns the match at the specified index (the "Nth" match), and distinct which returns a Results object guaranteed to be free of duplicate matches (helpful when get is used to conduct more than one search at a time).

//  Now, do all of the above in two lines of code, without temporary variables, 
//  just to demonstrate that it can be done.

    MyPage.get("table.my-output-table tbody tr:first-child").duplicateBefore().
        setContents("td.first-name", "Alexander", "td.middle-initial", "T", 
        "td.last-name", "Great");

    MyPage.get("table.my-output-table tbody tr").deleteClass("even-row odd-row").
        pop().even().addClass("even-row").pop(2).odd().addClass("odd-row");

In situations where an application and the markup on which it depends are created by, or may come to be maintained by, different people, it is anticipated that the contract between the code and markup can be expressed in very simple terms to which web page designers can conform with ease. For example:

So, the Qwicap templater allows code and markup to be kept separate. In the author's opinion this is beneficial in the creation and maintenance of code and markup alike, and the separation is achieved without sacrificing ease of markup manipulation by code.

1. The Qwicap.getDocument method is the correct way to load an XML/XHTML document when using the full-up Qwicap system. When using Qwicap's templater independent of the larger Qwicap system, however, documents are loaded either through an XMLCache object when documents will be used repeatedly during the life-cycle of your application, or through the XMLDocument class, when a document will be used only once.
2. A few CSS2 selector patterns are not supported because they are meaningless on the server side. These are ":link", ":visited", ":active", ":hover" and ":focus".
Valid XHTML 1.0! Valid CSS!