Please note that this page is being regularly updated as the script matures. Please check out the changelog to see what’s been added since it’s first release.
This is a solution to a niche problem. But even niche problems need solutions. A while back I started serving my pages with the application/xhtml+xml MIME type (in other words, as XML not HTML). The trouble was that all of the third party scripts on my site that used document.write()
to output their content stopped working. (To learn more about this issue read: Does document.write work in XHTML? and Why document.write() doesn’t work in XML.)
The solution isn’t as pretty as I had originally hoped, but it works.
My first attempt only worked with one script tag. My second attempt also worked with only one script tag, just using a different technique.
I then toyed around with an early version of the script below and it was a lot more elegant than it is now. It was more elegant because every time a document.write was called it checked to see how far along the DOM was in being built by counting the number of script tags in the page. It then dumped its contents at the last known script tag’s position. Very clean. I wish I could have used that. It’s just that it only worked in text/html mode. As soon as I switched MIME types to application/xhtml+xml, the parser stopped cooperating and everything ended up bunched together at one location (the first script tag’s position). That’s because when using the text/html MIME type you’re in the standard DOM where script tags are parsed as the DOM is being built. Not so with application/xhtml+xml. The DOM is built first and then all scripts are run afterwards. The code that’s being executed has no idea where it’s being called from in the DOM (because in real XHTML you’re not supposed to run scripts that adjust the DOM directly in the body of the document) so there’s no reference for me to point to and say “build your nodeset here”. So seeing as how there was a huge disconnect between the scripts themselves and where they were supposed to dump their contents, I had no choice but to take the route that I took.
The code supports multiple script tags as well as script tags with multiple document.write()s, something I couldn’t have hoped to support in the first two tries. The code below basically does the following:
I suppose I could cut out the need for step four by identifying the script tags by the contents of their src
attributes, but for now I’m just happy to get this thing working. Maybe in the next version. ;-)
So far I’ve tested it on Firefox 1.5(PC), IE 6(PC), Opera 8.5(PC), IE 5.2(Mac) and Safari 1.3.1(Mac) and they all seem to work fine. Take her for a spin and let me know how it works for you!
<script src="DocumentWriteOverride.js"></script>
<script>
window.onload = DocumentWriteOverride.Show; // of course you can bind it however you wish as long as it's on load
</script>
/*
Document Write Override v0.5
Written by Ara Pehlivanian (http://arapehlivanian.com)
This work is licensed under a Creative Commons Licence
http://creativecommons.org/licenses/by-nd/2.5/
*/
var DocumentWriteOverride = {
Signatures : [
["dw_technorati","<p id=\"te_l\"", ""],
["dw_feedburner","<div class=\"feedburner", ""],
["dw_measuremap","<script type='text/javascript' src='http://tracker.measuremap.com", ""]
],
Stack : [],
Store : function(str){
DocumentWriteOverride.Stack[DocumentWriteOverride.Stack.length] = str;
},
Show : function(){
var signatures = DocumentWriteOverride.Signatures;
var stack = DocumentWriteOverride.Stack;
var scripts = document.documentElement.getElementsByTagName("body")[0].getElementsByTagName("script");
var pointer = -1;
for(var i=0; i<stack.length; i++){
for(var j=0; j<signatures.length; j++){
if(stack[i].toString().indexOf(signatures[j][1]) != -1) pointer=j;
}
signatures[pointer][2] += stack[i];
}
for(var i=0; i<signatures.length; i++){
var node = document.getElementById(signatures[i][0]);
if(typeof node != "undefined"){
var str = DocumentWriteOverride.Cleaner.Clean(signatures[i][2].toString());
if(str.indexOf("<script") != -1){
var parentNodeRef = node.parentNode; //referencing the parent node directly causes the script to bomb
parentNodeRef.innerHTML += str.substring(0, str.indexOf("<script"));
DocumentWriteOverride.AddScript(parentNodeRef, str.substring(str.indexOf("<script"), str.indexOf("</script")+9));
parentNodeRef.innerHTML += str.substring(str.indexOf("</script")+9, str.length);
}else if(str.length > 0){
node.parentNode.innerHTML += str;
}
}
}
},
AddScript : function(parentNodeRef, fragment){
var delimiter = fragment.substr(fragment.indexOf("src=") + 4, 1);
var start = fragment.indexOf("src=") + 5;
var character = fragment.substr(start, 0);
var src = "";
while(character != delimiter){
src += character;
character = fragment.substr(start++, 1);
}
var script = document.createElement("script");
script.type = "text/javascript";
script.src = src;
parentNodeRef.appendChild(script);
},
Cleaner : {
Clean : function(str){
var tmpStr = str;
tmpStr = this.ReplaceAll(tmpStr, "» ", "&#187; "); // Technorati fix
tmpStr = this.ReplaceAll(tmpStr, "…<", "&#8230;<"); // FeedBurner fix
tmpStr = this.ReplaceAll(tmpStr, "&repeat=", "&repeat="); // MeasureMap fix
tmpStr = this.ReplaceAll(tmpStr, "&x=", "&x="); // MeasureMap fix
return tmpStr
},
ReplaceAll : function(str, from, to){ // Hat tip to Mark for this algo (http://www.experts-exchange.com/M_1235249.html)
var idx = str.indexOf(from);
while(idx > -1){
str = str.replace(from, to);
idx = str.indexOf(from);
}
return str
}
}
}
document.write = DocumentWriteOverride.Store;
–30–
Read more from the archive.