XHTML document.write() Support
Latest commit 4dcf9a4 Nov 27, 2012 @westonruter Merge pull request #1 from rafalwrzeszcz/feature/fix-attributes-escaping
Attributes content should be "unescaped", because browser engines escape...
Failed to load latest commit information.


XHTML document.write() Support

Note: this code is very old and is no longer supported and really isn't needed since application/xhtml+xml is dead. This writeup and code is left here for historical reasons. Only one example still works due to changes in Google's API offerings. I've also stored the old comments I got on my blog post before I started redirecting here to GitHub.

Update 2008-10-10: Minor fix for "parse error" issue that arose in Firefox 3.0.3 (thanks pinkduck).

Update 2008-06-05: Made modifications to HTMLParser to support more usages, and reverted the changes to it that turned the local variables into member properties since it wasn't necessary.

Update 2008-06-04: Added discussion about supported usages.

Google's AJAX APIs provide some incredible tools which equip web developers to do some amazing things. I've lately been dazzled by the power and accuracy of their AJAX Language API and also of the performance and convenience afforded by their AJAX Libraries API. The only issue I have with their APIs is that the fundamental google.load() function uses document.write() to output the necessary script elements to the DOM, which doesn't even appear to be necessary since google.setOnLoadCallback() executes after the scripts are loaded without using document.write() (but instead appended document with DOM methods). And the reason why using document.write() is bad, of course, is that it is not available when in XHTML.

Likewise, Google's AdSense program provides a great way for web authors to make get some compensation for their hard work. But it too relies on document.write() to output the necessary iframe element to display the advertisement. This has been well noted, and a workaround has been developed which utilizes the an object element. However, there is another solution which enables AdSense to work in XHTML without any HTML workaround, and which allows web authors to use the Google Ajax APIs in XHTML pages: simply define and implement document.write() yourself, as this script does.

When I set out to do this, somehow I completely missed a solution developed by John Resig (one of my biggest JavaScript heroes). My solution, however, has a couple advantages as I see it. First, his solution uses some regular expression hacks to attempt to make the HTML markup well-formed enough for the browser's XML parser, but as he notes, it is not very robust. Secondly, John's solution relies on innerHTML which causes it to completely fail in Safari 2 (although this implementation also fails for an unknown reason). I'm trying a different approach. Instead of using innerHTML, this implementation of document.write() parses the string argument of HTML markup into DOM nodes; if the DOM has not been completely loaded yet, it appends these DOM nodes to the document immediately after the requesting script element; otherwise, it appends the parsed nodes to the end of the body.

I've incorporated John Resig's own HTML Parser (via Erik Arvidsson), but I've made a couple key modifications to make it play nice with document.write(). I turned HTMLParser into a class with member properties in order to save the end state of the parser after all of the buffer has been processed. To this class I added a parse(moreHTML) method which allows additional markup to be passed into the parser for handling so that it can continue parsing from where it had finished from the previous buffer. And by removing the last parseEndTag() cleanup call (for document.write() is anything but clean), it then became possible for multiple document.write() calls to be made with arguments consisting of chopped up HTML fragments like just a start tag or end tag, which is exactly what AdSense does and is a common usage of the method.

Browser Support

This document.write() implementation is known to work at least in Firefox 2/3, Opera 9.26, and Safari 3. It will work in Internet Explorer, of course, since the document must be served as text/html to be viewed and so it will already have document.write(). For some unknown reason, this currently does not work in Safari 2: the error “TypeError - Undefined value” is raised on the line of HTML where a script element loads xhtml-document-write.js. Any help would be much appreciated. As a workaround in the mean time, simply serve documents as text/html for Safari 2 browsers.

Supported Usages

There are three common usages of document.write() in the wild of HTML, and the first two are currently supported:

  1. Outputting a well-formed HTML code fragment:

    document.write('<p>Hello <i>World</i>!</p>');

    This usage is fully supported by this implementation.

  2. Outputting a well-formed HTML code fragment spread out over multiple sequential function calls:

    document.write('Hello <i>World</i>!');

    This usage is also supported

  3. script elements with function calls outputting HTML fragments interspersed by arbitrary HTML elements:

    <script type="text/javascript">document.write('<b>');</script>
    Hello <i>World</i>!
    <script type="text/javascript">document.write('</b>');</script>

    This is not supported. Instead of outputting “Hello World!”, this implementation would output one empty b element, followed by just “Hello World!” This usage is more difficult to support although I have an idea of how to do it, but I may not end up implementing it unless there is demand for it.

One restriction, of course, to the use of this implementation is that the entire document must be well-formed XHTML without regard for the markup output by the calls to document.write(); thus you cannot do something like this (via Ian Hixie):

 <script type="text/javascript" xmlns="http://www.w3.org/xhtml/1999"/><[!CDATA[

Developed by Weston Ruter (@westonruter).