/*

// Le code suivant doit être copié dans le fichier HTML initial, de telle sorte
// qu'il soit le premier code JavaScript exécuté.

window.$OnloadFunctions   = [ ];
window.$OnunloadFunctions = [ ];
window.$OnresetFunctions  = [ ];

window.$AddOnloadFunction   = function(f) { window.$OnloadFunctions.push(f);   };
window.$AddOnunloadFunction = function(f) { window.$OnunloadFunctions.push(f); };
window.$AddOnresetFunction  = function(f) { window.$OnresetFunctions.push(f);  };

*/


// -- OUVERTURE ET FERMETURE DU DOCUMENT ---------------------------------------


window.onload = function() {
//-----======-------------

	var
		I;

	for (I = 0; I < $OnloadFunctions.length; I++)
		if (typeof $OnloadFunctions[I] == 'function')
			$OnloadFunctions[I]();
		else
			eval($OnloadFunctions[I]);
};


window.onunload = function() {
//-----========-------------

	var
		I;

	for (I = 0; I < $OnunloadFunctions.length; I++)
		if (typeof $OnunloadFunctions[I] == 'function')
			$OnunloadFunctions[I]();
		else
			eval($OnunloadFunctions[I]);
};


window.onreset = function() {
//-----=======-------------

	var
		I;

	for (I = 0; I < $OnresetFunctions.length; I++)
		if (typeof $OnresetFunctions[I] == 'function')
			$OnresetFunctions[I]();
		else
			eval($OnresetFunctions[I]);
};


// -- AUTRES FONCTIONNALITÉS ---------------------------------------------------


window.$ = function(X) {
//-----=--------------

/*
	Renvoie son argument, sauf si celui-ci est un string; dans ce cas la fonction renvoie l'élément
	HTML dont l'id vaut l'argument.
*/

	return (typeof X == 'string' ? document.getElementById(X) : X);
};


window.$Acn = function(NODE, CLASS_NAME) { // (A)dd (C)lass (N)ame
//-----====-----------------------------

/*
	S'il n'y est pas, ajoute le CLASS_NAME donné au className de l'élément
	donné. (Les CLASS_NAME sont toujours triés pour en faciliter la
	comparaison.)
*/

	NODE.className = $Tacn(NODE.className, CLASS_NAME);
};


window.$ArrayAdd = function(ARRAY, VALUE) {
//-----=========-------------------------

/*
	Renvoie le tableau donné, à la fin duquel la valeur donnée a été ajoutée,
	si elle en était initialement absente.
*/

	var
		I;

	for (I = 0; I < ARRAY.length; I++)
		if (ARRAY[I] == VALUE)
			return ARRAY;
	ARRAY[ARRAY.length] = VALUE;
	return ARRAY;
};


window.$ArrayCopy = function(ARRAY) {
//-----==========------------------

/*
	Renvoie un nouveau tableau dont chaque composante est la copie, au premier
	niveau, de la composante correspondante du tableau donné.
*/

	var
		COPY = [ ],
		I;

	for (I = 0; I < ARRAY.length; I++)
		COPY[I] = ARRAY[I];
	return COPY;
};


window.$ArrayIncludes = function(ARRAY, VALUE, MODE) {
//-----==============-------------------------------

/*
	Renvoie true ou false selon que la valeur donnée est présente ou non dans le
	tableau donné.
*/

	return window.$ArrayIndex(ARRAY, VALUE, MODE) != null;
};


window.$ArrayIndex = function(ARRAY, VALUE, MODE) {
//-----===========-------------------------------

/*
	Renvoie le premier indice auquel une valeur égale à la valeur donnée a été
	trouvée, ou null si aucune telle valeur n'est présente.
	
	Si MODE vaut 1 (resp. 2) il suffit que la valeur donnée corresponde à la partie gauche (resp.
	droite) d'une valeur du tableau pour constituer une occurrence.
*/

	var
		I;

	for (I = 0; I < ARRAY.length; I++)
		if (ARRAY[I] == VALUE ||
			(
				(typeof ARRAY[I] == 'string' && typeof VALUE == 'string') && (
					(MODE == 1 && ARRAY[I].indexOf(VALUE) == 0) ||
					(MODE == 2 && ARRAY[I].indexOf(VALUE) == ARRAY[I].length - VALUE.length)
				)
			)
		)
			return I;
	return null;
};


