Ponify

By Br0nyPony Last update Sep 17, 2012 — Installed 497 times.
// ==UserScript==
// @name           Ponify
// @namespace      http://pterocorn.blogspot.com
// @include        *
// ==/UserScript==
/*

	Copyright 2011 Ben Ashton <ben_ashton@gmx.co.uk>

	Ponify is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Ponify is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Ponify; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
	MA 02110-1301, USA.
	
*/

var Ponify = {};

//
// Please note that the documentation provided here is very breif. For a full tutorial on how to
// configure the Ponify UserScript, please go to the project page on my blog and navigate to the
// ponify section. There is also a video available which includes a basic tutorial on editing
// this script.
//
// http://pterocorn.blogspot.com
//


// 0 = No highlighting or tooltips
// 1 = Display tooltips and highlighting
// 2 = Tooltips only
// 3 = Highlighting only
Ponify.highlight = [0, "#0700FF"];


//Configure the list of replacements.
Ponify.replace = [	
	["confound those dover boys", "confound these ponies"],
	["what the heck", "what the hay"],
	["what the hell", "what the hay"],
	["philadelphia", "fillydelphia"],
	["sure as hell", "sure as hay"],
	["no one else", "no pony else"],
	["doctor who", "doctor whooves"],
	["stalingrad", "stalliongrad"],
	["girlfriend", "fillyfriend"],
	["old-timer", "old-trotter"],
	["everybody", "everypony"],
	["gentlemen", "gentlecolts"],
	["manhattan", "manehattan"],
	["trollable", "paraspritable"],
	["high-five", "hoof-five"],
	["boyfriend", "coltfriend"],
	["bro-fist", "bro-hoof"],
	["handedly", "hoofedly"],
	["naysayer", "neighsayer"],
	["butthurt", "flankhurt"],
	["everyone", "everypony"],
	["handmade", "hoofmade"],
	["trolling", "paraspriting"],
	["miracles", "mareacles"],
	["somebody", "somepony"],
	["bro fist", "bro hoof"],
	["marathon", "mareathon"],
	["someone", "somepony"],
	["america", "amareica"],
	["brofist", "brohoof"],
	["cowgirl", "cowpony"],
	["miracle", "mareacle"],
	["foolish", "foalish"],
	["dr. who", "dr. whooves"],
	["trolled", "parasprited"],
	["anybody", "anypony"],
	["persons", "ponies"],
	["ladies", "fillies"],
	["dr who", "dr whooves"],
	["trolls", "parasprites"],
	["people", "ponies"],
	["tattoo", "cutie mark"],
	["cowboy", "cowpony"],
	["humans", "ponies"],
	["person", "pony"],
	["nobody", "nopony"],
	["woman", "mare"],
	["boner", "wingboner"],
	["troll", "parasprite"],
	["human", "pony"],
	["hands", "hooves"],
	["folks", "foalks"],
	["girl", "filly"],
	["main", "mane"],
	["fool", "foal"],
	["feet", "hooves"],
	["hand", "hoof"],
	["foot", "hoof"],
	["boy", "colt"],
	["fap", "clop"],
	["man", "stallion"],
	["men", "stallions"]
];


// 0 = Blacklist
// 1 = Whitelist
Ponify.wlist_type = 0;


// Configure which websites are ponified and which ones aren't.
Ponify.websites = [
//	["example.com"],
//	["example.net"]
];

// 0 = Disabled
// 1 = Enabled
Ponify.pseudo_threading = 0;


//
// Everything below here can be left alone. That is, of course, unless you want to make some more
// advanced changes. If you do make any changes which significantly improve Ponify's performance
// or level of functionality, please consider emailing me your changed version so that I can
// make Ponify even better for everypony!
//


