DTDs and Javascript don’t mix

Remember this diagram from a couple of posts ago:

XUL framework block diagram
XUL framework block diagram

After our previous exciting instalment some of this should start to make a bit more sense. In particular, that post explains why there’s a double-ended arrow going between the ASP.Net layer and the DTD files. In particular:

  1. If the DTD files don’t exist, or if the translations in the database change, the ASP.Net layer re-creates the files.
  2. Once they do exist, the ASP.Net layer is responsible for picking the appropriate DTD for the user’s language, and injecting it directly into the top of the XUL code.

But that doesn’t explain why there’s another arrow leading from the DTD files directly to Firefox. The reason for that is the big DTD-shaped elephant in the room known as Javascript.

Using our DTD entities in XUL, XHTML, SVG or any other XML-based language is easy:

<button id="btn1" label="&menu.file.save;" />

But this only works because the entity reference – our internal translation reference – is replaced with the translated string when Firefox first parses the XML content. This parsing doesn’t occur for Javascript, and it doesn’t occur if we try to use Javascript to insert an entity reference into the XML. Neither of these lines will do what we would like:

document.getElementById("btn1").setAttribute("label", "&menu.file.save;");

In each case the text appears literally as “&menu.file.save;” and never gets translated to “Save Changes”.

To try to work around this problem we have a function called tfs_get_string() for trying to find a suitable translation which is used whenever we want to access DTD text via Javascript. This function takes the entity reference as a parameter, then takes a three-step approach to try to find the translated string:

  1. Try to find a special hidden XUL element which maps the entity reference to the translated string.
  2. If that fails, try to find the translated string by parsing the DTD file directly.
  3. Finally, if that also fails, use some simple rules to guess at a human readable name for the entity reference.

The first step relies on the fact that in XUL it is legitimate for us to make up our own elements. We have an element called <tfs_string> which has two parameters – an ID and a Value. When we are creating an application we usually have a good idea of which strings we will need to access via Javascript, so the idea is that for each of these strings we add a suitable <tfs_string> element to the XUL file. Using the example above, we would have an entry like this:

<tfs_string id="menu.file.save" value="&menu.file.save;" >

In other words the ID is the entity reference but without the ampersand and semicolon (which means it doesn’t get translated when the XUL is initially parsed), whereas the Value is the full, translatable entity reference (which does get translated when the XUL is initially parsed). What we end up with when our XUL document is parsed is something like this:

<tfs_string id="menu.file.save" value="Save Changes" >

A simple CSS rule ensures that our <tfs_string> elements are hidden from view. The first approach our tfs_get_string() function takes is simply to search all the <tfs_string> elements for one with an ID that matches the supplied entity reference. If one is found, it returns the element’s Value as the translated string.

Sometimes our developers forget to add suitable <tfs_string> elements. Rather than have the application fail at this point, the second step of the function kicks in. First a warning is issued to the Javascript console, in the hope that the developer will spot it and add a <tfs_string> element before shipping the code. Secondly a copy of the DTD file is downloaded from the server, and the Javascript parses it directly to try to find a matching entity reference. Assuming one is found, the associated translation is returned.

Ideally there would be no need to download another copy of the DTD – the code would simply parse the copy that’s included at the top of the XUL file. Unfortunately I’ve yet to find a way to get a handle to that copy – if anyone has any good ideas (that work with remote XUL), then please let me know. In the meantime we’re left with the browser occasionally pulling a copy of the DTD directly from the web server – hence that second arrow on the diagram.

If no translation is found in the DTD – which can happen for entity references that are dynamically generated, or for which there simply hasn’t been an entry created yet – the function makes a last attempt to return something human readable. It may not be in the correct language, but it’s better than the entity reference itself. To create this string it simply takes the text after the last full-stop and performs a few simple transformations to guess at a human readable form. So menu.file.save will end up as “Save”, whilst menu.file.save_changes would end up as “Save Changes”.

In an ideal world there would be a simpler way to access the DTD strings from Javascript. However I have yet to find such a way. The XUL world does provide property files and string bundles as a way to work around this limitation, however they weren’t suitable for our needs for two reasons:

  1. Property files and DTD files are separate – that would mean maintaining two sets of overlapping translations and ensuring that the right bits got updated with every translation change.
  2. I wasn’t able to get stringBundles to work in remote XUL