window.$ArrayRemove = function(ARRAY, VALUE, MODE) {
//-----============-------------------------------

/*
	Renvoie le tableau donné, dont la première occurrence de la valeur donnée a été enlevée.
*/

	var
		I;
		
	if ((I = $ArrayIndex(ARRAY, VALUE, MODE)) != null)
		ARRAY.splice(I, 1);
	return ARRAY;
};


window.$AsLiteral = function(X, PHP) {
//-----==========-------------------

/*
	Renvoie un littéral JavaScript (si PHP vaut false) ou PHP (si PHP vaut true)
	équivalent à la valeur X donnée.
*/

	var
		RES,
		INITED,
		K;
	
	RES = null;
	switch (typeof X) {
		case 'boolean' :
			RES = (X ? 'true' : 'false');
			break;
		case 'number' :
			RES = X;
			break;
		case 'string' :
			RES = '\'' +  X.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/[\x01-\x1F]/g, function(X) { var CODE = X.charCodeAt(0); return '\\x' + (CODE < 16 ? '0' : '') + CODE.toString(16); }) + '\'';
			break;
		case 'object' :
			if (X == null)
				RES = 'null';
			else {
				RES = (PHP ? 'array(' : '{');
				INITED = false;
				for (K in X) {
					if (INITED)
						RES += ',';
					else
						INITED = true;
					RES += $AsLiteral(K, PHP) + (PHP ? '=>' : ':') + $AsLiteral(X[K], PHP);
				}
				RES +=  (PHP ? ')' : '}');
			}
			break;
		default:
			RES = '\'*** UNABLE TO LITERALIZE ' + (typeof X).toUpperCase() + ' ***\'';
	}
	return RES;
};


window.$Ce = function(TAG_NAME) { // (C) reate (E)lement
//-----===---------------------

/*
	Crée et renvoie  un nouvel élément HTML de balise donnée.
*/

	return document.createElement(TAG_NAME);
};


window.$Ct = function(TEXT) { // (C)reate (T)extNode
//-----===-----------------

/*
	Crée et renvoie  un nouveau text NODE initialisé avec le string donné. Si
	aucun string n'est donné, l'initialise avec le string vide.
*/
	return document.createTextNode(TEXT ? TEXT : '');
};


window.$DomAncestrality = function(NODE1, NODE2) {
//-----================-------------------------

/*
	Renvoie:
		 0 si NODE1 = NODE2
		 1 si NODE1 < NODE2 (càd si NODE2 est ancêtre de NODE1)
		-1 si NODE1 > NODE2 (càd si NODE1 est ancêtre de NODE2)
		null dans tout autre cas
*/

	var
		NODE;
		
	if (NODE1 == NODE2)
		return 0;
	NODE = NODE2;
	while ((NODE = NODE.parentNode))
		if (NODE == NODE1)
			return -1;
	NODE = NODE1;
	while ((NODE = NODE.parentNode))
		if (NODE == NODE2)
			return 1;
	return null;
};


window.$DomChildIndex = function(NODE) {
//-----==============-----------------

	var
		NODE_ = NODE.parentNode.firstChild,
		I;

	I = 0;
	while (NODE_ != NODE) {
		NODE_ = NODE_.nextSibling;
		I++;
	}
	return I;
};


window.$DomEmpty = function(E) {
//-----=========--------------

/*
	Renvoie l'élément donné après en avoir enlevé tous les descendants.
*/

	while (E.firstChild)
		E.removeChild(E.firstChild);

	return E;
};


window.$DomLeft = function(ELEM, ROOT) {
//-----========-----------------------

/*
	Renvoie la coordonnée horizontale de l'élément donné par rapport à celle de
	ROOT, si celui-ci est donné, ou à celle de body sinon.
*/

	var
		LEFT = ELEM.offsetLeft;

	while ((ELEM = ELEM.offsetParent) && ELEM != ROOT)
		LEFT += ELEM.offsetLeft;

	return LEFT;
};


