SitePoint Sponsor

User Tag List

Results 1 to 4 of 4
  1. #1
    SitePoint Guru
    Join Date
    Feb 2005
    Posts
    602
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    rendering <A> elements from XML with Mozilla

    Note: I'm using Mozilla Firefox 1.0.

    Problem: When appending an imported XML node that happens to be a valid <A> element to an HTML node in the document, only the text within the link will render, not the link itself. That is, with:

    <a href="blank">blah</a>

    "blah" is rendered but not as a link - no underline and it can't be clicked as a link.

    However, when I checked what was added with innerHTML, it correctly displays the whole link markup (<a href="blank">blah</a>). It was also not simply a text node, since firstChild.nodeName returns the element name.

    Here's the html and javascript:
    Code:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
    <head>
    <script>
    	var xmlDoc = document.implementation.createDocument('', '', null)
    	xmlDoc.load('xml1.xml')
    	//xmlDoc.load('xml2.xml')
    	xmlDoc.onload = function () {
    		var root = xmlDoc.documentElement
    		//get first element
    		var node = root.firstChild
    		while (node.nodeType != 1)
    			node = node.nextSibling
    		test1(node)
    		test2(node)
    		test3(node)
    		test4(node)
    	}
    	
    	function test1(node) {
    		var div = document.createElement('div')
    		
    		var importedNode = document.importNode(node, true)
    		div.appendChild(importedNode)
    		
    		document.body.appendChild(div)
    		alert('test1: div.innerHTML=' + div.innerHTML + '; div.firstChild.nodeName:' + div.firstChild.nodeName)
    	}
    	
    	function test2(node) {
    		var div = document.createElement('div')
    		
    		var clonedNode = node.cloneNode(true)
    		div.appendChild(clonedNode)
    		
    		document.body.appendChild(div)
    		alert('test2: div.innerHTML=' + div.innerHTML + '; div.firstChild.nodeName:' + div.firstChild.nodeName)
    	}
    	
    	function test3(node) {
    		var div = document.createElement('div')
    		
    		var objXMLSerializer = new XMLSerializer()
    		var str = objXMLSerializer.serializeToString(node)
    		div.innerHTML = str
    		
    		document.body.appendChild(div)
    		alert('test3: div.innerHTML=' + div.innerHTML + '; div.firstChild.nodeName:' + div.firstChild.nodeName)
    	}
    	
    	function test4(node) {
    		var div = document.createElement('div')
    		
    		var range = document.createRange();
    		range.setStart(document.documentElement, 0)	//set bogus range position
    		var objXMLSerializer = new XMLSerializer()
    		var str = objXMLSerializer.serializeToString(node)
    		var docFragment = range.createContextualFragment(str)
    		div.appendChild(docFragment)
    		
    		document.body.appendChild(div)
    		alert('test4: div.innerHTML=' + div.innerHTML + '; div.firstChild.nodeName:' + div.firstChild.nodeName)
    	}
    </script>
    </head>
    <body></body>
    </html>
    The first XML test file (xml1.xml) - a is lowercase:
    Code:
    <?xml version="1.0" ?>
    <root>
    <a href="blank">blah</a>
    </root>
    The second XML test file (xml2.xml) - A is uppercase:
    Code:
    <?xml version="1.0" ?>
    <root>
    <A href="blank">blah</A>
    </root>
    test1() through test4() are the main algorithms.

    test1 (using importNode, appendChild):
    - text rendered
    - link NOT rendered
    - innerHTML returns (with xml1.xml): <a href="blank">blah</a>
    - firstChild.nodeName returns (with xml1.xml): a
    - innerHTML returns (with xml2.xml): <A href="blank">blah</A>
    - firstChild.nodeName returns (with xml2.xml): A

    test2 (using cloneNode, appendChild):
    - text rendered
    - link NOT rendered
    - innerHTML returns (with xml1.xml): <a href="blank">blah</a>
    - firstChild.nodeName returns (with xml1.xml): a
    - innerHTML returns (with xml2.xml): <A href="blank">blah</A>
    - firstChild.nodeName returns (with xml2.xml): A

    test3 (using XMLSerializer, innerHTML):
    - text rendered
    - link rendered
    - innerHTML returns (with xml1.xml): <a href="blank">blah</a>
    - firstChild.nodeName returns (with xml1.xml): A
    - innerHTML returns (with xml2.xml): <a href="blank">blah</a>
    - firstChild.nodeName returns (with xml2.xml): A

    test4 (using XMLSerializer, createRange, appendChild):
    - text rendered
    - link rendered
    - innerHTML returns (with xml1.xml): <a href="blank">blah</a>
    - firstChild.nodeName returns (with xml1.xml): A
    - innerHTML returns (with xml2.xml): <a href="blank">blah</a>
    - firstChild.nodeName returns (with xml2.xml): A

    test1 and test2 give identical results, and the same thing with test3 and test4. test3 and test4 are the only ones that work correctly.

    Why?

  2. #2
    SitePoint Wizard stereofrog's Avatar
    Join Date
    Apr 2004
    Location
    germany
    Posts
    4,324
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, I played with it a bit. Seems that nodes cloned or imported from different (non html) doctype are not treated as html nodes, but as generic Elements. Of course, Mozilla has no idea how to render generic Element and just skips it, displaying only underliying text node. With serialization it does work, because mozilla parses raw text as html and create html nodes by itself.

    I've found yet another solution, a kind of mixture of cloning and serialization.

    Code:
    Element.prototype.cloneTo = function(doc) {
    	var o = doc.createElement(this.nodeName);
    	for(var i = 0; i < this.attributes.length; i++)
    		o.setAttribute(
    			this.attributes[i].nodeName, 
    			this.attributes[i].nodeValue);
    	for(var i = 0; i < this.childNodes.length; i++)
    		o.appendChild(this.childNodes[i].cloneTo(doc));
    	return o;
    }
    Text.prototype.cloneTo = function(doc) {
    	return doc.createTextNode(this.nodeValue);
    }
    // etc etc
    // too lazy to define this for other nodeTypes...
    
    // let's test
    var xmlDoc = document.implementation.createDocument('', '', null)
    xmlDoc.load('xml1.xml')
    xmlDoc.onload = function () {
    	var node = xmlDoc.documentElement.firstChild
    	while (node.nodeType != 1)
    		node = node.nextSibling
    	document.body.appendChild(node.cloneTo(document))
    }
    Hope I didn't reinvent the wheel... Perhaps there is a cleaner solution possible, let me know if you find it.

  3. #3
    SitePoint Guru
    Join Date
    Feb 2005
    Posts
    602
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Actually, I already had some similar code - just deep copying the xml node into a html node. But I'm worried that it's inefficient compared to using XMLSerializer, which as I understand uses Mozilla's native parsing engine. I'll run some speed tests...

    In IE, the situation is solved easily just by using:

    div.innerHTML = node.xml

    Thanks for replying

  4. #4
    SitePoint Guru
    Join Date
    Feb 2005
    Posts
    602
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Latest speed test notes:

    -Mozilla: requires either using XMLSerializer or manual deep copy of node for node to be properly rendered
    -innerHTML is faster than appendChild if appending element
    -innerHTML is slower than appendChild if appending text node
    -Mozilla: manual deep copy of node is slower than XMLSerializer if node is not a text node (in which case it's much faster)
    -IE: manual deep copy of node is slightly slower than setting innerHTML/getting xml if node is not a text node (in which case it's much faster)
    -Mozilla: setting innerHTML is faster than using Range/createContextualFragment and appendChild
    -IE: insertAdjacentHTML is very slightly faster than setting innerHTML
    -Mozilla: note: great performance increase if Range and XMLSerializer is instantiated only once


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •