Introduction

The advantages of XML are slowly becoming apparent to the average web designer. Syndication formats have taken off, and AJAX has experienced an awakening onto the screen of hundreds of web applications. Yet, few have dared tackle XML namespaces, and bring integration of content such as XHTML, SVG, and MathML into the same document, and do so crossbrowser.

SVG

Gecko has supported XML namespaces the longest of any browser, initially utilized for XUL and XBL formats (which encourage use of elements from other namespaces), and then for MathML and SVG support. In Opera 8.5, a second decent SVG implementation was unleashed, and with recent WebKit builds, you can find the beginnings of an excellent renderer. Three native implementations, and the well-established Adobe SVG Plugin for IE -- why isn't inline SVG more common?

IE's requirement of a plugin is one motivating factor, as it isn't commonly known how to swap plugin content for embedded markup. However, IE supports a mangled version of XML namespaces (HTML namespaces is more like it), and combined with a proprietary processing directive, we can hack our way to victory. One limiting factor is, however, that inline SVG must be prefixed.

MathML

Gecko is the only browser to natively support MathML rendering. There is a page about MathML support on the WebKit website, but the lack of activity isn't encouraging. As for Opera, as far as I can tell, no MathML support is planned for the immediate future; Opera does support enough CSS3 to make a CSS-based semi-implementation feasible though. Lastly, Design Science created a MathML Plugin named MathPlayer for Internet Explorer, which does a fantastic job of rendering MathML. Via techniques identical to that of embedding SVG with IE, we can achieve prefixed, inline MathML markup.

XInclude

XInclude is a little known specification that allows XML documents to include others via an XPointer; picture a <?php include("file.xml"); ?>, but client-side instead of server-side. Since no browser supports XPointer either, the best we can do is a crippled XSLT-implementation using document().

Let us begin.

Statement of Problems

Our host document is XHTML. Internet Explorer doesn't support true XHTML, and it must parse it as HTML. However, when we're embedding multiple XML applications, the additional requirements of an XML parser make it much less prone to error.

Internet Explorer requires our XML markup to be prefixed. This is not in itself a problem, however when blocks of XML are in one namespace, I find it much neater to redeclare the default namespace instead of using prefixes everywhere. As a bonus, it reduces the size of the markup.

Solution

You may download all of the files in a single zip file, or view the source code below.

The host document (example.xhtml):