window.$DomStyle = function (E, CSS_NAME) {
//-----=========-------------------------

/*
	Renvoie la valeur réelle de la propriété CSS donnée pour l'élément donné.
	(CSS_NAME est de la forme "font-size" et pas "fontSize".)
*/

	if (E.currentStyle != undefined) // MSIE
		return E.currentStyle[$TextCamelize(CSS_NAME)];
	else if (window.getComputedStyle != undefined) // STANDARD
		return window.getComputedStyle(E, '').getPropertyValue(CSS_NAME);
	else
		return null;
};


window.$DomSwap = function(NODE1, NODE2) {
//-----========-------------------------

	var
		PARENT2 = NODE2.parentNode,
		AFTER2 = NODE2.nextSibling;

	NODE1.parentNode.replaceChild(NODE2, NODE1);
	PARENT2.insertBefore(NODE1, AFTER2);
};


window.$DomTop = function(ELEM, ROOT) {
//-----=======-----------------------

/*
	Renvoie la coordonnée verticale de l'élément donné par rapport à celle de
	ROOT, si celui-ci est donné, ou à celle de body sinon.
*/

	var
		TOP = ELEM.offsetTop;

	while ((ELEM = ELEM.offsetParent) && ELEM != ROOT)
		TOP += ELEM.offsetTop;

	return TOP;
};


window.$Dump = function(MESSAGE, INSPECT) {
//-----=====-----------------------------

	var
		PRE;

	document.body.appendChild(PRE = $Ce('pre'));
	PRE.appendChild($Ct(INSPECT ? $Inspect(MESSAGE) : MESSAGE));
	PRE.style.margin = 0;
	PRE.style.color = 'orange';

	return MESSAGE;
};


window.$Em_ = function(ANCHOR_ID, ADDRESS, LABEL) {
//-----===--------------------------------------

/*
	Défense anti-spam:
	
	1. Positionne l'attribut "href" de la balise "a" dont l'ID est donné, par "mailto:" suivi de
	l'adresse donnée, décodée au sens de DECODE. (C'est-à-dire que chaque caractère de ADDRESS est
	remplacé par son prédécesseur dans la table ASCII.)
	
	2. Si l'élément "a" est vide, alors si LABEL est donné, l'insère comme text-node de "a"; sinon,
	insère l'adresse donnée (décodée au sens de DECODE) comme text-node de "a". (Si "a" n'est pas
	vide, le laisse inchangé.)
*/

	var
		A = document.getElementById(ANCHOR_ID);
		
	function DECODE(TXT) {
	//-------======-----
	
		var
			RES = '',
			I;
			
		for (I = 0; I < TXT.length; I++)
			RES += String.fromCharCode(TXT.charCodeAt(I) - 1);
		return RES;
	}
	
	A.setAttribute('href', DECODE('nbjmup;' + ADDRESS));
	if (A.childNodes.length == 0)
		A.appendChild($Ct(LABEL ? LABEL : DECODE(ADDRESS)));
};


window.$Encrypt = function(TEXT, KEY) {
//-----========----------------------

/*
	Renvoie une chaîne résultant du chiffrement de la chaîne donnée avec
	la clé donnée. (Le déchiffrement sera obtenu à l'aide de la fonction
	réciproque $Decrypt() et de la même clé.)
	Les codes des caractères de la chaîne et de la clé sont supposés
	inférieurs à 256.
*/

	var
		T,
		I;
		
	T = '';
	for (I = 0; I < TEXT.length; I++)
		T += String.fromCharCode((TEXT.charCodeAt(I) + KEY.charCodeAt(I % KEY.length)) % 256);
	return T;
};


