Re: localization techniques and code review

Subject: Re: localization techniques and code review
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Mon, 20 Nov 2000 16:29:55 +0000

>> The nested xsl:for-each is necessary.  The key() function that you use
>> is scoped by the current node: it only looks for nodes within the
>> current document.  In the outer xsl:for-each, the current document is
>> your source XML.  You need to change it to the localization.xml
>> document to use the key to retrieve information quickly from it.
> I am confused about your statement about moving localization.xml to the
> outer xsl:for-each. How would that be more advantageous? Would it change my
> xsl:key?

Sorry, I obviously didn't explain what I meant very well.  You have
two xsl:for-eaches:

  <!-- outer xsl:for-each -->
  <xsl:for-each select="field">
    <xsl:variable name="field" select="." />
    <xsl:value-of select="$field" />
      <!-- inner xsl:for-each -->
      <xsl:for-each select="$trans">
            select="key('translate',concat($field,'-',$selectLang))" />

and you asked:

> 1. Am I on the right track? What about the use of nested
> <xsl:for-each select="document('localization.xml')">

I wasn't advocating moving localization.xml to the outer xsl:for-each,
but trying to explain why you need to have the nested xsl:for-eaches
that you have.

xsl:for-eaches change the current node. The current node is important
because it is the base to which XPaths and some functions are
relative. In this case, the key() function uses the current node to
work out what document to search for the nodes that you are indexing

If you didn't have the inner xsl:for-each, the key() function would
use the current node set in the outer xsl:for-each (i.e. the 'field'
element in your source XML) to work out what document to look in.
Since the 'field' element is in the source XML, the key() function
would look in the source XML document for the nodes.  You defined your
xsl:key as:

  <xsl:key name="translate" match="translation"
           use="concat(ancestor::word/@name,'-',@lang)" />

So, if there wasn't that inner xsl:for-each the key() function would
attempt to look for 'translation' elements in the source XML for that
particular word and language.  Since there aren't any 'translation'
elements in the source XML, it would come up with nothing.

So, the point of the inner xsl:for-each is to change the current node
to be a node in the localization.xml document.  With that as the
current node, the key() function correctly searches within the
localization.xml document.
> On another note, is there a way for me to modify the following statement to
> only return nodes which match lang=$selectLang attribute? (the idea is to
> filter non-chosen languages - could I use something like
> [@lang='$selectLang']??)
> <xsl:variable name="trans" select="document('localization.xml')" />
> How can the above statement be modified to achieve the above objective and
> is that even something to consider?

You could set your variable to just hold translations in a particular
language within the XSLT, but I think it would be more trouble than
it's worth.  To set your variable to hold just these translations, you
could use:

<xsl:variable name="trans"
              select="document('localization.xml')/localization/word/translation[@lang = $selectLang]" />

You could then do the equivalent of the above xsl:for-eaches with:

  <xsl:for-each select="field">
    <xsl:variable name="field" select="." />
    <xsl:value-of select="$field" />
      <xsl:value-of select="$trans[ancestor::word/@name = $field]" />

Generally this is likely to be slower than using keys, especially if
you have lots of fields to translate.

If you're worried about the size of localization.xml and consequently
the size of its DOM and the key's hashtable when the XSLT runs, then
you are probably better off thinking in terms of a pre-process that
filters the localization.xml file to only the language that you are
actually interested in before running the main stylesheet on it. You
could do this pre-processing with XSLT by creating a stylesheet that
copies the relevant parts of localization.xml. The main stylesheet
would then access the filtered information: you should change the way
the key is declared and used to reflect this.

> I am sorry. I think my choice of wording made you look deeper than I
> intended. (My knowledge is not deep enough to think of document caching at
> the server level :-)). I had meant to ask if I could re-print the results,
> another time without having to re-document() the localization.xml file,
> (perhaps in another style)? By clarifying the use of the $trans in:
> <xsl:variable name="trans" select="document('localization.xml')" />
> I know now that it is re-usable and has all the nodes inside it.

Just a clarification: it doesn't really have all the nodes inside it,
it just holds a reference to one node, the document's root node.  The
thing is that anything else in the document can be accessed from that
root node.  There's a difference between:

<xsl:variable name="trans" select="document('localization.xml')" />

which holds the root node of the document and:

<xsl:variable name="trans"
              select="document('localization.xml')/*/word" />

which holds all the 'word' elements in the document.

As an aside: probably as a hang over from using Saxon 5.4.something
where multiple document() calls accessed the relevant document
multiple times (a bug that's now fixed), I now always declare the
extra documents that I use as global variables, and access them
through that variable wherever I use them in the rest of the
stylesheet.  There's nothing wrong with accessing the document with
several document() calls, and processors will generally reuse the DOM
that they already have anyway, but having only one document() call
gives you greater maintainability 'cos you only have to edit the name
once if you move or rename the document, for example.

I hope that helps,


Jeni Tennison

 XSL-List info and archive:

Current Thread