Overview of the design and implementation of ePublisher page templates.
Table of Contents
The structured presentation of information fuses design with logic. One approach, traditional web CGI applications, force design (HTML) to exist inside logic (program source code). Another approach, ASP and PHP, invert this relationship and force logic (program source code) to exist inside the design (HTML). Neither approach is ideal. In one case, designers lack creative freedom. In the other, programmers complain that designers keep breaking their code.
The page template model is a new approach with the goal of satisfying both designers and programmers. It has been successfully used in projects like Zope, Plone, Woven, and Nevow. ePublisher uses a page template model based on priciples outlined in Woven and Nevow.
Traditional design/logic methods require design tool support to be effective. Products such as DreamWeaver, FrontPage, and GoLive went through many iterations before HTML code changes would not remove ASP/PHP code:
... <h2> <% date.current() %> </h2> ...
Still further iterations were required before environments like ColdFusion could have their custom tags preserved:
... <h2> <DataConnection table="random" ... /> </h2> ...
<h2 nevow:render="greet" />
The resulting markup can be previewed in 4.x series browers and edited with first generation design tools.
ePublisher implements page templates using a custom namespace, four attributes, and one XSL transform.
The page template XSL transform is presently located at:
In the future, it will be move out of the "html" directory. The page template XSL transform can be invoked on any valid XML source, not just XHTML.
For purposes of discussion, examples are taken from the WebWorks Help 5.0 page template, located at:
ePublisher Pro/Formats/WebWorks Help 5.0/Pages/Page.asp
Forward slashes (/) are used because that's the way God, UNIX, and the Web intended them to be.
ePublisher defines the following namespace for page templates:
The page template attributes are as follows:
wwpage:condition wwpage:content wwpage:replace wwpage:attribute-<name>
Additionally, the attribute "wwpage:attribute-<name>" supports two special values:
relative-to-output copy-relative-to-output absolute-to-output
These two values enable simplified page template design and maintenence.
Conditions are used to control when elements should be preserved. For example:
<hr wwpage:condition="header-exists" align="left" />
If the "header-exists" condition exists, then the above "<hr>" will be preserved. Otherwise, it disappears from the output.
Conditions are hierarchial. All nested elements and text are removed regardless of nested condition states. So, if following conditions are defined:
then applying the page template XSL transform to the following:
<table wwpage:condition="company-info-top"> <tr> <td> WebWorks </td> </tr> <tr wwpage:condition="company-phone-exists"> <td> Phone: 512-555-1234 </td> </tr> </table> <div wwpage:condition="lucky-you"> Lotto: 2 - 32 - 25 - 23 - 34 </div>
<div> Lotto: 2 - 32 - 25 - 23 - 34 </div>
If only "company-info-top" is defined, then the result would be:
<table> <tr> <td> WebWorks </td> </tr> </table>
Elements may have either their "innerHTML/innerXML" or "outerHTML/outerXML" replaced. This is controlled via:
In the future, alternate attributes will be defined; "wwpage:inner" for "wwpage:content" and "wwpage:outer" for "wwpage:replace".
For this example, assume that the replacement "intro" has been defined as:
<h2>Introduction</h2> <div>I really like the beginning of things.</div> <div>Ever so much better than the end or being stuck in the middle.</div>
When applied to the following page template:
<blockquote wwpage:content="intro"> Page content with header/footer. </blockquote> <div wwpage:replace="intro"> Page content with no header/footer. </div>
the result is:
<blockquote wwpage:content="intro"> <h2>Introduction</h2> <div>I really like the beginning of things.</div> <div>Ever so much better than the end or being stuck in the middle.</div> </blockquote> <h2>Introduction</h2> <div>I really like the beginning of things.</div> <div>Ever so much better than the end or being stuck in the middle.</div>
In the first case, a "wwpage:content" attribute was used. This preserves the original element and simply replaces its inner text/HTML/XML. In the second case, a "wwpage:replace" attribute was used. This instructs the page template XSL transform to insert the content in place of the element.
Page templates allow designers to put dummy text or markup into their designs without concern for how it will affect rendered output.
Element attributes are controlled by the "wwpage:attribute-<name>" attribute. For example:
<body style="text-align: left;" wwpage:attribute-style="body-style"> ... </body>
indicates that the "style" attribute of the "body" element should be replaced by whatever value is defined for "body-style". The match is made by finding all attributes that start with "wwpage:attribute-", trimming off the prefix, and taking the result. In this way, multiple attributes on an element can be updated:
<table cellpadding="1" cellspacing="2" wwpage:attribute-cellpadding="cell-padding" wwpage:attribute-cellspacing="cell-spacing"> ... </table>
Again, designers still can insert real values that are preserved through multiple edits of the page template.
When using the "wwpage:attribute-<name>" mechanism to replace attribute values, two special replacement values are available:
These special replacement values allow nested output hierarchies, a feature that did not exist in Publisher 2003.
In Publisher 2003, a user could specify the following:
<link rel="StyleSheet" href="css/webworks.css" type="text/css" media="all" />
If things were laid out as follows:
GUI Files alpha.fm beta.fm Support css webworks.css
then Publisher 2003 would generate:
Output alpha.html <link rel="StyleSheet" href="css/webworks.css" /> beta.html <link rel="StyleSheet" href="css/webworks.css" /> css webworks.css
ePublisher is much more flexible. A user can choose to create nested folders:
GUI Group alpha.fm Nest beta.fm Pages Page.asp css webworks.css
which should be output as:
Group alpha.html <link rel="StyleSheet" href="css/webworks.css" /> Nest beta.html <link rel="StyleSheet" href="../css/webworks.css" /> css webworks.css
This is allowed via the "copy-relative-to-output" replacement value. The page template XSL transform knows to look for the file "css/webworks.css" relative to the "Page.asp" file, copies it to the output group folder, and then determines the correct relative path.
For cases where the file copy is handled elsewhere, the "relative-to-output" replacement value simply updates the relative path. So:
The relative path replacement attributes can be used on any valid URI path attribute on any element.
The page template XSL transform is applied to a loaded Page.asp (or other XML file) with two parameters, conditions and replacements. Specifying conditions and replacements boils down to creating an XML node set. In Microsoft's .Net XSL transform engine, that can be accomplished by:
<xsl:variable name="VarCondsAsXML"> <wwpage:Condition name="company-info-exists" /> </xsl:variable> <xsl:variable name="VarConditions" select="msxsl:node-set($VarCondsAsXML)" />
Standard XSL control structures can be used when defining a condition set:
<xsl:variable name="VarCondsAsXML"> <wwpage:Condition name="company-info-exists" /> <xsl:if test="$VarBreadcrumbsBottomGenerateOption = 'true'"> <wwpage:Condition name="breadcrumbs-bottom" /> </xsl:if> </xsl:variable> <xsl:variable name="VarConditions" select="msxsl:node-set($VarCondsAsXML)" />
Replacements work similarly:
<xsl:variable name="VarRpsAsXML"> <wwpage:Replacement name="body-style" value="color: green;" /> <wwpage:Replacement name="intro"> <h2>Introduction</h2> <div>I really like the beginning of things.</div> <div>Ever so much better than the end or being stuck in the middle.</div> </wwpage:Replacement> </xsl:variable> <xsl:variable name="VarReplacements" select="msxsl:node-set($VarRpsAsXML)" />
Replacements can be defined either by an empty XML element with a value attribute or by a non-empty XML element containing text or an XML sub-tree.
At present, most formats define conditions and replacements in:
Formats/<Format Name>/Transforms/pages.xsl Formats/<Format Name>/Transforms/popups.xsl Formats/<Format Name>/Transforms/wrappers.xsl
"Dynamic HTML" and "Simple HTML" also define conditions and replacements in:
Formats/<Format Name>/Transforms/toc.xsl Formats/<Format Name>/Transforms/index.xsl
Non-XML files have the same need for replacing content that XML files do. The only difference is that they cannot perform any kind of "smart" replacements due to lack of structure. Further, conditions are not supported, again due to lack of structure.
To support generic text/binary files, the multiple search/replace XSL extension was defined. It is implemented in the following namespace:
Replacements are specified as follows:
<xsl:variable name="VarRsAsXML"> <wwmultisere:Entry match="$BinaryIndex;" replacement="true" /> <wwmultisere:Entry match="$CompiledFile;" replacement="help.chm" /> </xsl:variable> <xsl:variable name="VarReplacements" select="msxsl:node-set($VarRsAsXML)/*" />
Unlike page template replacements, only empty XML elements with "match" and "replacement" attributes are supported.
When invoked on a file such as:
[OPTIONS] Binary Index=$BinaryIndex; Compatibility=1.1 or later Compiled file=$CompiledFile; ...
will result in:
[OPTIONS] Binary Index=true Compatibility=1.1 or later Compiled file=help.chm ...
Fans of Publisher 2003 will notice a similarity of syntax. This is purely for "that retro thrill" and is not required for operation. Match attributes can be any valid string. Keep in mind that they are not regular expressions, so do not expect "$Match.*;" to do anything beyond what Notepad might do with it. Additionally, "$" and ";" are used here for the same reason they existed in Publisher 2003; to identify variables in all usage contexts.
<wwmultisere:Entry match="$Ben" replacement="Ben was here." />
will output as:
Email Ben was here.Allums.
Likely not what you had in mind.
For an excellent example of the multiple search/replace XSL extension, look at:
Formats/Microsoft HTML Help 1.x/Pages/template.hhp Formats/Microsoft HTML Help 1.x/Tranforms/htmlhelp_hhp.xsl
Even non-XML files have encodings. And ePublisher does not magically know your file's encoding when it applies a search/replace operation to one. As a result, the very first parameter that must be supplied is the encoding of the non-XML template file:
<xsl:value-of select="wwmultisere:ReplaceAllInFile('UTF-8', $TemplatePath, $OutPath, $VarReplacements)" />
Since all XML node sets exist as Unicode in memory, the search/replace engine must know how to translate the replacement entry atrributes, "match" and "replacement", to the correct byte sequences before operating on the file.
Unlike XML templates that are transformed to different encodings by the XSL transform engine, the search/replace engine does no such thing. In the future, an XSL extension method will be made available to enable non-XML file encoding translations.