window.$Decrypt = function(TEXT, KEY) {
//-----========----------------------

/*
	Renvoie une chaîne résultant du déchiffrement de la chaîne donnée
	avec la clé donnée lors du chiffrement. (Le chiffrement est supposé
	avoir été obtenu à l'aide de la fonction réciproque $Encrypt().)
*/

	var
		T,
		I;
		
	T = '';
	for (I = 0; I < TEXT.length; I++)
		T += String.fromCharCode((256 + TEXT.charCodeAt(I) - KEY.charCodeAt(I % KEY.length)) % 256);
	return T;
};


window.$Hcn = function(NODE, CLASS_NAME, MODE) { // (H)as (C)lass (N)ame
//-----====-----------------------------------

/*
	Renvoie true ou false selon que le CLASS_NAME donné appartient ou non au
	className de l'élément donné.
*/

	var
		I,
		CLASS_NAMES;
		
	return (MODE ?
		((I = $ArrayIndex((CLASS_NAMES = NODE.className.split(/ +/)), CLASS_NAME, MODE)) == null ? null : CLASS_NAMES[I]) :
		$ArrayIncludes(NODE.className.split(/ +/), CLASS_NAME)
	);
};


window.$Inspect = function(VAL, MAX_DEPTH, DEPTH, MARKED) {
//-----=======-----------=-------------------------------

	function AS_STRING(V) {
	//......._________.....

		if (V === undefined)
			return 'undefined';
		if (V === null)
			return 'null';
		return '( ' +
			(typeof V).substr(0, 1).toUpperCase() +
			': ' +
			(typeof V.toString == 'function' ?
				V.toString().substr(0, 320).replace(/((\r\n)|\r|\n|\s)+/g, ' ') :
				'??' // MSIE
			) +
			' )';
	}
	
	var
		I,
		K,
		INDENT,
		AUX,
		RES;
		
	if (MAX_DEPTH == null)
		MAX_DEPTH = 1;
	if (DEPTH == null)
		DEPTH = 1;
	INDENT = '';
	for (I = 1; I < DEPTH; I++)
		INDENT += '\t';
	if (MAX_DEPTH > 0 && DEPTH > MAX_DEPTH)
		return AS_STRING(VAL);
	if (MARKED == null)
		MARKED = [ ];
	switch (typeof VAL) {
		case 'string' :
		case 'boolean' :
		case 'number' :
		case 'function' :
			return INDENT + AS_STRING(VAL);
		case 'object' :
			if (VAL == null)
				return INDENT + 'null';
			else {
				for (I = 0; I < MARKED.length; I++)
					if (MARKED[I] == VAL)
						return RES + ' ** RECURSION ** ';
				MARKED.push(VAL);
				RES = INDENT + (VAL instanceof Array ? '[' : '{') + '\n';
				for (K in VAL)
					RES += INDENT + '\t' + K + ' : ' + $Inspect(VAL[K], MAX_DEPTH, DEPTH + 1, MARKED) + '\n';
				RES += (VAL instanceof Array ? ']' : '}') + '\n';
				return RES;
			}
		case 'undefined' :
			return INDENT + 'undefined';
		default :
			return INDENT + 'ERROR : Unknown type "' + (typeof VAL) +'"';
	}
	return void null;
};


window.$Lt = function(TEXT, PARAMS) {
//-----===-------------------------

/*
	Renvoie la traduction du texte donné (en anglais) dans la langue courante.
*/

	var
		RES = TEXT;
		
	if (
			// Une langue est spécifiée.
		typeof $UI_Lang != 'undefined' &&
			// La langue spécifiée n'est pas la langue native.
		$UI_Lang != 'en' &&
			// Le texte donné doit être traduit.
		TEXT.match(/\D/) &&
			// Le fichier de localisation est présent.
		typeof $L10n != 'undefined' &&
			// Le texte à traduire est présent dans le fichier de localisation.
		$L10n.UI_Localizations[TEXT] != undefined &&
			// La traduction attendue est présente dans le fichier de loc.
		$L10n.UI_Localizations[TEXT][$L10n.UI_Languages[$UI_Lang]] != undefined
	)
		RES = $L10n.UI_Localizations[TEXT][$L10n.UI_Languages[$UI_Lang]];
	if (PARAMS != null)
		for (PARAM in PARAMS)
			RES = RES.replace(new RegExp('{' + PARAM + '}'), (PARAM.match(/^_/) ? PARAMS[PARAM] : $Lt(PARAMS[PARAM])));
	return RES;
};


