Hacking the Technorati Badge or: How I Hacked Document.Write() in Order to Make it Work With application/xhtml+xml

I’ve reworked this code to make it unobtrusive and consequently lighter. See “Hacking the Technorati Badge part Deux” to read about it.
The first thing I noticed when I switched to application/xhtml+xml was how unforgiving the XML parser is with badly formed markup. It stops parsing and displays a big fat red error message where your page is supposed to be. The second thing I noticed was how my nifty little third party JavaScript includes all stopped working. I’m talking of course about my AdSense ads and my Technorati Badge (which is now back in my footer thank-you-very-much).

Looking into the mystery disappearance I found that I was getting a bunch of JavaScript errors like this:

uncaught exception: [Exception... "Object cannot be created in this context" code: "9" nsresult: "0x80530009 (NS_ERROR_DOM_NOT_SUPPORTED_ERR)" location:...

Putting two and two together I realized that “Object cannot be created in this context” and “ERROR_DOM_NOT_SUPPORTED” meant that JavaScript’s document.write() method wasn’t supported in the XML DOM.

Well, help after a few frustrating evenings of trying to write a string-to-html-DOM-nodes parser in JavaScript (yes, I can be that dumb sometimes) and a few philosophical discussions with Mathieu, I finally gave up. That is until he gave me the key to fixing this problem. You see, I got pretty far in my string-to-html-DOM-nodes parser work. The problem wasn’t that. The problem I was having–and what Mathieu so deftly solved–was in identifying where the document.write() was occuring. Maybe it would help at this point if I told you that my solution to getting document.write() working in application/xhtml+xml was to override the document.write() method and redirect it through a function of my own. That way I could manage what it did and essentially force it to play nice instead of crapping out. Every time I referred to this in my override code I just got the document object returned to me, rather than the DOM node that the script was being run in. (You know what I mean, when Technorati or AdSense tells you to just plop their ready-to-go, cut-and-paste <script> tag into your page.) So what, you may ask was Mathieu’s solution? A global variable set to the current script tag’s parent node’s ID. That way, the document.write() override could just grab that node and do its work inside it.

Then of course I also realized that I could replace my tedious, potentially error prone, and ultimately overkill code with a simple .innerHTML instead. That’s right, the non-standard workhorse .innerHTML which is no worse than using the W3C’s DOM methods (thanks PPK). So even though the purist in me was screaming for me to use my recursive beast of a node creator, the minimalist in me said “what are you crazy? Just use .innerHTML“.

Long story short (too late for that huh?) here’s the code. Oh and there’s an added function in there to clean up Technorati’s broken markup. Yes, you heard me, it’s broken.

The JavaScript:

var Utils = {
DWID : "",
DW : function(str){
var el = document.getElementById(Utils.DWID);
str = Utils.CleanTechnorati(str);
el.innerHTML = str;
},
CleanTechnorati : function(str){
return str.substring(0, str.indexOf("&#187")+5) + ";" + str.substring(str.indexOf("&#187")+5, str.length);
}
}
document.write = Utils.DW;

The XHTML:

<script type="text/javascript">Utils.DWID = "technorati_include";</script>
<li id="technorati_include"><script type="text/javascript" /></li>

So basically, every time you’ve got a script you want to include, you just change the value of the global variable just before and assign it the script’s parent element’s ID and you’re all set! Enjoy!

Tags: