// ==UserScript==
// @name XML tree
// @author TarquinWJ 
// @namespace http://www.howtocreate.co.uk/ 
// @version 1.1.1
// @description  Shows XML files in a syntax highlighted tree view,
//			that can be expanded and collapsed.
// @ujs:category general: developer_tools
// @ujs:published 2006-03-01 14:17
// @ujs:modified 2006-03-01 14:17
// @ujs:documentation http://userjs.org/scripts/general/developer_tools/xml-tree 
// @ujs:download http://userjs.org/scripts/download/general/developer_tools/xml-tree.user.js 
// @ujs:download.gm http://userjs.org/scripts/download/general/developer_tools/xml-tree.user.js 
// @exclude attachment:/*
// ==/UserScript==


/* 
 * Please see
 * http://www.howtocreate.co.uk/operaStuff/userJavaScript.html#terms
 * for License and Terms of Use
 */

(function () {

/*********************
Configure options here
*********************/

//Define root nodes to exclude - <html>, <wml>, and <svg> are essential and will always be excluded, but
//you may also want to exclude others like <rss> and <feed>, as these denote RSS and Atom newsfeeds.
//If a file is loaded that starts with one of these nodes, it will never be shown in XML tree form.
// for example: var excludedTypes = ['rss','feed'];
var excludedTypes = [];

//It is possible to use HTML elements in XML by prefixing them with <html: - I have never seen a real
//XML file using this without also using an XML stylesheet, but I have seen it inside Atom newsfeeds. Set
//this to true to protect files that use the prefix without an XML stylesheet, so they can render as HTML.
var protectHTMLNS = false;

//Large XML files will take a while to process and may make the browser appear to hang up while they are
//being processed. The script will warn you when the file is over the size specified here (KB).
var warnSize = 50;

//The font used on the page (I have to use a specified font, sorry, or it screws up when I use
//preformatting tags) - you can use a full CSS font family value if you want.
var pageFont = 'serif';

//Font size to use for the XML tree view - multiplied by your normal font size
//1 is normal, 2 is double size
var fontSize = 1;

//Percentage of height to devote to the notification bar at the top
//If you make this too small for your display, you will get an ugly double scrollbar
var barSize = 15;

//Show links to expand/collapse all - 0 = no, 1 = yes, 2 = yes and stay static as the page scrolls
var showECAll = 1;

//Configure the styles for displaying the XML file (remember that backgrounds show through the children) -
//in this format: ['background colour','colour','font weight']
  //the whole page
var defaults =   ['#fff',       '#000',   'normal'];
  //the banner at the top
var banner   =   ['#ccc',       '#000',   'normal'];
  //doctype tags
var doctypes =   ['transparent','#000',   'bold'];
  //processing instruction tags
var procinsts =  ['transparent','#77f',   'normal'];
  //normal element tags
var elemtags =   ['transparent','#808',   'bold'];
  //element attributes
var attribut =   ['transparent','#f90',   'normal'];
  //attribute values
var attribval =  ['transparent','#bb0',   'normal'];
  //XML comments
var xmlcomment = ['transparent','#5bb',   'normal'];
  //text nodes
var txtnodes =   ['transparent','#777',   'normal'];
  //CDATA blocks
var cdatablock = ['transparent','#d77',   'normal'];
  //expand/collapse links
var eclink =     ['transparent','#00f',   'normal'];

/*** Language strings ***/

//large XML file notice, used to construct a dialog: warnPreText warnSize warnMidText _actualSize_ warnPostText
var warnPreText  = 'XML tree script:\n\nThis file is over ';
var warnMidText  = 'KB (actual size is ';
var warnPostText = 'KB) and it may take a long time to produce the XML tree view. Your browser will appear to hang up while processing and may run slowly when displaying the XML tree.\n\n(Note, you can configure this warning threshold inside the User JavaScript file.)\n\nAre you sure you want to continue?';

//The notification at the top
var notification = 'This XML file does not appear to have any style information associated with it. The XML tree is shown below.';
var noticeGoBack = 'Go back to the rendered XML view.';

//notice when opening expand/collapse links in a new window (need to double escape any quotes or slashes)
var clickNormal = 'Please click the link normally';

/****************************************
***********   Stop editing!   ***********
****************************************/

function htmlspecialchars(oStr) {
	if( !oStr ) { return ''; }
	return oStr.replace(/&/g,'&amp;').replace(/\"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}

function inarray(oNeedle,oHaystack) {
	for( var i = 0; i < oHaystack.length; i++ ) {
		if( oHaystack[i] == oNeedle ) { return true; }
	}
	return false;
}

function getChildList(oOb) {
	for( var i = 0, j, s = '', emptyS = /^\s*$/; j = oOb.childNodes[i]; i++ ) {
		if( j.nodeType == 3 || j.nodeType == 4 ) {
			if( j.nodeValue.match(emptyS) ) { continue; }
			if( j.nodeType == 3 ) {
				s += '<li class=\"text\">'+htmlspecialchars(htmlspecialchars(j.nodeValue))+'<\/li>';
			} else {
				s += '<li class=\"cdata\"><pre>&lt;![CDATA['+htmlspecialchars(j.nodeValue)+']]&gt;<\/pre><\/li>';
			}
		} else if( j.nodeType == 1 ) {
			s += '<li class=\"element\">&lt;'+htmlspecialchars(j.tagName);
			for( var n = 0, m; m = j.attributes[n]; n++ ) {
				s += ' <span class=\"attrib\">'+htmlspecialchars(m.nodeName)+'=&quot;<span class=\"attrib-value\">'+htmlspecialchars(htmlspecialchars(m.nodeValue))+'</span>&quot;<\/span>';
			}
			s += '&gt;';
			if( !j.childNodes.length || ( j.childNodes.length == 1 && j.firstChild.nodeType == 3 && j.firstChild.nodeValue.length < 51 ) ) {
				s += ((j.firstChild&&!j.firstChild.nodeValue.match(emptyS))?('<span class=\"text\">'+htmlspecialchars(htmlspecialchars(j.firstChild.nodeValue))+'<\/span>'):'')+
					'&lt;\/'+htmlspecialchars(j.tagName)+'&gt;<\/li>';
			} else {
				s += '<a href=\"javascript:alert(\''+clickNormal+'\');\" onclick=\"toggleXML(this);return false;\">&ndash;</a><ul>'+getChildList(j)+'<\/ul>&lt;\/'+htmlspecialchars(j.tagName)+'&gt;<\/li>';
			}
		} else if( j.nodeType == 8 ) {
			s += '<li class=\"comments\"><pre>&lt;!--'+htmlspecialchars(j.nodeValue)+'--&gt;<\/pre><\/li>';
		}
	}
	return s;
}

excludedTypes = excludedTypes.concat('html','wml','wml:wml','svg');
if( !document.documentElement || inarray(document.documentElement.tagName.toLowerCase(),excludedTypes) ) { return; }
var foo = new XMLSerializer();
var theDocStr = foo.serializeToString(document);
if( theDocStr.indexOf('<?xml-stylesheet') + 1 || ( protectHTMLNS && theDocStr.indexOf('<html:') + 1 ) ) { return; }
if( ( theDocStr.length > ( warnSize * 1024 ) ) && !confirm(warnPreText+warnSize+warnMidText+Math.round(theDocStr.length/1024)+warnPostText) ) { return; }

var xmlStyles = 'html, body {\n'+
'	color: '+defaults[1]+';\n'+
'	background-color: '+defaults[0]+';\n'+
'	font-weight: '+defaults[2]+';\n'+
'}\n'+
'html, body, ul, li, pre, div {\n'+
'	font-family: '+pageFont+';\n'+
'	font-size: 1em;\n'+
'	margin: 0px;\n'+
'	padding: 0px;\n'+
'	display: block;\n'+
'}\n'+
'body {\n'+
'	font-size: '+fontSize+'em;\n'+
'}\n'+
'body > ul {\n'+
'	margin: 1em 0px;\n'+
'}\n'+
'div#excol {\n'+
(showECAll?'':'	display: none;\n')+
'	position: '+((showECAll==1)?'absolute':'fixed')+';\n'+
'	right: 46px;\n'+
'	top: 0px;\n'+
'}\n'+
'div#excol > a {\n'+
'	position: static;\n'+
'}\n'+
'li.doctyp {\n'+
'	color: '+doctypes[1]+';\n'+
'	background-color: '+doctypes[0]+';\n'+
'	font-weight: '+doctypes[2]+';\n'+
'}\n'+
'li.procinst {\n'+
'	color: '+procinsts[1]+';\n'+
'	background-color: '+procinsts[0]+';\n'+
'	font-weight: '+procinsts[2]+';\n'+
'}\n'+
'li.element {\n'+
'	color: '+elemtags[1]+';\n'+
'	background-color: '+elemtags[0]+';\n'+
'	font-weight: '+elemtags[2]+';\n'+
'}\n'+
'span.attrib {\n'+
'	color: '+attribut[1]+';\n'+
'	background-color: '+attribut[0]+';\n'+
'	font-weight: '+attribut[2]+';\n'+
'}\n'+
'span.attrib-value {\n'+
'	color: '+attribval[1]+';\n'+
'	background-color: '+attribval[0]+';\n'+
'	font-weight: '+attribval[2]+';\n'+
'}\n'+
'li.comments, li.comments pre {\n'+
'	color: '+xmlcomment[1]+';\n'+
'	background-color: '+xmlcomment[0]+';\n'+
'	font-weight: '+xmlcomment[2]+';\n'+
'}\n'+
'li.text, span.text {\n'+
'	color: '+txtnodes[1]+';\n'+
'	background-color: '+txtnodes[0]+';\n'+
'	font-weight: '+txtnodes[2]+';\n'+
'}\n'+
'li.cdata, li.cdata pre {\n'+
'	color: '+cdatablock[1]+';\n'+
'	background-color: '+cdatablock[0]+';\n'+
'	font-weight: '+cdatablock[2]+';\n'+
'}\n'+
'li {\n'+
'	margin-left: 1.5em;\n'+
'	position: relative;\n'+
'}\n'+
'a {\n'+
'	color: '+eclink[1]+';\n'+
'	background-color: '+eclink[0]+';\n'+
'	font-weight: '+eclink[2]+';\n'+
'	text-decoration: none;\n'+
'	position: absolute;\n'+
'	top: -0.2em;\n'+
'	left: -1em;\n'+
'	font-weight: bold;\n'+
'}\n'+
'pre {\n'+
'	white-space: pre;\n'+
'}';

var outStr = theDocStr.match(/^\s*(<\?[^>]*\?>\s*)+/);
if( !outStr ) { outStr = ''; } else {
	outStr = '<li class=\"procinst\">'+htmlspecialchars(outStr[0]).replace(/\?&gt;\s*&lt;\?/g,'?&gt;<\/li><li>&lt;?')+'<\/li>';
}
var d = document.doctype;
if( d ) {
	outStr += '<li class=\"doctyp\">&lt;!DOCTYPE '+htmlspecialchars(d.name)+
		(d.publicId ? (' &quot;'+htmlspecialchars(htmlspecialchars(d.publicId))+'&quot;'):'')+
		(d.systemId ? (' &quot;'+htmlspecialchars(htmlspecialchars(d.systemId))+'&quot;'):'')+
		'&gt;<\/li>';
}
outStr += getChildList(document);

outStr = '<!DOCTYPE HTML PUBLIC \"-\/\/W3C\/\/DTD HTML 4.0\/\/EN\" \"http:\/\/www.w3.org\/TR\/REC-html40\/strict.dtd\">\n'+
	'<html><head><title>'+htmlspecialchars(location.href)+'<\/title><style type=\"text\/css\">\n'+
	xmlStyles+'\n<\/style><script type=\"text\/javascript\">\n'+
	'function toggleXML(oEl) {\n'+
	'	if( !oEl.lastStVal && oEl.firstChild.nodeValue != \'+\' ) { oEl.lastStVal = oEl.firstChild.nodeValue; }\n'+
	'	oEl.firstChild.nodeValue = ( oEl.firstChild.nodeValue == \'+\' ) ? oEl.lastStVal : \'+\';\n'+
	'	oEl.nextSibling.style.display = oEl.nextSibling.style.display ? \'\' : \'none\';\n'+
	'}\nfunction toggleAll(oVal) {\n'+
	'	for( var v = 2, w; w = document.links[v]; v++ ) {\n'+
	'		if( ( w.nextSibling.style.display && !oVal ) || ( !w.nextSibling.style.display && oVal ) ) { w.onclick(); }'+
	'	}'+
	'}\n<\/script><\/head><body><div id=\"excol\"><a href=\"javascript:alert(\''+clickNormal+'\');\" '+
	'onclick=\"toggleAll(true);return false;\">[&ndash;]</a> <a href=\"javascript:alert(\''+clickNormal+'\');\" '+
	'onclick=\"toggleAll();return false;\">[+]</a></div><ul>'+outStr+'<\/ul><\/body><\/html>';

var replacedRootNode = document.createElement('hairyTurnip');
while(document.documentElement.firstChild) { replacedRootNode.appendChild(document.documentElement.firstChild); }

var posDiv = document.createElementNS('http://www.w3.org/TR/REC-html40','div');
posDiv.appendChild(document.createTextNode(notification+' '));
posDiv.appendChild(document.createElementNS('http://www.w3.org/TR/REC-html40','a'));
posDiv.lastChild.appendChild(document.createTextNode(noticeGoBack));
posDiv.lastChild.onclick = function () {
	//rendering bug fix for small XML files
	document.documentElement.firstChild.style = 'background-color: #fff; border-bottom-color: #fff; color: #fff;';
	document.documentElement.firstChild.lastChild.style = 'background-color: #fff; color: #fff;';
	setTimeout(function () {
		while(document.documentElement.firstChild) { document.documentElement.removeChild(document.documentElement.firstChild); }
		while(replacedRootNode.firstChild) { document.documentElement.appendChild(replacedRootNode.firstChild); }
	},1);
	return false;
};
posDiv.lastChild.href = 'javascript:alert(\''+clickNormal+'\');';
posDiv.style = 'width: 100%; height: '+barSize+'%; box-sizing: border-box; font-size: '+fontSize+'em; color: '+banner[1]+'; background-color: '+banner[0]+'; border-bottom: 3px solid '+banner[1]+'; font-weight: '+banner[2]+'; padding: 0px 8px; display: table; vertical-align: middle;';
posDiv.lastChild.style = 'color: '+eclink[1]+'; background-color: '+eclink[0]+';';
document.documentElement.appendChild(posDiv);

//(encod?encod[1]:'utf-8')
//var encod = theDocStr.match(/<\?xml\s+[^>]*encoding=["']([^"']*)["']/);
var ifr = document.createElementNS('http://www.w3.org/TR/REC-html40','iframe');
ifr.style = 'width: 100%; height: '+(100-barSize)+'%; border: none; display: block;';
ifr.src = 'data:text\/html;charset=utf-8,'+encodeURIComponent(outStr);
document.documentElement.appendChild(ifr);
if( !document.evaluate ) {
	//bug fix for Opera 8.5- (sometimes, files that load fast fail to create elements correctly -
	//reload-from-cache fixes it [seems to work every time, no infinite loops, I hope])
	if( !ifr.outerHTML ) { history.go(0); return; }
	//bug fix for Opera 8.5- (fails to dynamically set the src and href)
	ifr.outerHTML = ifr.outerHTML.replace(/<iframe/,'<iframe src=\"'+ifr.src+'\"');
}

})();