window.$Nca = function(NODE, CLASS_NAME) { // (N)earest (C)lassNamed (A)ncestor
//-----====-----------------------------

/*
	Renvoie le premier élément dont le className contient le CLASS_NAME donné,
	rencontré dans un parcours allant de l'élément donné jusqu'à la racine.

	Si le className de l'élément donné contient le CLASS_NAME donné, c'est cet
	élément qui est renvoyé.

	Si aucun CLASS_NAME n'est donné, renvoie le premier noeud de type 1.

	Si aucun élément n'est donné ou si aucun élément de CLASS_NAME donné n'est
	rencontré, renvoie null.
*/

	while (NODE && (NODE.nodeType != 1 || ! $Hcn(NODE, CLASS_NAME)))
		NODE = NODE.parentNode;

	return NODE;
};


window.$Nta = function(NODE, TAG_NAME) { // (N)earest (T)agNamed (A)ncestor
//-----====---------------------------

/*
	Renvoie le premier élément dont le tagName correspond au tagName donné,
	rencontré dans un parcours allant de l'élément donné jusqu'à la racine.

	Si le tagName de l'élément donné correspond au tagName donné, c'est cet
	élément qui est renvoyé.

	Si aucun tagName n'est donné, renvoie le premier noeud de type 1.

	Si aucun élément n'est donné ou si aucun élément de tagName donné n'est
	rencontré, renvoie null.
*/

	if (TAG_NAME)
		TAG_NAME = TAG_NAME.toLowerCase();
	while (NODE && (NODE.nodeType != 1 || TAG_NAME && $Tn(NODE) != TAG_NAME))
		NODE = NODE.parentNode;

	return NODE;
};


window.$ObjectCopy = function(OBJECT) {
//-----===========-------------------

/*
	Renvoie un nouvel objet dont chaque propriété est la copie, au premier
	niveau, de la propriété correspondante de l'objet donné.
*/

	var
		COPY = { },
		P;

	for (P in OBJECT)
		COPY[P] = OBJECT[P];
	return COPY;
};


window.$ObjectSize = function(OBJECT) {
//-----===========-------------------

/*
	Renvoie le nombre de propriétés de l'objet donné.
*/

	var
		SIZE,
		P;

	SIZE = 0;
	for (P in OBJECT)
		SIZE++;
	return SIZE;
};


window.$PathInfoCase = function(PATH, CASE) {
//-----=============-----------------------

	/*
		Correspondant exactement à la fonction PHP de même nom, présentée dans
		la section "Filesystem Functions" du manuel PHP, sauf la présence ici du
		second paramètre, CASE, qui, si présent, convertit les résultats
		en minuscules (CASE = -1) ou majuscules (CASE = +1).
	*/
	
	var
		MATCHES,
		K;
		
	if (CASE == -1)
		PATH = PATH.toLowerCase();
	else if (CASE == +1)
		PATH = PATH.toUpperCase();
	MATCHES = PATH.match(/(.*\/)?(([^.]*)|(.*\.(.*)))$/);
	for (K in MATCHES)
		if (MATCHES[K] == null)
			MATCHES[K] = '';
	return {
		'dirname' : MATCHES[1],
		'basename' : (MATCHES[3] ? MATCHES[3] : MATCHES[4]),
		'extension' : (MATCHES[3] ? '' : MATCHES[5])
	};
};


