Skip to content

Rough Book

random musings

Menu
  • About Me
  • Contact
  • Projects
    • bAdkOde
    • CherryBlossom
    • FXCalendar
    • Sulekha
Menu

innerHTML alternative for XHTML documents in Firefox

Posted on July 1, 2005July 27, 2016 by vivin

I finally figured out an alternative to using innerHTML for an XHTML document in Firefox. Earlier I talked about trying to use innerHTML on my website, and how I ran into problems. Basically, the property is not supported for XHTML documents in Firefox. This makes sense if you think about it, because HTML isn't the same as XHTML. Since HTML isn't as strict as XHTML, there exists the possiblity of inserting badly formed code into the document, and result wouldn't be valid. From what I understand, this "problem" will be fixed in the 1.1 release of Firefox. Until then, I have an alternative. It may seem a little convoluted, but it really isn't all that bad.

My approach revolves around using the DOM Core Methods. There is also another way, and that uses XSLT. I haven't really looked into it, but I plan to, very soon. It could be faster.

Anyway, I realized that there was no way I could actually plug the XHTML code in directly into the document. I would have to parse it and create objects for each of the elements. But how do we parse the code? There really is no point writing our own parser, since it will be slow. Luckily, you can use the DOMParser object. This parser will let you parse an XML document. Since an XHTML document is essentially XML, you can use the DOMParser object to parse your code. The DOMParser object has three methods - parseFromBuffer, parseFromStream, and parseFromString. The one I used, is the last one. parseFromStream works like XMLHttpRequest, and allows you to read XML from a URI. I could have used that also, but parseFromString worked better for me.

The first thing you want to do, is create a DOMParser object. After that, you use parseFromString to parse your XML. parseFromString returns a Document object, which as you will see, can help us out a lot. So the code for parsing the XHTML looks like this:

[sourcecode language="javascript"]
var parser = new DOMParser();
var XMLdoc = parser.parseFromString("" + myXHTML + "");
[/sourcecode]

It is not necessary to enclose your XHTML snippet with a root tag, but it will take care of the situation where your snippet doesn't have a root element (so it would seem like it has more than one root element). In my case, I am inserting a snippet of XHTML code, and there is no root element, which is why I am enclosing the code with those tags.

I thought that once I had the Document object, I could simply figure out what the children of root are, and simply add them to the DOM by using appendChild. But that didn't work out. This is due to the fact that XMLdoc is an XML Document, and not an HTML Document. Plugging it into the DOM directly won't really help. What we have to do is figure out a way to transform the XML Document into an HTML Document. The sure-fire way to do that is to use XSLT. I poked around some code and realized that there was much I had to learn about XSLT before I could try to use it. My only other option was to do it through Javascript. This, as it turned out, wasn't so bad. The Document object is essentially a tree, and all you'd have to do is "walk" the tree. While walking the tree, you can also create an object tree that you can later append to the main DOM.

I wrote a recursive function called walkTree that does exactly this. On a side-note, I am aware of the existence of the TreeWalker object, but I was too impatient to figure out how exactly it worked. It may actually prove to be faster, but who knows - maybe I will give it a try someday. Anyway, all those Data Structures and Algorithms classes that I had to take in College came to good use. My basic algorithm is this - you walk the tree, and every time you encounter a node, you create the appropriate XHTML element using createElementNS. First let's look at the code to traverse the tree. Here is the basic skeleton that I use:

[sourcecode language="javascript"]
function walkTree(node)
{
if(node.hasChildNodes())
{
node = node.firstChild;

do
{
alert("The node is " + node.nodeName +
" and its value is " + node.nodeValue
);

walkTree(node);
node = node.nextSibling;
}
while(node);
}
}
[/sourcecode]

Now we have to figure out how to use this skeleton to create elements and append them to the DOM. Like I mentioned before, you can create elements in XHTML using the createElementNS object. So you could do:

[sourcecode language="javascript"]
var element = createElementNS("http://www.w3.org/1999/xhtml", tagName);
[/sourcecode]

The URI in createElementNS basically identifies the namespace we want to use. In this case, we want to use the XHTML namespace defined by the W3C. Now that we have created the element, we want to set its attributes. However, we don't really know what the attributes are at runtime. But we can still access them using the attributes property of a node. The attributes property of a node is an array that contains all the attributes and their values. Now we have to figure out how to copy the attributes over to the element. There really is no easy way to do this. The only way I could figure out how to do this, was to construct Javascript code and use the eval() function. The reason for this is that every location in the attributes array is of type Node. The nodeName and nodeValue properties give us the attribute name and the attribute value respectively. So the code to set attributes would look like:

[sourcecode language="javascript"]

