//
// Autohyphen library for DOM-endabled browsers.
// Version 2003-12-02.
// (C) Dmitry Koteroff, 2003.
// http://dklab.ru
// 
// Autohyphen: class to add hyphens into the specially styled text 
// elements on the page. Also it can remove hyphens from elements.
//
// Direct usage:
// ------
// <style>
//   p  { filter:hyphenize() }
//   td { filter:hyphenize("text-align:justify") }
// </style>
// <script language=JavaScript src="Autohyphen.js"><\script>
// <body onload="new Autohyphen().hyphenizeTree(this)">
//   ...
// </body>
//
// In the second example (td element) also sets aditional style
// (justification) for those elements who was really hyphenized
// (used to avoid justification in browsers which do not support &shy;).
//
// Исходный материал
// -----------------
// Есть алгоpитм (П.Хpистова в модификации Дымченко и Ваpсанофьева),
// в котоpом заложены шесть пpавил, и они накpывают 99.99%.
// Итак, х - это одна из "ьъй", тогда пpавила:
// "х-" "г-г" "гс-сг" "сг-сг" "гс-ссг" "гсс-ссг"
// Пpименяются последовательно, в поpядке наpастания длины,
// и не надо забывать, что они могyт пеpекpываться концами.
// 
// Модификация
// -----------
// Похоже, в этом алгоритме ошибка. По крайней мере, в соответствии
// с ним будут откусываться одиночные буквы в начале и конце слов.
// Я модифицировал его так (см. ниже правила в переменной rules).
// Правила применяются к тексту по очереди, начиная С ПОСЛЕДНЕГО.
// 
function Autohyphen() { this.Autohyphen() }
Autohyphen.prototype = {
	tshy:         "&shy;",      // hyphen entity
	shy:          null,         // hyphen unicode character 
	x:            '[йьъ]',      // специальная
	g:            '[аеёиоуыэюяaeiouy]', // гласная
	s:            '(?:sh|ch|qu|[бвгджзклмнпрстфхцчшщbcdfghjklmnpqrstvwxz])', // согласная
	l:            '[ьъйаеёиоуыэюябвгджзйклмнпрстфхцчшщъьaeiouybcdfghjklmnpqrstvwxz]', // буква
	rules:        null,         // hyphenization rules
	filterName:   "hyphenize",  // name of filter style entity
	argCache:     {},

	// Constructor. Creates the new hyphenizer for element and
	// attaches the cleaner to it (to handle Copy+Paste).
	Autohyphen: function() {
		// Translate shy to Unicode char. 
		// ATTENTION: Called only once per class!
		if (Autohyphen.prototype.shy == null) with (Autohyphen.prototype) {
			var node = document.createElement("span");
			node.innerHTML = tshy;
			shy = node.childNodes[0].nodeValue; // works good
			// Prepare all the rules.
			rules = [
				[s+g, g+l],
				[g+s, s+g],
				[s+g, s+g],
				[g+s, s+s+g],
				[g+s+s, s+g],
				[g+s+s, s+s+g],
				[x, l+l],
				null
			];
		}
	},

	// Hyphenizes this node if it is a text node ONLY.
	hyphenizeTextNode: function (node, forceApply) {
		if (node.nodeType != 3) return false;
		var parent = node.parentNode;
		var apply = forceApply || this.getApplyMethod(parent);
		if (apply) {
			node.nodeValue = this.hyphenizeText(node.nodeValue);
			if (apply != true) parent.style.cssText = apply;
			return true;
		}
		return false;
	},

	// Hyphenizes all properly styled child nodes of this node.
	hyphenizeTree: function (node, forceApply) {
		var children = node.childNodes;
		var apply = forceApply || this.getApplyMethod(node);
		for (var i=0, len=children.length; i<len; i++) {
			var child = children[i];
			if (child.nodeType == 3) {
				if (apply) {
					child.nodeValue = this.hyphenizeText(child.nodeValue);
					if (apply != true) node.style.cssText = apply;
				}
			} else {
				this.hyphenizeTree(child, apply);
			}
		}
	},

	// Removes hyphens from all the child nodes.
	dehyphenizeTree: function (node) {
		var children = node.childNodes;
		for (var i=0, len=children.length; i<len; i++) {
			var child = children[i];
			if (child.nodeType == 3) {
				child.nodeValue = this.dehyphenizeText(child.nodeValue);
			} else {
				this.dehyphenizeTree(child);
			}
		}
	},

	// Adds hyphens to static text.
	hyphenizeText: function (text) {
		for (var i=this.rules.length-1; i>=0; i--) {
			var ru = this.rules[i]; if (!ru) continue;
			var re = new RegExp('('+ru[0]+')(?='+ru[1]+')', 'gi')
			text = text.replace(re, '$1'+this.shy);
		}
		return text;
	},

	// Removes hyphens from static text.
	dehyphenizeText: function (text) {
		re = new RegExp(this.shy, 'g');
		return text.replace(re, "");
	},

	// Determines if this node needs to be hyphenized.
	getApplyMethod: function(elt) {
		// IE-specific!
		var filter = (elt.currentStyle || elt.style || "").filter;
		if (!filter) return false;
		if (!filter.indexOf || filter.indexOf(this.filterName)<0) return false;
		// Extrace argument if present.
		if (new RegExp(this.filterName+"\s*(\\((.*)\\))?").exec(filter) != null) {
			var st = RegExp.$2;
			if (st) return st.replace(/^(["'])(.*)\1$/, "$2");
			return true;
		}
		return false;
	}
};


// This class is used to walk through dinamically building DOM.
// If the tree is not fully build yet in the time of calling process(),
// next call to process() will walt through the rest of nodes
// appended since previout call.
function DynamicEnumerator(node, callback) { this.DynamicEnumerator(node, callback) }
DynamicEnumerator.prototype = {
	node: null,
	root: null,

	// node - root node to start from (excluding)
	// callback - this function will be called for each node
	DynamicEnumerator: function(node, callback) {
		this.callback = callback;
		this.root = node;
		this.node = node;
	},

	// Walks through the tree and calls callback function.
	// Multiple calls to process() will process dynamically builded tree.
	process: function() { with (this) {
		var passed = 0;
		while (1) {
			var n = node;
			while (1) {
				var nProcessed = (n.processed||0);
				if (n && n.childNodes.length > nProcessed) {
					this.callback(n.childNodes[nProcessed]);
					passed++;
					n.processed = nProcessed + 1;
					node = n.childNodes[nProcessed];
					break;
				} else if (!n || n == root) {
					return passed;
				} else {
					n = n.parentNode;
				}
			}
		}
	}}
};


//
// Autohyphen library wrapper for HTC-compatible browsers.
// Version 2003-12-02.
// (C) Dmitry Koteroff, 2003.
// http://dklab.ru
//
// This library also corrects the clipboard data while copying
// text from hyphenized pages (removes stupid &shy;'s from clipbopard).
//
// Usage:
//
// <script src="Autohyphen_wrap.js"></script>
// <style>
//   body { behavior:url(Autohyphen.htc) }
//   p, dd, li, a, i { filter:hyphenize("text-align:justify"); }
// </style>
// <body> 
//   ...
// </body>
//

document.hyphenized = 0;
function Autohyphen_onStart() {
	// This function may be called in TWO different concurrent threads!
	document.hyphenized++;
	if (document.hyphenized > 1) return;

	// Hyphenize.
	var autohyphen = new Autohyphen();
	var enumerator = new DynamicEnumerator(document.body, function(node) {
		autohyphen.hyphenizeTextNode(node);
	});
	enumerator.process();
	setInterval(function() { enumerator.process() }, 100);

	// Create temp iframe only once. We need iframe because we could 
	// not paste to invisible span without showing the pasted data.
	var name = "ClipboardCleanerTempPad";	
	if (!window.frames[name]) {
		var s = document.createElement("span");
		s.style.position="absolute";
		s.style.visibility="hidden";
		document.body.insertBefore(s, document.body.firstChild);
		// document.body.appendChild() DOES NOT WORK properly!
		// It causes aborting of load of the document.
		s.innerHTML = "<iframe name="+name+" width=1 height=1></iframe>";
	}
	var pad = window.frames[name].document;

	var insideOncutcopy = false;

	// Called on cut or copy command.
	var f_oncutcopy = function (method) {
		if (insideOncutcopy) return true;
		
		// Emulate copy event.
		insideOncutcopy = true;
		document.execCommand(method);
		event.returnValue = false;
		insideOncutcopy = false;	
		
		// Paste to temporary document.		
		pad.innerHTML = "";
		var r = pad.body.createTextRange();
		r.execCommand("Paste");

		// Process the content.
		autohyphen.dehyphenizeTree(pad.body);

		// Copy back from temporary document to clipboard.
		var r = pad.body.createTextRange();
		r.execCommand("Copy");
	}

	document.body.attachEvent("oncopy", function() { f_oncutcopy('Copy') });
	document.body.attachEvent("oncut", function() { f_oncutcopy('Cut') });
}


if (document.attachEvent) {
	document.attachEvent("onactivate", Autohyphen_onStart);
	document.attachEvent("onreadystatechange", Autohyphen_onStart);
}