window.$Platform = function(WORDS) {
//-----=========------------------

	/*
		Si WORDS est donné :
		
			$Platform() renvoie true ou false selon que tous les mots de WORDS
			sont présents, ou non, dans la chaîne d'identification de l'agent.

			(WORDS est une liste de mots telle que ceux-ci sont des suites de
			caractères séparés par \W+. L'ordre et la casse des mots ne sont pas
			considérés.)

		Si non :

			$Platform() renvoie la chaîne d'identification de l'agent.

		MacOS is est caractérisé par : mac
		Windows …                    : win
		Explorer …                   : msie
		Netscape …                   : netscape

		Exemples :

			$Platform('msie mac')
			$Platform('mac')
			$Platform('WIN')
			$Platform('NETSCAPE')
	*/

	var
		I,
		WORDS_LIST;

	if ($Platform.STRING == undefined)
		$Platform.STRING =
			(typeof navigator == 'undefined' ?
				'' :
				(navigator.userAgent == undefined ?
					'' :
					navigator.userAgent +
						(navigator.platform == undefined ?
							'' :
							' ' + navigator.platform
						)
				)
			);
	if (WORDS == null)
		return $Platform.STRING;
	WORDS_LIST = WORDS.split(/\W+/);
	for (I = 0; I < WORDS_LIST.length; I++)
		if (! $Platform.STRING.match(new RegExp(WORDS_LIST[I], 'i')))
			return false;

	return true;
};


window.$RadioValue = function(BUTTONS, VALUE) {
//-----===========---------------------------

/*
	Lit ou écrit la valeur de boutons-radio.
	(Actuellement, se limite à la lecture.)
*/

	var
		I;
		
	for (I = 0; I < BUTTONS.length; I++)
		if (BUTTONS[I].checked)
			return BUTTONS[I].value;
	return null;
};


window.$Rcn = function(NODE, CLASS_NAME, MODE) { // (R)emove (C)lass (N)ame
//-----====-----------------------------------

/*
	S'il y est, enlève le CLASS_NAME donné du className de l'élément donné.
*/

	var
		CLASSES = $Trcn(NODE.className, CLASS_NAME, MODE);

	if (CLASSES == '') {
		NODE.removeAttribute('className'); // IE
		NODE.removeAttribute('class'); // OTHERS
	}
	else
		NODE.className = CLASSES;
};


window.$ServerTime = function(PHP_TIME) {
//-----===========---------------------

	if (PHP_TIME)
		$ServerTime.OFFSET = PHP_TIME - new Date().getTime() / 1000;
	return new Date().getTime() / 1000 + $ServerTime.OFFSET;
};


window.$TA_GetSelectionBounds = function(TEXTAREA) {
//-----======================---------------------

	var
		S,
		R,
		LENGTH,
		MOVE_MIN, MOVE_MAX, MOVE, BM,
		
		START;

	if ((S = document.selection) != undefined) { // MSIE
		TEXTAREA.focus(); // Nécessaire dans le cas où la sélection se réduit au caret !?
		if ((R = S.createRange()) && TEXTAREA == R.parentElement()) {
			LENGTH = R.text.length;

			// Solution de principe :
			
			START = 0;
			while (R.move('character', -1) && TEXTAREA == R.parentElement())
				START++;
			return [ START, START + LENGTH ];

			
/*			// Solution optimisée, par recherche dichotomique :

			// RESTE UN PROBLEME: LORSQUE LE CARET EST POSITIONNE TOUT A LA FIN 8?
			MOVE_MIN = 0;
			MOVE_MAX = TEXTAREA.value.length - LENGTH + 1;
			do  {
				MOVE = Math.floor((MOVE_MIN + MOVE_MAX) / 2);
				BM = R.getBookmark();
				if (R.move('character', - MOVE) && R.parentElement() == TEXTAREA) {
					MOVE_MIN = MOVE;
				}
				else {
					MOVE_MAX = MOVE;
				}
				R.moveToBookmark(BM);
			} while (MOVE_MAX > MOVE_MIN + 1)
			return [ MOVE_MIN, MOVE_MIN + LENGTH ];
*/		}
		return [ 0, 0 ];
	}
	else // OTHERS
		return [ TEXTAREA.selectionStart, TEXTAREA.selectionEnd ];
};


