XSLT Reflection
by Jirka KosekNovember 05, 2003
Reading XSLT Code
The most fundamental reflection task is to read code. This is very easy
in XSLT. You can query an XSLT stylesheet like any other XML document, the
only thing you must not forget is to specify the correct XSLT namespace
for all queried elements. The following examples assume that the prefix
xsl is bound to a XSLT namespace
(http://www.w3.org/1999/XSL/Transform).
We can query the source document, which is in fact another XSLT stylesheet, as any other XML document. For example, to get the total number of templates in the stylesheet, we can use:
Number of templates: <xsl:value-of select="count(//xsl:template)"/>
We can also create templates that match elements in the source XSLT stylesheet. For example, to get statistics about keys defined in a particular stylesheet, we can use the following template:
<xsl:template match="xsl:key">
Key name: <xsl:value-of select="@name"/>
Matches: <xsl:value-of select="@match"/>
Use expr: <xsl:value-of select="@use"/>
</xsl:template>
We can even access the contents of the currently processed stylesheet
by placing the call to document('') function at the beginning
of XPath expression.
Reading XSLT code from a XSLT stylesheet is not tricky. However, that's not true for generating XSLT code. We cannot generate XSLT elements in templates directly, as this will confuse the XSLT processor. It cannot recognize which element should be considered an instruction, controlling transformation flow, and which element should be just copied to the output.
One way of overcoming this issue is to use the xsl:element
instruction to emit all elements in the generated XSLT stylesheet. This
approach is a little inconvenient; in order to create a simple
stylesheet like
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
Hello! This text was created by an automatically generated stylesheet.
</xsl:template>
</xsl:stylesheet>
you have to use a rather verbose stylesheet, like
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:element name="xsl:stylesheet">
<xsl:attribute name="version">1.0</xsl:attribute>
<xsl:element name="xsl:template">
<xsl:attribute name="match">/</xsl:attribute>
Hello! This text was created by an automatically generated stylesheet.
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The second approach is easier, once you know how to use the
xsl:namespace-alias instruction. This instruction allows us
to remap namespaces after a document is transformed, and thus we can
generate XSLT elements directly using temporary namespace.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xslo="http://www.w3.org/1999/XSL/TransformAlias"
version="1.0">
<xsl:namespace-alias stylesheet-prefix="xslo" result-prefix="xsl"/>
<xsl:template match="/">
<xslo:stylesheet version="1.0">
<xslo:template match="/">
Hello! This text was created by an automatically generated stylesheet.
</xslo:template>
</xslo:stylesheet>
</xsl:template>
</xsl:stylesheet>
You can read more about this namespace aliasing technique in the article Namespaces and XSLT Stylesheets by Bob DuCharme, or you can read the corresponding section in the XSLT recommendation.
Now that we have learned how to read, query, and write XSLT stylesheets using XSLT, we can utilize our knowledge to do something really useful.
Convert HTML Stylesheets to XHTML Stylesheets
An obvious use of XSLT reflection is to refactor existing stylesheets. Suppose we have a large base of stylesheets that should be changed in a way that can be algorithmically captured. For example, we may want to modify existing HTML stylesheets to produce XHTML. In the case of only one stylesheet such rewriting will be done by hand. But if there is more than one stylesheet, the XHTML stylesheet should be automatically derived from the HTML one. Such an automatic derivation can be expressed in a form of the XSLT transformation.
Let's summarize the main differences between HTML and XHTML:
-
all XHTML elements belong to the namespace
http://www.w3.org/1999/xhtml -
XHTML is an XML language, not an SGML one
-
XHTML uses different public and system identifiers in the DOCTYPE declaration
Now we must express these changes as changes in the XSLT stylesheet. The first change means that for each non-namespaced element in the original HTML stylesheet, we must add the correct namespace. All other elements should be copied intact. The following template can accomplish this change for us:
<xsl:template match="*">
<xsl:choose>
<!-- When the element is not in a namespace, then it is HTML element
which should be transformed into a XHTML element in a proper namespace -->
<xsl:when test="namespace-uri(.) = ''">
<xsl:element name="{local-name(.)}"
namespace="http://www.w3.org/1999/xhtml">
<!-- Copy through attributes -->
<xsl:copy-of select="@*"/>
<!-- Process content of the element -->
<xsl:apply-templates/>
</xsl:element>
</xsl:when>
<!-- Other elements (mostly XSLT instructions) are copied through -->
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
However this doesn't correct namespace for elements produced by the
xsl:element instruction. Therefore, another template is
needed.
<xsl:template match="xsl:element">
<!-- Copy xsl:element instruction -->
<xsl:copy>
<!-- Copy original attributes -->
<xsl:copy-of select="@*"/>
<!-- Add element to the right namespace -->
<xsl:attribute name="namespace">http://www.w3.org/1999/xhtml</xsl:attribute>
<!-- Process content of the instruction -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
To complete it, we must also copy the possible content of elements like text, comments and processing instructions.
<xsl:template match="comment()|processing-instruction()|text()">
<xsl:copy/>
</xsl:template>
The second thing to do is to change output method from HTML to XML, and
also output the correct public and system identifiers for XHTML. This
behavior is controlled by the xsl:output instruction. The
following template processes this job.
<xsl:template match="xsl:output">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="method">xml</xsl:attribute>
<xsl:attribute name="encoding">UTF-8</xsl:attribute>
<xsl:attribute name="doctype-public">
-//W3C//DTD XHTML 1.0 Transitional//EN
</xsl:attribute>
<xsl:attribute name="doctype-system">
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
</xsl:attribute>
</xsl:copy>
</xsl:template>
To handle the cases where xsl:output is missing, we should
test it and create a new xsl:output instruction in the output
XHTML stylesheet.
It seems that the stylesheet is now ready to convert HTML stylesheets into XHTML ones. But if we try it, we find that generated stylesheets contain a lot of default namespace declarations in the following form:
<someelement xmlns="http://www.w3.org/1999/xhtml">
Even worse, this declaration is usually repeated in HTML generated by this stylesheet. This is not an error, but the page is gratuitously long and it can cause problems for some older browsers.
We can get rid of these superfluous declarations by declaring a XHTML namespace as a default namespace for the root element of the stylesheet. This sounds easy, but XSLT doesn't offer any standard way for creating such declarations. In the most widely used processors, like Saxon and xsltproc, the following trick works. We can create an element in a XHTML namespace and store it in a variable. From this variable we can copy just the namespace axis to the root element, and we will get the corresponding default namespace declaration here.
<xsl:template match="xsl:stylesheet" >
<!-- Store a temporary element from a XHTML namespace in the variable -->
<xsl:variable name="temp">
<xsl:element name="dummy" namespace="http://www.w3.org/1999/xhtml"/>
</xsl:variable>
<!-- Copy xsl:stylesheet element -->
<xsl:copy>
<!-- Copy just the namespace declarations from a dummy element -->
<xsl:copy-of select="exsl:node-set($temp)//namespace::*"/>
<!-- Copy original xsl:stylesheets attributes -->
<xsl:copy-of select="@*"/>
<!-- Process the content of the original stylesheet -->
<xsl:apply-templates/>
</xsl:template>
The complete stylesheet is a part of sample files. You can use it to convert almost any HTML stylesheet to a stylesheet that produces XHTML. The same method is used in DocBook XSL stylesheets to produce the XHTML version of stylesheets from the HTML version, which is the version on which real human development is done.
Localization Without Performance Loss
XSLT is often used to create a web site from XML sources. Many organizations today need multilingual websites. The common approach in creating such sites with XSLT is to store locale dependent messages in a special XML file known as the message catalog. Every time we need to display a language dependent text, we call a special template with parameters identifying the current language and the requested text. So instead of simply typing
<h1>Welcome!</h1>
in the non-localized stylesheet, we call template which returns correct welcome text in the desired language.
<h1>
<xsl:call-template name="gentext">
<xsl:with-param name="text">Welcome</xsl:with-param>
<xsl:with-param name="lang" select="$currentLang"/>
</xsl:call-template>
</h1>
These gentext templates usually utilize the
document() function to lookup the desired text in an external
message catalog.
This solution has two big drawbacks. Typing a long code for calling a template is very inconvenient, especially in comparison with writing a non-localized stylesheet. The second drawback is poor performance. A stylesheet must repeatedly look for messages in the message catalog during each server request.
Both of these drawbacks can be easily overcome using a simple solution. We will not create a real stylesheet, but just a stylesheet template that can be later transformed into specialized stylesheets for each language. The stylesheet template will be a real XSLT stylesheet, which will use elements from a special namespace instead of the text constants. For example the heading with the welcome text will be written as
<h1><msg:Welcome/></h1>
Such a template for a XSLT stylesheet can be merged with message
catalog for each language, elements from the msg namespace
will be replaced by a localized text, and we will get the XSLT stylesheet
for each supported language. Such stylesheets contain all localized text
directly, so there is no performance cost. Writing template stylesheets is
also much easier then the common solution using localization templates
invoked at runtime.
Our solution is better overall, only its management requirements are higher. We must process the stylesheet template into a real localized one every time a change is made to the template or to the message catalog. This transformation can be of course expressed as XSLT transformation and the whole process should be automated by Makefile, Ant task, or batch file.
|
| Figure 1. From the stylesheet template to localized stylesheets |
Let's explore the proposed solution deeper. Message catalogs are simple
XML documents. For each language there is one such a file named after ISO
language code (e.g. en.xml for English, cs.xml
for Czech). The sample catalog looks like this:
<?xml version="1.0" encoding="utf-8"?>
<l lang="en">
<text key="Invoice">Invoice</text>
<text key="Welcome">Hello and Welcome!</text>
<text key="Description">Description</text>
<text key="Quantity">Quantity</text>
<text key="UnitPrice">Unit price</text>
<text key="Subtotal">Subtotal</text>
<text key="Total">Total</text>
</l>
Note that key names correspond to local names of the msg:*
elements in the template stylesheet.
Now we need a transformation to replace all occurrences of the
msg:* elements with the corresponding texts from the
catalog. This can be easily expressed by the following stylesheet. It
copies all stylesheet parts to output unmodified except the
msg:* elements.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msg="urn:x-kosek:schemas:messages:1.0"
exclude-result-prefixes="msg"
version="1.0">
<xsl:output method="xml"/>
<xsl:param name="lang">en</xsl:param>
<xsl:param name="messages" select="document(concat($lang, '.xml'))/l"/>
<!-- Copy stylesheet untouched -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Replace msg:* elements with corresponding entry from message catalog -->
<xsl:template match="msg:*" priority="1">
<xsl:value-of select="$messages/text[@key = local-name(current())]"/>
</xsl:template>
</xsl:stylesheet>
You can download the sample stylesheet template with message catalogs and other files.
Conclusion
This article showed two real world advantages of XSLT being expressed in the XML syntax. This allows authors of stylesheets to manipulate with the XSLT code directly from their stylesheet and utilize it for various interesting effects. This functionality of XSLT is very similar to the concept of reflection known from other programming languages.
ResourcesXML.com's article about Namespaces and XSLT Stylesheets. Literate Programming in XML by Norman Walsh shows another very interesting usage of XSLT. XSL-List – open forum on XSL. |
Do you have a favorite use of XSLT reflection? Share comments and questions in our forum.
(* You must be a member of XML.com to use this feature.)
Comment on this Article
| Titles Only | Titles Only | Newest First |
- About Localization. How to improve it? [Corrected thread]
2005-03-27 09:54:18 knocte [Reply]
(Same message as the last one. When you read "lnk" tag, I mean an "a" tag, like the one present in XHTML.)
Hello Jirka. Firstly, congratulations for your great article.
I arrived to this stuff looking for localization methods using XSLT. I liked yours, but I think it doesn't suit to my needs. Do you know any other method which would be a bit more evolved?
The thing is that I would like to find (or implement by myself) a method like yours, but with the following additional features:
a) Ability to insert inline invariant terms into localized strings. Example: "Welcome to %1, we hope you have a good experience in our site."
b) Default language present on the primary non-translated template. You will be familiar with this concept if you have used Gettext. The "key" for each localized string is the string itself in the default language. This can be thought as being a disadvantage or an advantage. For example, instead of using <h1><msg:Welcome/></h1>, the approach would be something like <h1><msg>Welcome to the site</msg></h1>.
c) Finally, I have to say that in my experience of working with XML+XSLT conversion to extract usually output with XHTML format, I have found out that sometimes we need to translate strings that are inside attributes of tag elements. I mean, how could you translate this example with your technique?: <lnk title="Click here to go to the main section" href="#main">Go back</lnk>.
There, you cannot use a msg tag for localization inside an attribute (<lnk title="<msg>Click here...</msg>">...), because it would be an illegal XML document.
I am thinking on my own localization implementation using XSLT and it should be, so as to fit my three requisites, something like this:
- Part of the primary stylesheet:
<h1>{{You have to admit that %1% is your friend}John}.</h1>
<lnk title="* {{The best link, reported by %1% and %2%}Philip,Frank}. *">The Link</lnk>
- Dictionary XML for Spanish language:
<dictionary lang="es">
<expression key="You have to admit that %1% is your friend">Tienes que admitir que %1% es tu amigo</expression>
<expression key="The best link, reported by %1% and %2%">El mejor enlace, notificado por %1% y %2%</expression>
</dictionary>
- And the final hypothetical result, product of applying the XSLT transformation using the "es" dictionary, could
be:
<h1>Tienes que admitir que John es tu amigo.</h1>
<lnk title="* El mejor enlace, notificado por Philip y Frank. *">The Link</lnk>
Do you think this type of XSLT transformation can be implemented? Would you implement it this way? Do you think
there are any drawbacks about this kind of implementation? Could you think of any improvement or any additional interesting requisite for this localization system?
Thanks in advance.
Regards,
knocte
- About Localization. How to improve it? [Corrected thread]
2005-03-29 02:10:08 Jirka Kosek [Reply]
Ad c) -- you can create attributes with localized content using technique described in article. E.g.:
<lnk title="Click here to go to the main section">
<xsl:attribute name="href">
<xsl:text>#</xsl:text>
<msg:main/>
</xsl:attribute>
Go back
</lnk>
Regarding your proposal. I dont think that it is good idea to use non-XML syntax for marking up localized text (curly braces in your case). This will prevent you from using XML tools (including XSLT) to manage and process localized content.
But if you will replace curly braces with some purely XML based syntax you can quite easily implement everything you need.
Another possibility is to create stylesheet that will extract text to localize into PO file for gettext and then use custom XSLT extension function to call gettext to translate strings on the fly.
- About Localization. How to improve it? [Corrected thread]
- About Localization. How to improve it?
2005-03-27 09:41:40 knocte [Reply]
Hello Jirka. Firstly, congratulations for your great article.
I arrived to this stuff looking for localization methods using XSLT. I liked yours, but I think it doesn't suit to my needs. Do you know any other method which would be a bit more evolved?
The thing is that I would like to find (or implement by myself) a method like yours, but with the following additional features:
a) Ability to insert inline invariant terms into localized strings. Example: "Welcome to %1, we hope you have a good experience in our site."
b) Default language present on the primary non-translated template. You will be familiar with this concept if you have used Gettext. The "key" for each localized string is the string itself in the default language. This can be thought as being a disadvantage or an advantage. For example, instead of using <h1><msg:Welcome/></h1>, the approach would be something like <h1><msg>Welcome to the site</msg></h1>.
c) Finally, I have to say that in my experience of working with XML+XSLT conversion to extract usually output with XHTML format, I have found out that sometimes we need to translate strings that are inside attributes of tag elements. I mean, how could you translate this example with your technique?: Go back.
There, you cannot use a msg tag for localization inside an attribute (), because it would be an illegal XML document.
I am thinking on my own localization implementation using XSLT and it should be, so as to fit my three requisites, something like this:
- Part of the primary stylesheet:
<h1>{{You have to admit that %1% is your friend}John}.</h1>
The Link
- Dictionary XML for Spanish language:
<dictionary lang="es">
<expression key="You have to admit that %1% is your friend">Tienes que admitir que %1% es tu amigo</expression>
<expression key="The best link, reported by %1% and %2%">El mejor enlace, notificado por %1% y %2%</expression>
</dictionary>
- And the final hypothetical result, product of applying the XSLT transformation using the "es" dictionary, could be:
<h1>Tienes que admitir que John es tu amigo.</h1>
The Link
Do you think this type of XSLT transformation can be implemented? Would you implement it this way? Do you think there are any drawbacks about this kind of implementation? Could you think of any improvement or any additional interesting requisite for this localization system?
Thanks in advance.
Regards,
knocte
- About Localization. How to improve it?
2005-03-27 09:44:02 knocte [Reply]
Sorry, some elements haven't appeared in the post because they have been rendered in HTML. That's because this posting mechanism doesn't have a PREVIEW feature!
I will try to fix the message and post it again as another reply.
- About Localization. How to improve it?
- Real Reflection ?
2003-11-06 01:21:34 Franck Guillaud [Reply]
Well, I see a difference between Reflection in XSLT and Reflection in LISP (for instance):
you can't use this reflection property in a single pass transformation. We have no "eval($xslcode)" nor any way to dynamically create anonymous template. LISP has lambda functions and such, which allow to eval LISP code dynamically.
- Real Reflection ?
2005-12-07 07:22:20 jaypeevoss [Reply]
You may want to check out my XSLT reflection extension package for Xalan (named "Nalax") at
http://nalax.sourceforge.net. It is pretty fresh, so the quality is still pre-beta, but I'll try to stabilize it as quickly as possible (because I need it for a different project myself).
Regards,
Jens
- Real Reflection ?
2003-11-07 13:07:45 James Fuller [Reply]
there was some discussion about including an eval type definition in exslt at some point, but this never stuck...with the dyn:evaluate function being applied to the common scenario of manually building up an evaluating an XPATH statement.
reflection is a powerful concept, I dont need to statically bind my definition of it to an eval() function. All of its concepts are possible ( and facilitated with XSLT 2.0 ) through chaining transformations together.
- Real Reflection ?
2003-11-06 04:04:21 Oleg Tkachenko [Reply]
Well, first of all reflection is only reflection, and not necessarily includes on-the-fly code generation.
Anyway some sort of such is available via dyn:evaluate EXSLT extension function, which allows to evaluate dynamically generated XPath expressions, but AFAIK XSL WG decided not to include such functionality to XSLT/XPath 2.0 edition to keep XSLT strongly compiled language.
- Real Reflection ?
2003-11-07 04:57:59 Franck Guillaud [Reply]
University is far away from my memory now, but AFAIK reflection is the ability for a language to evaluate itself. Doesn't this imply evaluation of dynamical
constructs ?
- Real Reflection ?
2003-11-10 12:41:12 Dimitre Novatchev [Reply]
The FXSL functional programming library for XSLT makes it possible to dynamically generate new functions (and apply them). This can be done through a number of ways -- e.g. functional composition, partial application (currying) or what is equivalent to creation of new functions using lambda expressions.
Read for example:
http://www.idealliance.org/papers/extreme03/xslfo-pdf/2003/Novatchev01/EML2003Novatchev01.pdf
or the PPT presentation at:
http://www.idealliance.org/papers/extreme03/author-pkg/2003/Novatchev01/EML2003Novatchev01.zip
Dimitre Novatchev.
http://fxsl.sourceforge.net/ -- the home of FXSL
- Real Reflection ?
2003-11-07 06:41:46 Jirka Kosek [Reply]
It is a matter of definition, which can vary from person to person. But as Oleg said the reflection is only reflection -- it allows you to access code writen in some language within this language. This way works reflection in many other languages, e.g. Java.
Dynamic evaluation of the code during the runtime means that you have programmatic access to compiler/interpreter at runtime. This is a built-in feature of some langauges. XSLT doesn't offer this, but it should be possible (at least in theory) to implement XSLT processor in XSLT and then you will be able to do dynamic evaluation at the runtime.
- Real Reflection ?
2005-01-17 12:00:15 mnl05 [Reply]
I had a question regarding xsl revision. Once we dynamically generate xsl file and deploy it on cliet site, client might do some additional but restricted changes such as add few lines of text in between. In next phase, we might also do some changes in the xsl and now the challenge is to incorporate the changes the client has done in the previous version of xsl into new file we are going to deploy in this next phase. I would really appreciate the suggestion as to what would be the right approach.
Best regards,
mnl05
- Real Reflection ?
2005-01-18 07:03:49 Jirka Kosek [Reply]
You can do this if you make your stylesheets modular. In every place where user is expected to insert something place call for named template (xsl:call-template). This template will be empty by default.
Then deploy your stylesheet as two files -- main stylesheet and driver that will just import main stylesheet. Users will use driver stylesheet and once they want to modify something they can redefine appropriate named template in driver file.
Such way in a future you will just distribute main stylesheet after changes because all customizations will be stored in the driver file.
If you want more straightforward solution you can try to fiddle with tools like XmlDiff to propagate changes from user back to metadata that were used to create stylesheet dynamically.
- Real Reflection ?
- Real Reflection ?
- Real Reflection ?
- Real Reflection ?
- Real Reflection ?
