|
|
||||||
|
|
![]() |
|
![]() |
|
||
|
|
||||||
Dynamically Generated XSL Revisited
By Michael Floyd
In the January installment of this column, I demonstrated how to dynamically generate XSL style-sheet transformations, which can then be applied to XML documents. In that column, I assumed that the developer has intrinsic knowledge of the structure and organization, or "schema," of the data being transformed. That knowledge is important because style-sheet transformations often use simple step patterns (or even full-blown XPath expressions) to locate a given element or attribute in the document tree, then use
<xsl:value-of>to retrieve the item's content. So, XSL style sheets are highly reliant on a document's structure.By moving from statically created style sheets to dynamically generated transformations, you shift responsibility from the style-sheet author to the DOM developer. However, if you can generalize the process, you can realize significant benefits from generating your XSL dynamically.
The key to generalizing this process lies in the schema. If you have a formal schema, such as a DTD or XML Schema document, you should be able to discover enough about the organization and structure to generate a reasonable XSL style-sheet document.
This month, I'll examine that process and discuss how far you can take it. I wrote this article with the assumption that you, the developer, are familiar with XML Data Reduced (XDR) schemas, the XSL Transformation language (XSLT), and the Document Object Model (DOM) (Visit www3.org for more information).
Style Sheet Propagation
When creating an XML application, a document is parsed and markup elements are placed into a document tree. An XSL style sheet containing the transformation is then applied to the elements in the tree. You can instruct the parser to apply a specific style sheet by including a
<?stylesheet ...?>declaration in the XML document instance. Or, more typically, you can apply the style sheet dynamically using the DOM'stransformNodemethod.In both cases, the style sheet is presumed to be static. One problem with static style sheetsall static documents, including scripts and programsis that by modifying the original XML document, you risk breaking the application. At the least, document modifications require maintenance throughout the rest of your application.
A potentially more serious problem is the proliferation of XSLT style sheets. For example, my Web site (through the use of the Rocket XML framework) uses a collection of style sheets to transform XML documents into HTML. Because my site transforms the XML documents on the server, the browser sees only HTML. By doing a little browser detection, I can select a specific style sheet, and thereby customize the HTML that's output to the requesting browser. For example, if a Netscape Navigator 4 browser requests a document, I detect the browser and version, and attach the style sheet for that particular browser. Because I have style sheets for Navigator 3 and 4, Internet Explorer 3, 4, and 5, and so on, I end up with half a dozen of them, all designed to render the same class of documents.
The problem is compounded because I have several types of documents (that is, documents of differing structures), resulting in another propagation of style sheets. This is further complicated by the fact that my site also supports four user interfaces, or themes, for each Web page served. These themes are, of course, generated by different style sheets. Ultimately, I support well over 100 style sheets.
Reliance on Structure
While XML documents separate data from presentation and program logic, XSL style sheets do no such thing. Depending on how you write them, your style sheets can rely heavily on the structure of the XML document itself.
This presents a serious problem in the case of my Web site (and Rocket). A simple change, like adding a new element type for a particular class of XML documents, could affect two dozen or more style sheets, making the system quite fragile. Generating style sheets dynamically eases this situation. However, one problem remains: Style sheets, even dynamically generated ones, must rely to some degree on the overall structure and organization of the XML document instance.
Fortunately, you can minimize a style sheet's reliance on document structure. First, process your elements in the order which they appear in the document. This isn't always possible, but when it is, describe the treatment of elements in template rules and let the XSL processor do the work. But don't use
<xsl:value-of>elements in your root template unless they're being processed in a document. Instead, use<xsl:apply-templates>and write a separate template rule for each element type in your document.You have a few options to change the order in which elements are transformed. For example, you can skip (or ignore) an element by generating an
<xsl:if>test in your transformation. This test still ties you to document structure, but you can control this by setting a flag in your script.Working with the Schema
Once you've minimized your style sheet's reliance on structure, use your document's schema to discover its overall structure. That information should let you generate a reasonable style sheet. At this point, XML Schemas shine over DTDs. To read a DTD programmatically, you must write routines to open the DTD, scan its elements, and generate a tokens list. In other words, you must hack the DTD.
By contrast, an XML Schema document is a well-formed XML document. Thus, you can load it into a DOM object, construct a document tree, and traverse it like any XML document. Why write a parser when you already have one?
Once you have access to the schema, you only need to walk the root element's
<element>definitions. Listing 1 shows a typical article document on my Web site and Listing 2 shows its corresponding XML Data Reduced (XDR) schema. (XDR is the schema format supported by Microsoft. For more information on XDR, see my September 2000 column or visit msdn.microsoft.com/library/psdk/xmlsdk/xmlp7k6d.htm.) This schema defines an<article>element type that turns out to be the root element. I discovered that the root element contains only elements when I checked its content attribute. Further, I've learned from the<ElementType>declaration that subelements of<article>must occur sequentially.Using this information, we can walk the list of subelements (
<element>definitions within<article>'sElementTypedeclaration) to get the structure of these subelements. For those elements that contain only string data, simply generate an<xsl:value-of>within the root template to retrieve and transform the template's content. Elements containing subelements (eithereltOnlyormixed) should cause a separate match template to be generated. Keep in mind that an<apply-templates>will also be generated to invoke each template rule as it's needed.Generating the Style Sheet
Listing 3 presents a client-side script to demonstrate these concepts. The script (JavaScript) is embedded in an HTML document. When the Web page loads, the
parsefunction is invoked. (Note that because this function saves the resulting style sheet on a disk, you should give it a .hta file extension, indicating an HTML application.)First, the
parsefunction creates two new DOM objects: one to contain the schema document and the other to hold the new style sheet. Listing 3 loads the schema document into the first object (schemaDocument).If that schema document loads successfully,
parsebegins constructing the style sheet. The first step is to create a string representing a skeleton style sheet. This style sheet simply contains an empty<xsl:stylesheet>element. That string is then loaded into thexslDocumentobject using theloadXMLfunction. Thenparsecreates the root template by calling the DOM'screateElementmethod to make a new element calledxsl:template. Creating a new element doesn't automatically insert the element into the document tree, so theappendChildfunction insertsxsl:templateas the last child of<xsl:stylesheet>. Once the element is in the tree, Listing 3 uses thesetAttributefunction to insert thematch="/index.html"attribute-value pair into the<xsl:template>element.The next step is to add HTML code to the transformation. So,
parseagain uses thecreateElementfunction to create theHTMLandBODYelements, andappendChildto insert these into the style sheet. As I described in my January column, I use CSS style rules to render various elements. Hence, a<LINK>tag is then generated to link in an external CSS style sheet. When the HTML transformation is generated, these style rules will be accessible to the HTML document.At this stage,
parsebegins working with the schema document to generate XSL templates. The function usesgetElementsByTagNameto retrieve allElementTypedeclarations in the schema document. One of these will be theElementTypedeclaration for<article>, so we use aforloop to scan the list of element types. When the article element is encountered,parseuses the DOM'schildNodesmethod to retrieve a list ofarticle's subelements.Because I'm processing all elements in document order,
parsejust cycles through the list. For elements that don't contain subelements, the function generates an<xsl:value-of>element that retrieves the original element's content and places it in the transformation. In this case,parsemust also attach a corresponding CSS style rule. You do this in HTML with a<DIV>tag, and assigning the name of the CSS rule to theCLASSattribute. By giving each CSS rule the same name as the element type being rendered, you could write something like:
<DIV CLASS="byline">Michael Floyd</DIV>Here, the CSS rule called
bylineis used to render the content of the<DIV>element. In Listing 2, the XML name used to represent this data is also calledbyline. So, when theparsefunction encounters thebylineelement type in the schema, it calls the DOM'sgetAttributemethod to retrieve thebylinevalue, and assigns that value to thecssStylevariable. ThecssStylevariable is then inserted into<DIV>'sCLASSattribute. To get the actual value,Michael Floyd, from the XML document instance, the XSL style sheet must use:
<xsl:value-of select="article/byline">In the
parsefunction, the step pattern is constructed with the same value used forcssStyle(acquired from the samegetAttributecall).Finally,
parsegenerates separate template rules for element types that contain subelements. The remainder of Listing 3 generates these template rules and is similar to the code I presented in my January column. You can read that column for a complete discussion.Associating Meaning
Style sheets are used to associate meaning with markup elements, preventing us from completely generalizing style-sheet transformations. You can't render a
<bold>element unless you know how to generate the appropriate transformation. There are a few ways to solve this problem. One is to let the user associate meaning interactively. You might create a tool that scans the schema, presents a list of all elements (and appropriate attributes), and lets an end user assign a property or behavior. An even simpler method is to create a mapping in your code between markup elements and their transformations. Either way, by moving from statically created style sheets to dynamically generated transformations you can solve the problem of propagating style sheets, and reduce maintenance of them, while generalizing the overall process.(Get the source code for this article here.)
Michael is the author of Building Web Sites with XML from Prentice Hall, and teaches Beyond HTML's Dynamic XML training course. He carries the honorary title of editor at large at Web Techniques. He can be reached at mfloyd@lifestylesSantaCruz.com.
|
|