window.$TA_SetSelectionBounds = function(TEXTAREA, BOUNDS) {
//-----======================-----------------------------

	var
		SEL,
		R;
	
	if ((SEL = document.selection) != undefined) { // MSIE
		R = TEXTAREA.createTextRange();
		R.collapse(true);
		R.moveStart('character', BOUNDS[0]);
		R.moveEnd('character', BOUNDS[1] - BOUNDS[0]);
		R.select();
	}
	else // OTHERS
		TEXTAREA.setSelectionRange(BOUNDS[0], BOUNDS[1]);
};


window.$TA_GetSelectionText = function(TEXTAREA) {
//-----====================---------------------	

/*
	Renvoie une copie du texte sélectionné dans la textarea donnée.
*/

	var
		SEL,
		R;
	
	if ((SEL = document.selection) != undefined) { // MSIE
	//	TEXTAREA.focus();
		return SEL.createRange().text;
	}
	else // OTHERS
		return TEXTAREA.value.substr(TEXTAREA.selectionStart, TEXTAREA.selectionEnd - TEXTAREA.selectionStart);
};


window.$TA_SetSelectionText = function(TEXTAREA, TEXT) {
//-----====================---------------------------

/*
	Remplace le texte sélectionné dans la textarea donnée par le texte donné.
*/

	var
		SEL,
		AUX;
	
	if ((SEL = document.selection) != undefined) { // MSIE
		TEXTAREA.focus();
		SEL.createRange().text = TEXT;
	}
	else { // OTHERS
		TEXTAREA.value =
			TEXTAREA.value.substr(0, (AUX = TEXTAREA.selectionStart)) +
			TEXT +
			TEXTAREA.value.substr(TEXTAREA.selectionEnd);
		$TA_SetSelectionBounds(TEXTAREA, [ AUX, AUX + TEXT.length ]);
	}
};


window.$Tacn = function(TEXT, CLASS_NAME) { // (T)extual (A)dd (C)lass (N)ame
//-----====------------------------------

/*
	S'il n'y est pas, ajoute le CLASS_NAME donné à la chaîne donnée. (Les
	CLASS_NAME sont triés.)
*/

	return $ArrayAdd(TEXT.split(/ +/), CLASS_NAME).sort().join(' ');
};


window.$TextCamelize = function(TEXT) {
//-----=============-----------------

	function BUMP(FLAT) { return FLAT.charAt(0) + FLAT.charAt(2).toUpperCase(); }
	return TEXT.replace(/\w-\w/g, BUMP);
};


window.$TextHtml2plain = function(HTML) {
//-----===============-----------------

/*
	Renvoie un string "text/plain" correspondant au string donné, supposé
	"text/html" sans balise.
*/

	return HTML.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
};


window.$TextPlain2html = function(PLAIN) {
//-----===============-------------------------------

/*
	Renvoie un string "text/html" correspondant au string donné, supposé
	"text/plain".
*/

	return PLAIN.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};


window.$TextQ = function(TEXT, DELIM) {
//-----======------------------------

	/*
		Renvoie une chaîne obtenue en entourant la chaîne donnée par le
		délimiteur donné, et en effectuant tous les échappements nécessaires.

		Le délimiteur peut être un guillemet (double quote) ou une apostrophe
		(single quote); si le délimiteur n'est pas donné, c'est l'apostrophe qui
		est utilisée.

		Pour toute chaîne, l'opération garantit l'assertion :

			eval($TextQ(TEXT)) == TEXT
	*/

	var
		REGEXP1 = new RegExp((DELIM = DELIM || '\''), 'g'),
		REGEXP2 = new RegExp('\\\\', 'g');

	return DELIM + TEXT.replace(REGEXP2, '\\\\').replace(REGEXP1, '\\' + DELIM) + DELIM;
};


window.$TextS = function(N, INVERSE) {
//-----======-----------------------

/*
	Si INVERSE ne vaut pas true, renvoie "s" ou "" selon que N est supérieur à
	1 ou non. Si INVERSE vaut true, inverse la condition.
*/

	return ($Xor(N > 1, INVERSE) ? 's' : '');
};