Ponify.urlCheck = function(){
	var url = location.href;	

	if(!this.websites.length && this.wlist_type){ return; }

	var r = /([^\/]+:\/\/)?(www\.)?(([^\/]*)[^\?#]*)/;
	var a = r.exec(url)[3];

	for(var i = 0; i < this.websites.length; i++){
		var b = r.exec(this.websites[i][0])[3];
		if((a.substr(0, b.length) == b) != this.wlist_type){ return; }
	}

	// Sort the replacement list by length to give priority to larger matches
	this.replace.sort(function(a, b){
			return a[0].length < b[0].length ? 1 : -1;
	});
		
	return true;
}

Ponify.adaptive_case_word = function(word, rep){
	var m = Math.min(rep.length, word.length);

	var r = '';
	var c = 0;
	
	var a = 0;

	var fix_me = [];

	for(var i = 0; i < word.length; i++){
		var t = (word[i] != word[i].toLowerCase());
		c += t;
		
		if(i < m){
			if(/\W|_|\d/.test(word[i])){
				r += rep[i];
				fix_me.push(i);
				a++;
			} else{
				r += t ? rep[i].toUpperCase() : rep[i];
			}
		}
	}
	
	if(a != word.length){
		var avg = Math.round(c / (word.length - a));
	} else{
		var avg = 0;
	}
				
	if(avg){
		for(var i = fix_me.length - 1; i >= 0; i--){
			r = r.substr(0, fix_me[i]) + rep[fix_me[i]].toUpperCase() + r.substr(fix_me[i]+1)
		}
	}
	
	if(rep.length > word.length){
		r += avg
			? rep.substring(word.length).toUpperCase()
			: rep.substring(word.length);
	}

	return r;
}


Ponify.adaptive_case_multiword = function(word, rep){
			
	var ca = [];

	for(var i in word){
		// Prevent small words from polluting the results
		if(word[i].length < 2){ continue; }
		for(var j in word[i]){
			if(/\W|_|\d/.test(word[i][j])){ continue; }
			var c = word[i][j] != word[i][j].toLowerCase();
			if(ca[j] == undefined){
				ca[j] = [];
				ca[j][0] = c;
				ca[j][1] = 1;
			} else{
				ca[j][0] += c;
				ca[j][1]++;
			}
		}
	}

	var c = 0;
	var l = 0;
	
	var fix_me = [];
	
	//Calculate the average case pattern
	for(var i = 0; i < ca.length; i++){
		if(ca[i] != undefined){
			l++;
			ca[i] = Math.round(ca[i][0] / ca[i][1]);
			c += ca[i]
		} else{
			fix_me.push(i);
		}
	}

	// Calculate the average case
	if(ca.length){
		ca.avg = Math.round(c / l);
	} else{
		ca.avg = 0;
	}
	
	// Fill any holes in the average pattern with the average case
	for(var i in fix_me){
		ca[fix_me[i]] = ca.avg;
	}

	var replaced = []

	for(var i = 0; i < rep.length; i++){
		replaced[i] = "";		
		
		switch(i < word.length){
			case true:
				var wl = word[i].replace(/\W|_|\d/g,"");
				var rl = rep[i].replace(/\W|_|\d/g,"");
				if((wl.length > 2 || wl.length >= rl.length) || !ca.length){
					replaced[i] = this.adaptive_case_word(word[i], rep[i]);
					break;
				}
			default:
				for(var j in rep[i]){
					replaced[i] += (j < ca.length ? ca[j] : ca.avg) ?
						rep[i][j].toUpperCase() : rep[i][j];
				}
				break;					
		}
	}

	return(replaced.join(" "));
}

// Take two words and attempt to make the case of the first word match that
// of the second with as much accuracy as possible.
Ponify.adaptive_case = function(word, rep){
	var aw = word.split(" ");
	var ar = rep.split(" ");
	
	if(Math.max(aw.length, ar.length) != 1){
		return this.adaptive_case_multiword(aw, ar);
	} else{
		return this.adaptive_case_word(word, rep);
	}
}


// Ponify a string and return either a text node or a <ponifytext> node depending
// on whether or not advanced highlighting/tooltips are enabled
Ponify.ponifyText = function(v, mode){

	if(typeof(v) != "string"){ return; }
	
	// Skip text nodes with nothing but spaces/tabs/etc (there are a lot of these)
	if(!/\S/.test(v)){ return; }
	
	console.debug(v);
	
	var track = [];
	var p;
	if(mode == undefined){ mode = this.highlight[0]; }

	for(var i = 0; i < this.replace.length; i++){
		var r = new RegExp(
			"(^|\\W|_)" +
			this.replace[i][0].replace(/[\W]/g, "\\$&") +
			"([\\W]|$|_)", "i"
		);
		p = 0;
		var c;
		
		while((c = v.substring(p).search(r)) != -1){
			// Test the first character to see if it's part of the word
			p += c + (/\W|_/.test(v[p + c]) && v[p + c] != this.replace[i][0][0]);
			var word = v.substring(p, p + this.replace[i][0].length);				
			var s = this.adaptive_case(word, this.replace[i][1]);

			if(!mode){
				v = v.substr(0, p) + s + v.substr(p + word.length);
			} else{
				track.unshift([p, word, s]);
			}
			
			p += word.length;
		}
	}

	if(!mode){ return v; }
	if(!track.length){ return 0; }
		
	track.sort(function(a, b){ return a[0] > b[0] ? -1 : 1; });
								
	var ponify_text = document.createElement("ponifytext");
	
	p = v.length;
	for(var i = 0; i < track.length; i++){

		// Prevent duplicate replacements with pairs like "ladies and gentlemen" "gentlemen"
		if(track[i][0] + track[i][1].length > p){ continue; }
		
		// TextNodes for non-ponified text
		if(track[i][0] + track[i][1].length < p){
			ponify_text.insertBefore(
				document.createTextNode(
					v.substring(track[i][0] + track[i][1].length, p)
				), ponify_text.childNodes[0]
			);
		}
		var ponify = document.createElement("ponify");

		if(mode == 1 || mode == 2){
			ponify.setAttribute("title", track[i][1]);
		}
		if(mode == 1 || mode == 3){
			ponify.style.color = this.highlight[1];
		}

		ponify.appendChild(document.createTextNode(track[i][2]));
		ponify_text.insertBefore(ponify, ponify_text.childNodes[0])
		p = track[i][0];
	}
	if(p != 0){
		ponify_text.insertBefore(
			document.createTextNode(v.substring(0, p)
		), ponify_text.childNodes[0]);
	}

	return ponify_text;
}

Ponify.ponifyReplace = function(node){
	var rep;
	
	var p = node.parentNode;
	
	if(!p){ return; }
	
	if(p.nodeName == "PRE" || p.nodeName == "TITLE" || p.nodeName == "OPTION"){
		rep = this.ponifyText(node.nodeValue, 0);
	} else{
		rep = this.ponifyText(node.nodeValue);		
	}
		
	switch(typeof(rep)){
		case "string":
			node.data = rep;
			break;
		case "object":
			this.DOMLock = true;
			node.parentNode.replaceChild(rep, node);
			this.DOMLock = false;
			delete node;
			break;
	}
}

Ponify.pseudoThread = function(text_nodes){
	var l = Math.min(text_nodes.snapshotLength, text_nodes.pos + 100);
	for(text_nodes.pos; text_nodes.pos < l; text_nodes.pos++){
		this.ponifyReplace(text_nodes.snapshotItem(text_nodes.pos));
	}
	if(text_nodes.snapshotLength - text_nodes.pos){
		setTimeout(function(){ Ponify.pseudoThread(text_nodes); }, 20);
	}
}

// Ponify the contents of elem and all child nodes
Ponify.ponify = function(elem){
	if(elem.nodeType == 3){
		var p = elem.parentNode;
		
		if(!p){ return; }
		
		var p_name = p.nodeName;
		if(p_name != "STYLE" && p_name != "SCRIPT" && p_name != "TEXTAREA"){
			this.ponifyReplace(elem);
		}
	} else{
		var text_nodes = document.evaluate(
			".//text()[not(ancestor::script) and not(ancestor::style) and not(ancestor::textarea)]",
			elem, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

		if(text_nodes.snapshotLength){
			if(this.pseudo_threading){
				text_nodes.pos = 0;
				this.pseudoThread(text_nodes);
			} else{
				for(var i = 0; i < text_nodes.snapshotLength; i++){
					var node = text_nodes.snapshotItem(i);
					this.ponifyReplace(node);
				}
			}
		}
	}
}

// Ponify nodes that are added to the document
Ponify.nodeInserted = function(event){
	if(!this.DOMLock){
		Ponify.ponify(event.target);
	}
}


Ponify.DOMLock = false;
Ponify.enabled = Ponify.urlCheck();


if(Ponify.enabled){
	Ponify.ponify(document);
	document.addEventListener("DOMNodeInserted", Ponify.nodeInserted, true);
}