for(var i = 0; i < node.attributes.length; i++) { var currAttribute = node.attributes[i]; if(currAttribute.nodeName == "style") { applyStyle(element, currAttribute.nodeValue); } else if(currAttribute.nodeName == "class") { element.className = currAttribute.nodeValue; } else { eval("element." + node.attributes[j].nodeName + " = \"" + node.attributes[j].nodeValue + "\"" ); } } [/sourcecode] Pretty straightforward, but why do we have the if statements for "style" and "class"? Well, I'm sure you can see why you need the if statement for "class". The attribute name in Javascript is different from the attribute name used in the XHTML code. But what's that applyStyle function? Well, that function translates the CSS style into the Javascript format. So for example, if we had a style background-color:#ffffff;, the Javascript equivalent is obj.style.backgroundColor = "#ffffff";. I wrote a simple function that performs this translation:

[sourcecode language="javascript"]
function applyStyle(obj, style)
{
// Remove all whitespaces, and remove terminating ; if any
style = new String(style).replace(/\s/g, "").replace(/;$/, "");

// Split into an array of attribute:value pairs
var attributes = style.split(";");

for(var i = 0; i < attributes.length; i++) { // Split the pair into the attribute and the value // Then translate the CSS style attribute into Javascript var attrvalue_pair = attributes[i].split(":"); var attr = attrvalue_pair[0].replace(/-[a-z]/g, function($1) { return new String($1).toUpperCase().replace(/-/g, ""); } ); // eval the constructed Javascript code eval("obj.style." + attr + " = \"" + attrvalue_pair[1] + "\""); } } [/sourcecode] Now we can put it all together: [sourcecode language="javascript"] function insertXHTML(myXHTML, myObject) { var parser = new DOMParser(); var XMLdoc = parser.parseFromString(";" + myXHTML + "", "text/xml");

if(XMLdoc.documentElement.nodeName == "parserror")
{
alert("Your XML document has errors");
}

else
{
walkTree(XMLdoc.childNodes[0], myObject);
}
}

function walkTree(node, parent)
{
// Failing case for recursion. We don't want to continue
// if the node has no children.

if(node.hasChildNodes())
{
// Set the node pointer to the first child of the current node
// and start looping through all the children

node = node.firstChild;

do
{
// We don't want any whitespace (line breaks, carriage returns, tabs or spaces)

if(!/^[\t\r\n ]+$/.test(node.data))
{
var element;

// Each node has a nodeType. The nodeType for a TextNode is 3. We want to handle
// Text Nodes separately from nodes that are tags

if(node.nodeType != 3)
{
// Create a new element using createElementNS as this is an XHTML file

element = document.createElementNS("http://www.w3.org/1999/xhtml", node.nodeName);

// Here, we manage the set the element's attributes by copying them from the node.
// Notice that we want to handle the style and class attributes separately.
// The applyStyle function uses the CSS and creates the equivalent Javascript code
// for the style object of the current element. Look at the applyStyle function for
// more information

if(node.attributes)
{
for(var j = 0; j < node.attributes.length; j++) { var currAttribute = node.attributes[j]; if(currAttribute.nodeName == "style") { applyStyle(element, currAttribute.nodeValue); } else if(currAttribute.nodeName == "class") { element.className = currAttribute.nodeValue; } else { // I guess this might be a hack, but I couldn't think of any other way to do it. // here we set any other attributes that the node may have. We construct Javascript // code to set the attributes and eval it. nodeName is the attribute name, and nodeValue // is the attribute value eval("element." + currAttribute.nodeName + " = \"" + currAttribute.nodeValue + "\""); } } } } else { // If the element is a text node, we create a TextNode element element = document.createTextNode(node.nodeValue); } // We make a recursive call to the function, to continue traversing the tree walkTree(node, element); // We append the element that we have created, to the parent element parent.appendChild(element); } // Move onto the next sibling of the current node node = node.nextSibling; } while(node); } } [/sourcecode] And there it is, folks. As easy as pie! I've found that it's not that slow, especially if you're parsing it just once. I'm using this same code for the Live! Preview, and it gets noticeably laggy when the code to be inserted is large. I guess if I was using XSLT, it wouldn't be this slow. That's going to be my next project! Feel free to use this code, and I hope it helps you out. Also, if you find ways to make it better/faster, or find bugs, or if you hate/love it, don't hesitate to comment on it. Update: It was pointed out to me that there is a much simpler way of inserting XHTML into the document. You can use the importNode function, instead of walking the tree and manually copying the attributes. There doesn't seem to be a speed advantage, but it is much simpler:

[sourcecode language="javascript"]
function insertXHTML(myXHTML, myObject)
{
var parser = new DOMParser();
var XMLdoc = parser.parseFromString("

" + comment + "

", "application/xhtml+xml");

var root = XMLdoc.documentElement;

for(i = 0; i < root.childNodes.length; i++) { myObject.appendChild(document.importNode(root.childNodes[i], true)); } } [/sourcecode] I wish I had known about this function earlier - it would have saved me a whole lot of trouble! References

  • Developer Guide - Parsing and Serializing XML
  • Parsing and serializing X - MozillaZine Knowledge Base
  • Whitespace in the DOM by L. David Baron

6 thoughts on “innerHTML alternative for XHTML documents in Firefox”

  1. Nobody says:
    July 22, 2005 at 3:09 pm

    Thanks man! I had a method similar to your second one, but it wouldn’t show the pictures in Firefox, and it would import only with the container node.

    These two parts fixed the whole problem:

    var root = XMLdoc.documentElement;
    …(document.importNode(root.childNodes[i], true));

    Check out my script

    Reply
  2. vivin says:
    July 27, 2005 at 8:08 pm

    Hey,

    Glad you found it useful – and I checked out your script. Pretty cool – I guess you were having the same problem I was.

    Reply
  3. rolandog says:
    July 8, 2006 at 2:26 am

    Thanks for this writeup. I was worrying of a standards compliant way of having comment previews. 😀

    Reply
  4. Ian says:
    February 27, 2007 at 9:24 am

    You’re the man

    Reply
  5. Scott Marcus says:
    March 29, 2011 at 5:40 pm

    Shouldn’t line 04 of the second solution be:

    var XMLdoc = parser.parseFromString(“” + myHTML + “”, “application/xhtml+xml”);

    rather than:

    var XMLdoc = parser.parseFromString(“” + comment + “”, “application/xhtml+xml”);

    NOTE: The variable “comment” (which is never declared) is replaced with the parameter “myHTML” (which was declared but never used).

    Reply
    1. vivin says:
      April 6, 2011 at 9:39 am

      @Scott You’re right!

      Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

Archives

  • February 2023
  • April 2020
  • February 2020
  • January 2020
  • December 2019
  • November 2019
  • September 2019
  • August 2019
  • July 2019
  • June 2019
  • May 2019
  • March 2019
  • February 2019
  • January 2019
  • December 2018
  • November 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • January 2018
  • December 2017
  • November 2017
  • October 2017
  • June 2017
  • March 2017
  • November 2016
  • August 2016
  • July 2016
  • June 2016
  • February 2016
  • August 2015
  • July 2014
  • June 2014
  • March 2014
  • December 2013
  • November 2013
  • September 2013
  • July 2013
  • June 2013
  • March 2013
  • February 2013
  • January 2013
  • October 2012
  • July 2012
  • June 2012
  • January 2012
  • December 2011
  • November 2011
  • October 2011
  • September 2011
  • July 2011
  • June 2011
  • May 2011
  • February 2011
  • January 2011
  • December 2010
  • November 2010
  • October 2010
  • September 2010
  • July 2010
  • June 2010
  • May 2010
  • April 2010
  • March 2010
  • January 2010
  • December 2009
  • November 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • August 2008
  • March 2008
  • February 2008
  • November 2007
  • July 2007
  • June 2007
  • May 2007
  • March 2007
  • December 2006
  • October 2006
  • September 2006
  • August 2006
  • June 2006
  • April 2006
  • March 2006
  • January 2006
  • December 2005
  • November 2005
  • October 2005
  • September 2005
  • August 2005
  • July 2005
  • June 2005
  • May 2005
  • April 2005
  • February 2005
  • October 2004
  • September 2004
  • August 2004
  • July 2004
  • June 2004
  • May 2004
  • April 2004
  • March 2004
  • February 2004
  • January 2004
  • December 2003
  • November 2003
  • October 2003
  • September 2003
  • July 2003
  • June 2003
  • May 2003
  • March 2003
  • February 2003
  • January 2003
  • December 2002
  • November 2002
  • October 2002
  • September 2002
  • August 2002
  • July 2002
  • June 2002
  • May 2002
  • April 2002
  • February 2002
  • September 2001
  • August 2001
  • April 2001
  • March 2001
  • February 2001
  • January 2001
  • December 2000
  • November 2000
  • October 2000
  • August 2000
  • July 2000
  • June 2000
  • May 2000
  • March 2000
  • January 2000
  • December 1999
  • November 1999
  • October 1999
  • September 1999
©2023 Rough Book | Built using WordPress and Responsive Blogily theme by Superb
All original content on these pages is fingerprinted and certified by Digiprove