window.$TextTrim = function(TEXT, MODE) {
//-----=========-----------------------

/*
	Renvoie un string correspondant au string donné, éventuellement amputé de
	certains caractères blancs, selon la valeur de MODE.

	MODE est un code binaire dont seuls sont interprétés les 3 bits les moins
	significatifs (valeurs décimales de 0 à 7) :

	bit0 = 1 : suppression de tous caractères blancs au début.
	bit1 = 1 : suppression de tous caractères blancs à la fin.
	bit2 = 1 : remplacement de toute suite de caractères blancs consécutifs par
	           un seul caractère espace.

	Si MODE n'est pas donné, le mode considéré sera 3 (càd: début et fin).
*/

	MODE = MODE || 3;
	if (MODE & 1)
		TEXT = TEXT.replace(/^\s+/, '');
	if (MODE & 2)
		TEXT = TEXT.replace(/\s+$/, '');
	if (MODE & 4)
		TEXT = TEXT.replace(/\s+/g, ' ');
	return TEXT;
};


window.$Time = function() { 
//-----=====-------------

/*
	Renvoie la mesure courante du temps dans les mêmes format et unité que la
	fonction PHP correspondante.
*/

	return new Date().getTime() / 1000;
};


window.$Tn = function(NODE) { // (T)ag (N)ame
//-----===-----------------

/*
	Renvoie le tag name de l'élément donné, converti en minuscules, ou null
	si l'élément n'a pas de propriété tagName.
*/

	return (NODE && typeof NODE.tagName != 'undefined' ? NODE.tagName.toLowerCase() : null);
};


window.$Trcn = function(TEXT, CLASS_NAME, MODE) { // (T)extual (R)emove (C)lass (N)ame
//-----=====-----------------------------------

/*
	S'il y est, enlève le CLASS_NAME donné de la chaîne donnée.
*/

	return $ArrayRemove(TEXT.split(/ +/), CLASS_NAME, MODE).join(' ');

};


window.$Xor = function(X, Y) {
//-----====-----------------

	return ! X != ! Y;
};


window.$SetWidgetValue = function(WIDGET, VALUE) {
//-----===============--------------------------

	var
		I,
		J,
		VALUES;
		
	if (WIDGET.type == undefined)
		WIDGET.type = 'radio';
	switch (WIDGET.type) {
		case 'select-one' :
		case 'select-multiple' :
			VALUES = VALUE.split(/,/);
			for (I = 0; I < WIDGET.options.length; I++) {
				WIDGET.options[I].selected = false;
				for (J = 0; (! WIDGET.options[I].selected && J < VALUES.length); J++)
					if (WIDGET.options[I].value == VALUES[J])
						WIDGET.options[I].selected = true;
			}
			return VALUE;
		case 'checkbox' :
			WIDGET.checked = (WIDGET.value == VALUE);
			return VALUE;
		case 'hidden' :
		case 'text' :
		case 'textarea' :
			WIDGET.value = VALUE;
			return VALUE;
		case 'radio' :
			for (I = 0; I < WIDGET.length; I++) 
				if (WIDGET[I].value == VALUE) {
					WIDGET[I].checked = true;
					return VALUE;
				}
	}
	return null;
};


window.$GetWidgetValue = function(WIDGET) {
//-----===============-------------------

	var
		I,
		VALUES;
		
	if (WIDGET.type == undefined)
		WIDGET.type = 'radio';
	switch (WIDGET.type) {
		case 'checkbox' :
			return (WIDGET.checked ? WIDGET.value : '');
		case 'select-one' :
		case 'select-multiple' :
			VALUES = [ ];
			for (I = 0; I < WIDGET.options.length; I++)
				if (WIDGET.options[I].selected)
					VALUES[VALUES.length] = WIDGET.options[I].value;
			return VALUES.join(',');
		case 'hidden' :
		case 'text' :
		case 'textarea' :
		case 'password' :
			return WIDGET.value.replace(/(\r?\n)|(\n?\r)/g, '\n');
		case 'radio' : 
			for (I = 0; I < WIDGET.length; I++) 
				if (WIDGET[I].checked)
					return WIDGET[I].value;
			return '';
	}
	return null;
};