<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="preprocess.xsl"?> <html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:svg="http://www.w3.org/2000/svg"> <head> <title>Crossbrowser Compound XML Documents</title> <style type="text/css"> math\:math, math { display: block; margin-bottom: -1em; } h4 { margin: 0.5em 0; padding: 0; } div { float: left; padding: 10px; border: 3px solid #000; margin-right: 30px; } </style> </head> <body> <div> <h4>Actual Rendering:</h4> <xi:include href="demo.xml" xpointer="id('math')"/> <xi:include href="demo.xml" xpointer="id('svg')"/> </div> <div> <h4>Reference rendering:</h4> <img src="reference.png" alt="x^2+y^2=25"/> </div> <p>If the renderings are similar, then your web browser configuration supports MathML, SVG, and XSLT.</p> </body> </html>

The file to be included via XInclude (demo.xml):

<?xml version="1.0" encoding="UTF-8"?> <objects> <math xmlns="http://www.w3.org/1998/Math/MathML" id="math"> <semantics> <apply> <eq/> <apply> <plus/> <apply> <pow/> <ci>x</ci> <cn>2</cn> </apply> <apply> <pow/> <ci>y</ci> <cn>2</cn> </apply> </apply> <cn>25</cn> </apply> <annotation-xml encoding="MathML-Presentation"> <mrow> <msup> <mi>x</mi> <mn>2</mn> </msup> <mo>+</mo> <msup> <mi>y</mi> <mn>2</mn> </msup> <mo>=</mo> <mn>25</mn> </mrow> </annotation-xml> </semantics> </math> <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" version="1.1" id="svg"> <g transform="scale(10,-10) translate(10,-10)"> <path stroke="black" fill="none" stroke-width="0.05" d="M -10 0 L 10 0 M 0 10 L 0 -10"/> <circle stroke="red" fill="none" stroke-width="0.05" r="5" x="0" y="0"/> </g> </svg> </objects>

The preprocessing file (preprocess.xsl):

<?xml version="1.0" encoding="UTF-8"?> <stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xi="http://www.w3.org/2001/XInclude"> <!-- Copyright (C) 2006 Jason Davis, www.browserland.org This transformation (including hacks.xsl) is licensed under the LGPL: http://www.gnu.org/licenses/lgpl.html --> <import href="identity.xsl"/> <import href="hacks.xsl"/> <template match="xi:include"> <variable name="href" select="@href"/> <variable name="xpointer" select="@xpointer"/> <variable name="a" select="substring($xpointer, 5)"/> <variable name="b" select="string-length($a)"/> <apply-templates select="document($href)" mode="include"> <with-param name="id" select="substring($a, 1, $b - 2)"/> </apply-templates> </template> <template match="/" mode="include"> <param name="id"/> <variable name="uid" select="id($id)"/> <variable name="ids" select="//*[@id = $id]"/> <choose> <when test="count($uid) = 0"> <apply-templates select="$ids[1]"/> </when> <otherwise> <apply-templates select="$uid"/> </otherwise> </choose> </template> </stylesheet>

The hacking file (hacks.xsl):

<?xml version="1.0" encoding="UTF-8"?> <stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:svg="http://www.w3.org/2000/svg"> <!-- Insert IE-only code --> <template match="html:head[string(system-property('xsl:vendor')) = 'Microsoft']"> <element name="head" namespace="http://www.w3.org/1999/xhtml"> <element name="script" namespace="http://www.w3.org/1999/xhtml"> (function() { var l = String.fromCharCode(60), r = String.fromCharCode(62); document.write(l + 'object id="MathPlayer" classid="clsid:32F66A20-7614-11D4-BD11-00104BD3F987"' + r + l + '/object' + r); document.write(l + 'object id="AdobeSVG" classid="clsid:78156A80-C6A1-4BBF-8E6A-3CD390EEB4E2"' + r + l + '/object' + r); document.namespaces("math").doImport("#MathPlayer"); document.namespaces("svg").doImport("#AdobeSVG"); })() </element> <element name="script" namespace="http://www.w3.org/1999/xhtml"> <attribute name="for">window</attribute> <attribute name="event">onbeforeunload</attribute> <text> document.body.innerHTML = ""; </text> </element> <apply-templates select="node()|@*"/> </element> </template> <!-- Insert Gecko-only code --> <template match="html:head[string(system-property('xsl:vendor')) = 'Transformiix']"> <element name="head" namespace="http://www.w3.org/1999/xhtml"> <element name="style" namespace="http://www.w3.org/1999/xhtml"> <attribute name="type">text/css</attribute> <text><![CDATA[ math > semantics > * { display: none; } math > semantics > annotation-xml[encoding="MathML-Presentation"] { display: block; } ]]></text> </element> <apply-templates select="node()|@*"/> </element> </template> <!-- Prefix MathML and SVG elements if Internet Explorer --> <template match="*[string(system-property('xsl:vendor')) = 'Microsoft' and namespace-uri() = 'http://www.w3.org/1998/Math/MathML']"> <element name="math:{local-name()}"> <apply-templates select="node()|@*[name()!='xmlns']"/> </element> </template> <template match="*[string(system-property('xsl:vendor')) = 'Microsoft' and namespace-uri()='http://www.w3.org/2000/svg']"> <element name="svg:{local-name()}"> <apply-templates select="node()|@*[name()!='xmlns']"/> </element> </template> </stylesheet>

The identity file (identity.xsl):

<?xml version="1.0" encoding="UTF-8"?> <stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform"> <template match="node()|@*"> <copy><apply-templates select="node()|@*"/></copy> </template> </stylesheet>

Explanation

There is a lot of XSLT being thrown around, and too much to explain for anyone who hasn't worked with it before, but a casual explanation will be offered for the individual files.

The Host Document

The namespace declarations in the root element are essential for IE's well-being. Also, note the <?xml-stylesheet?> inclusion. We serve the document as plain-jane XML to IE, and XHTML (or just XML if we desire) to other browsers. Everybody understands XSLT nowadays, and they go on processing the document.

The Included File

This is just to demonstrate a nice aspect of XInclude - the ability to create a single file containing information-widgets and to reuse them. I assign them id attributes to reference them via my XPointers. This should be an XML document.

The Preprocessor

This document implements the crippled XInclude. It doesn't support fallback content (a very useful part of the specification), and only allows a very specific subset of XPointers, namely those that solely utilize the id() function. This is more than sufficient for my needs though.

The Hacking Document

This document goes through and outputs prefixed tags if the client is Internet Explorer (and a little Javascript to apply some HTML namespace voodoo), and outputs some special code for Mozilla as well to render combined Presentation and Content MathML markup nicely.

The Identity Document

Lastly, this is perhaps the most important (and most common) document of XSLT. It simply outputs the input -- deceptively simple, but immensely useful.

Compatibility

The XInclude costs Opera's support, as it doesn't seem to support enough of the XSLT specification to handle the transformation. If you wanted to cut it out, I'm sure that Opera would accept the transformation once more, but as it stands, the source code on this page only works in Internet Explorer and Firefox 1.5. This is hopefully, however, food for thought, and a push for a compound document future on the web.