/** 

* Copyright 2005-2006 massimocorner.com

* @author      Massimo Foti (massimo@massimocorner.com)

* @version     1.3.1, 2006-07-22

 */



// Create all the validator objects required inside the document

function tmt_validatorInit(){

	var formNodes = document.getElementsByTagName("form");

	for(var i=0; i<formNodes.length; i++){

		if(formNodes[i].getAttribute("tmt:validate") == "true"){

			// Attach a validator object to each form that requires it

			formNodes[i].tmt_validator = new tmt_formValidator(formNodes[i]);

			// Set the form node's onsubmit event 

			// We use a gigantic hack to preserve exiting calls attached to the onsubmit event (most likely validation routines)

			if(typeof formNodes[i].onsubmit != "function"){

				formNodes[i].onsubmit = function(){

					return tmt_validateForm(this);

				}

			}

			else{

				// Store a reference to the old function

				formNodes[i].tmt_oldSubmit = formNodes[i].onsubmit;

				formNodes[i].onsubmit = function(){

					// If the existing function return true, send the form

					if(this.tmt_oldSubmit()){

						return tmt_validateForm(this);

					}

					return false;

				}

			}

		}

	}

}



// Perform the validation

function tmt_validateForm(formNode){

	var errorMsg = "";

	var formValidator = formNode.tmt_validator;

	// Be sure the form contains a validator object

	if(formValidator){

		var focusGiven = false;

		// This array will store all the field validators that contains errors

		// They may be required by the callback

		var invalidFields = new Array();

		// Validate all the fields

		for(var i=0; i<formValidator.validators.length; i++){

			if(formValidator.validators[i].validate()){

				// Append to the global error string

				errorMsg += formValidator.validators[i].message + "\n";

				invalidFields[invalidFields.length] = formValidator.validators[i];

				// Give focus to the first invalid text/textarea field

				if(!focusGiven && (formValidator.validators[i].type == "text")){

					formValidator.validators[i].getFocus();

					focusGiven = true;

				}

			}

		}

		if(errorMsg != ""){

			// We have errors, display them

			if(!formValidator.callback){

				// We don't have callbacks, just display an alert

				alert(errorMsg);

			}

			else{

				// Invoke the callbak, it will take care of displaying the errors

				eval(formValidator.callback + "(formNode, invalidFields)");

			}

		}

		else{

			// Everything is fine, disable form submission to avoid multiple submits

			formValidator.blockSubmit();

		}

	}

	return errorMsg.length == 0; 

}



/* Object constructors */



// Form validator

function tmt_formValidator(formNode){

	// Store all the validator objects inside an array

	this.validators = new Array();

	// Add the specified callback only if the function is currently defined

	if(formNode.getAttribute("tmt:callback") && window[formNode.getAttribute("tmt:callback")]){

		this.callback = formNode.getAttribute("tmt:callback");

	}

	var fieldsArray = tmt_getTextfieldNodes(formNode);

	for(var i=0; i<fieldsArray.length; i++){

		// Create a validator for each text field

		this.validators[this.validators.length] = tmt_textValidatorFactory(fieldsArray[i]);

		

		if(fieldsArray[i].getAttribute("type")){

			// Set the onchange event for each image upload validation

			if((fieldsArray[i].getAttribute("type").toLowerCase() == "file") &&	(fieldsArray[i].getAttribute("tmt:image") == "true")){

				fieldsArray[i].onchange = function(){

					tmt_validateImg(this);

				}

			}

		}

		if(fieldsArray[i].getAttribute("tmt:filters")){

			// Call the filters on the onkeyup and onblur events

			addEvent(fieldsArray[i], "keyup", function(){tmt_filterField(this);});

			addEvent(fieldsArray[i], "blur", function(){tmt_filterField(this);});

		}

	}

	var selectNodes = formNode.getElementsByTagName("select");

	for(var j=0; j<selectNodes.length; j++){

		// Create a validator for each select element

		this.validators[this.validators.length] = tmt_selectValidatorFactory(selectNodes[j]);

	}

	var boxTable = tmt_getNodesTable(formNode, "checkbox");

	for(var boxName in boxTable){

		// Create a validator for each group of checkboxes

		this.validators[this.validators.length] = tmt_boxValidatorFactory(boxTable[boxName]);

	}

	var radioTable = tmt_getNodesTable(formNode, "radio");

	for(var radioName in radioTable){

		// Create a validator for each group of radios

		this.validators[this.validators.length] = tmt_radioValidatorFactory(radioTable[radioName]);

	}

	// Store all the submit buttons

	this.buttons = tmt_getSubmitNodes(formNode);

	// Define a method that can block multiple submits

	this.blockSubmit = function(){

		// Check to see if we want to disable submit buttons

		if(!formNode.getAttribute("tmt:blocksubmit") && !(formNode.getAttribute("tmt:blocksubmit") == "false")){

			// Disable each submit button

			for(var i=0; i<this.buttons.length; i++){

				if(this.buttons[i].getAttribute("tmt:waitmessage")){

					this.buttons[i].value = this.buttons[i].getAttribute("tmt:waitmessage");

				}

				this.buttons[i].disabled = true;

			}

		}

	}

}



// Abstract field validator constructor

function tmt_abstractValidator(fieldNode){

	this.message = "";

	this.name = fieldNode.name;

	if(fieldNode.getAttribute("tmt:message")){

		this.message = fieldNode.getAttribute("tmt:message");

	}

	var errorClass = "";

	if(fieldNode.getAttribute("tmt:errorclass")){

		errorClass = fieldNode.getAttribute("tmt:errorclass");

	}

	this.flagInvalid = function(){

		// Append the CSS class to the existing one

		if(errorClass){

			// Flag only if it's not already flagged

			if(fieldNode.className.indexOf(errorClass) == -1){

				fieldNode.className = fieldNode.className + " " + errorClass;

			}

		}

		// Set the title attribute in order to show a tootip

		fieldNode.setAttribute("title", this.message);

	}

	this.flagValid = function(){

		// Remove the CSS class

		if(errorClass){

			var regClass = new RegExp("\\b" + errorClass);

			fieldNode.className = fieldNode.className.replace(regClass, "");

		}

		fieldNode.removeAttribute("title");

	}

	this.validate = function(){

		// If the field contains error, flag it as invalid and return the error message

		// Be careful, this method contains multiple exit points!!!

		if(fieldNode.disabled){

			// Disabled fields are always valid

			this.flagValid();

			return false;

		}

		if(!this.isValid()){

			this.flagInvalid();

			return true;

		}

		else{

			this.flagValid();

			return false;

		}

	}

}



// Create a validator for text and texarea fields

function tmt_textValidatorFactory(fieldNode){

	// Create a generic validator, than add specific properties and methods as needed

	var obj = new tmt_abstractValidator(fieldNode);

	obj.type = "text";

	var required = false;

	if(fieldNode.getAttribute("tmt:required")){

		required = fieldNode.getAttribute("tmt:required");

	}

	// Put focus and cursor inside the field

	obj.getFocus = function(){

		// This try block is required to solve an obscure issue with IE and hidden fields

		try{

			fieldNode.focus();

			fieldNode.select();

		}

		catch(exception){

		}

	}

	// Check if the field is empty

	obj.isEmpty = function(){

		return fieldNode.value == "";

	}

	// Check if the field is required

	obj.isRequired = function(){

		return required;

	}

	// Check if the field satisfy the rules associated with it

	// Be careful, this method contains multiple exit points!!!

	obj.isValid = function(){

		// The tmt:required="conditional" attribute has a special meaning. 

		// The field isn't strictly required, so it may sometimes be empty, 

		// but before we let it go, we need to check any rule that may apply to it

		if(obj.isEmpty() && (required != "conditional")){

			if(obj.isRequired()){

				return false;

			}

			else{

				return true;

			}

		}

		else{

			// Loop over all the available rules

			for(var rule in tmt_globalRules){

				// Check if the current rule is required for the field

				if(fieldNode.getAttribute("tmt:" + rule)){

					// Invoke the rule

					if(!eval("tmt_globalRules." + rule + "(fieldNode)")){

						return false;

					}

				}

			}

		}

		return true;

	}

	return obj;

}



// Create a validator for <select> fields

function tmt_selectValidatorFactory(selectNode){

	// Create a generic validator, than add specific properties and methods as needed

	var obj = new tmt_abstractValidator(selectNode);

	obj.type = "select";

	var required = false;

	var invalidIndex;

	if(selectNode.getAttribute("tmt:invalidindex")){

		invalidIndex = selectNode.getAttribute("tmt:invalidindex");

	}

	var invalidValue;

	if(selectNode.getAttribute("tmt:invalidvalue") != null){

		invalidValue = selectNode.getAttribute("tmt:invalidvalue");

	}

	// Check if the field is required

	obj.isRequired = function(){

		return required;

	}

	// Check if the field satisfy the rules associated with it

	// Be careful, this method contains multiple exit points!!!	// Check if the select validate

	obj.isValid = function(){

		// Check for index

		if(selectNode.selectedIndex == invalidIndex){

			return false;

		}

		// Check for value

		if(selectNode.value == invalidValue){

			return false;

		}

		// Loop over all the available rules

		for(var rule in tmt_globalRules){

			// Check if the current rule is required for the field

			if(selectNode.getAttribute("tmt:" + rule)){

				// Invoke the rule

				if(!eval("tmt_globalRules." + rule + "(selectNode)")){

					return false;

				}

			}

		}

		return true;

	}

	return obj;

}



// Generic validator for grouped fields (radio and checkboxes)

function tmt_groupValidatorFactory(buttonGroup){

	this.name = buttonGroup.name;

	this.message = "";

	this.errorClass = "";

	// Since fields from the same group can have conflicting attribute values, the last one win

	for(var i=0; i<buttonGroup.elements.length; i++){

		if(buttonGroup.elements[i].getAttribute("tmt:message")){

			this.message = buttonGroup.elements[i].getAttribute("tmt:message");

		}

		if(buttonGroup.elements[i].getAttribute("tmt:errorclass")){

			this.errorClass = buttonGroup.elements[i].getAttribute("tmt:errorclass");

		}

	}

	this.flagInvalid = function(){

		// Append the CSS class to the existing one

		if(this.errorClass){

			for(var i=0; i<buttonGroup.elements.length; i++){

				// Flag only if it's not already flagged

				if(buttonGroup.elements[i].className.indexOf(this.errorClass) == -1){

					buttonGroup.elements[i].className = buttonGroup.elements[i].className + " " + this.errorClass;

				}

				buttonGroup.elements[i].setAttribute("title", this.message);

			}

		}

	}

	this.flagValid = function(){

		// Remove the CSS class

		if(this.errorClass){

			var regClass = new RegExp("\\b" + this.errorClass);

			for(var i=0; i<buttonGroup.elements.length; i++){

				buttonGroup.elements[i].className = buttonGroup.elements[i].className.replace(regClass, "");

				buttonGroup.elements[i].removeAttribute("title");

			}

		}

	}

	this.validate = function(){

		var errorMsg = "";

		// If the field group contains error, flag it as invalid and return the error message

		if(!this.isValid()){

			errorMsg += this.message;

			this.flagInvalid();

		}

		else{

			this.flagValid();

		}

		return errorMsg;

	}

}



// Checkbox validator (one for each group of boxes sharing the same name)

function tmt_boxValidatorFactory(boxGroup){

	var obj = new tmt_groupValidatorFactory(boxGroup);

	obj.type = "box";

	var minchecked = 0;

	var maxchecked = boxGroup.elements.length;

	// Since checkboxes from the same group can have conflicting attribute values, the last one win

	for(var i=0; i<boxGroup.elements.length; i++){

		if(boxGroup.elements[i].getAttribute("tmt:minchecked")){

			minchecked = boxGroup.elements[i].getAttribute("tmt:minchecked");

		}

		if(boxGroup.elements[i].getAttribute("tmt:maxchecked")){

			maxchecked = boxGroup.elements[i].getAttribute("tmt:maxchecked");

		}

	}

	// Check if the boxes validate

	obj.isValid = function(){

		var checkCounter = 0;

		for(var i=0; i<boxGroup.elements.length; i++){

		    // For each checked box, increase the counter

			if(boxGroup.elements[i].checked){

				checkCounter++;

			}

		}

		return (checkCounter >=  minchecked) && (checkCounter <= maxchecked);

	}

	return obj;

}



// Radio validator (one for each group of radios sharing the same name)

function tmt_radioValidatorFactory(radioGroup){

	var obj = new tmt_groupValidatorFactory(radioGroup);

	obj.type = "radio";



	obj.isRequired = function(){

		var requiredFlag = false;

		// Since radios from the same group can have conflicting attribute values, the last one win

		for(var i=0; i<radioGroup.elements.length; i++){

			if(radioGroup.elements[i].disabled == false){

				if(radioGroup.elements[i].getAttribute("tmt:required")){

					requiredFlag = radioGroup.elements[i].getAttribute("tmt:required");

				}

			}

		}

		return requiredFlag;

	}

	

	// Check if the radio validate

	obj.isValid = function(){

		if(obj.isRequired()){

			for(var i=0; i<radioGroup.elements.length; i++){

				// As soon as one is checked, we are fine

				if(radioGroup.elements[i].checked){

					return true;

				}

			}

			return false;

		}

		// It's not required, so it's fine

		else{

			return true;

		}	

	}

	return obj;

}



// This global objects store all the validation rules

// Every rule is stored as a method that accepts the field node as argument and return a boolean

var tmt_globalRules = new Object;

tmt_globalRules.datepattern = function(fieldNode){

	var globalObj = tmt_globalDatePatterns[fieldNode.getAttribute("tmt:datepattern")];

	if(globalObj){

		// Split the date into 3 different bits using the separator

		var dateBits = fieldNode.value.split(globalObj.s);

		// First try to create a new date out of the bits

		var testDate = new Date(dateBits[globalObj.y], (dateBits[globalObj.m]-1), dateBits[globalObj.d]);

		// Make sure values match after conversion

		var isDate = (testDate.getFullYear() == dateBits[globalObj.y])

				 && (testDate.getMonth() == dateBits[globalObj.m]-1)

				 && (testDate.getDate() == dateBits[globalObj.d]);

		// If it's a date and it matches the RegExp, it's a go

		return isDate && globalObj.rex.test(fieldNode.value);

	}

}

tmt_globalRules.equalto = function(fieldNode){

	var twinNode = document.getElementById(fieldNode.getAttribute("tmt:equalto"));

	return twinNode.value == fieldNode.value;

}

tmt_globalRules.maxlength = function(fieldNode){

	if(fieldNode.value.length > fieldNode.getAttribute("tmt:maxlength")){

		return false;

	}

	return true;

}

tmt_globalRules.maxnumber = function(fieldNode){

	if(parseFloat(fieldNode.value) > fieldNode.getAttribute("tmt:maxnumber")){

		return false;

	}

	return true;

}

tmt_globalRules.minlength = function(fieldNode){

	if(fieldNode.value.length < fieldNode.getAttribute("tmt:minlength")){

		return false;

	}

	return true;

}

tmt_globalRules.minnumber = function(fieldNode){

	if(parseFloat(fieldNode.value) < fieldNode.getAttribute("tmt:minnumber")){

		return false;

	}

	return true;

}

tmt_globalRules.pattern = function(fieldNode){

	var reg = tmt_globalPatterns[fieldNode.getAttribute("tmt:pattern")];

	if(reg){

		return reg.test(fieldNode.value);

	}

	else{

		// If the pattern is missing, skip it

		return true;	

	}

}



/* Image upload validation */



tmt_globalRules.image = function(fieldNode){

	// If the flag isn't defined we assume things are fine

	if(!fieldNode.isValidImg){

		fieldNode.isValidImg = "true";

	}

	return fieldNode.isValidImg == "true";

}



// Check the currently selected image and set a validity flag

function tmt_validateImg(fieldNode){

	var imgURL = "file:///" + fieldNode.value;

	var img = new Image();

	img.maxSize =  fieldNode.getAttribute("tmt:imagemaxsize");

	img.maxWidth = fieldNode.getAttribute("tmt:imagemaxwidth");

	img.minWidth = fieldNode.getAttribute("tmt:imageminwidth");

	img.maxHeight = fieldNode.getAttribute("tmt:imagemaxheight");

	img.minHeight = fieldNode.getAttribute("tmt:imageminheight");

	// Store a reference to the input field

	img.fieldNode = fieldNode;

	// The image's data can be read only after loading. That's why we need a callback

	img.onload = tmt_validateImgCallback;

	img.src = imgURL;

}



function tmt_validateImgCallback(){

	var errorsCount = 0;

	// Check every constrain and increment the error counter accordingly

	if(this.fileSize && this.maxSize && (this.fileSize/1024) > this.maxSize){

		errorsCount ++;

	}

	if(this.maxWidth && (this.width > this.maxWidth)){

		errorsCount ++;

	}

	if(this.minWidth && (this.width < this.minWidth)){

		errorsCount ++;

	}

	if(this.maxHeight && (this.height > this.maxHeight)){

		errorsCount ++;

	}

	if(this.minHeight && (this.height < this.minHeight)){

		errorsCount ++;

	}

	// Store the valid flag inside the DOM node itself

	this.fieldNode.isValidImg = (errorsCount != 0) ? "false" : "true";

}



// This global objects store all the RegExp patterns for strings

var tmt_globalPatterns = new Object;

tmt_globalPatterns.email = new RegExp("^[\\w\\.=-]+@[\\w\\.-]+\\.[\\w\\.-]{2,4}$");

tmt_globalPatterns.lettersonly = new RegExp("^[a-zA-Z]*$");

tmt_globalPatterns.alphanumeric = new RegExp("^\\w*$");

tmt_globalPatterns.integer = new RegExp("^-?\\d\\d*$");

tmt_globalPatterns.positiveinteger = new RegExp("^\\d\\d*$");

tmt_globalPatterns.number = new RegExp("^-?(\\d\\d*\\.\\d*$)|(^-?\\d\\d*$)|(^-?\\.\\d\\d*$)");

tmt_globalPatterns.filepath_pdf = new RegExp("\\\\[\\w_]*\\.([pP][dD][fF])$");

tmt_globalPatterns.filepath_jpg_gif = new RegExp("\\\\[\\w_]*\\.([gG][iI][fF])|([jJ][pP][eE]?[gG])$");

tmt_globalPatterns.filepath_jpg = new RegExp("\\\\[\\w_]*\\.([jJ][pP][eE]?[gG])$");

tmt_globalPatterns.filepath_zip = new RegExp("\\\\[\\w_]*\\.([zZ][iI][pP])$");

tmt_globalPatterns.filepath = new RegExp("\\\\[\\w_]*\\.\\w{3}$");



// This global objects store all the info required for date validation

var tmt_globalDatePatterns = new Object;

tmt_globalDatePatterns["YYYY-MM-DD"] = tmt_dateInfo("^\([0-9]{4}\)\\-\([0-1][0-9]\)\\-\([0-3][0-9]\)$", 0, 1, 2, "-");

tmt_globalDatePatterns["YYYY-M-D"] = tmt_dateInfo("^\([0-9]{4}\)\\-\([0-1]?[0-9]\)\\-\([0-3]?[0-9]\)$", 0, 1, 2, "-");

tmt_globalDatePatterns["MM.DD.YYYY"] = tmt_dateInfo("^\([0-1][0-9]\)\\.\([0-3][0-9]\)\\.\([0-9]{4}\)$", 2, 0, 1, ".");

tmt_globalDatePatterns["M.D.YYYY"] = tmt_dateInfo("^\([0-1]?[0-9]\)\\.\([0-3]?[0-9]\)\\.\([0-9]{4}\)$", 2, 0, 1, ".");

tmt_globalDatePatterns["MM/DD/YYYY"] = tmt_dateInfo("^\([0-1][0-9]\)\/\([0-3][0-9]\)\/\([0-9]{4}\)$", 2, 0, 1, "/");

tmt_globalDatePatterns["M/D/YYYY"] = tmt_dateInfo("^\([0-1]?[0-9]\)\/\([0-3]?[0-9]\)\/\([0-9]{4}\)$", 2, 0, 1, "/");

tmt_globalDatePatterns["MM-DD-YYYY"] = tmt_dateInfo("^\([0-21][0-9]\)\\-\([0-3][0-9]\)\\-\([0-9]{4}\)$", 2, 0, 1, "-");

tmt_globalDatePatterns["M-D-YYYY"] = tmt_dateInfo("^\([0-1]?[0-9]\)\\-\([0-3]?[0-9]\)\\-\([0-9]{4}\)$", 2, 0, 1, "-");

tmt_globalDatePatterns["DD.MM.YYYY"] = tmt_dateInfo("^\([0-3][0-9]\)\\.\([0-1][0-9]\)\\.\([0-9]{4}\)$", 2, 1, 0, ".");

tmt_globalDatePatterns["D.M.YYYY"] = tmt_dateInfo("^\([0-3]?[0-9]\)\\.\([0-1]?[0-9]\)\\.\([0-9]{4}\)$", 2, 1, 0, ".");

tmt_globalDatePatterns["DD/MM/YYYY"] = tmt_dateInfo("^\([0-3][0-9]\)\/\([0-1][0-9]\)\/\([0-9]{4}\)$", 2, 1, 0, "/");

tmt_globalDatePatterns["D/M/YYYY"] = tmt_dateInfo("^\([0-3]?[0-9]\)\/\([0-1]?[0-9]\)\/\([0-9]{4}\)$", 2, 1, 0, "/");

tmt_globalDatePatterns["DD-MM-YYYY"] = tmt_dateInfo("^\([0-3][0-9]\)\\-\([0-1][0-9]\)\\-\([0-9]{4}\)$", 2, 1, 0, "-");

tmt_globalDatePatterns["D-M-YYYY"] = tmt_dateInfo("^\([0-3]?[0-9]\)\\-\([0-1]?[0-9]\)\\-\([0-9]{4}\)$", 2, 1, 0, "-");



// Create an object that stores date validation's info

function tmt_dateInfo(rex, year, month, day, separator){

	var infoObj = new Object;

	infoObj.rex = new RegExp(rex);

	infoObj.y = year;

	infoObj.m = month;

	infoObj.d = day;

	infoObj.s = separator;

	return infoObj;

}



/* Filters */



// This global objects store all the info required for filters

var tmt_globalFilters = new Object;

tmt_globalFilters.ltrim = tmt_filterInfo("^(\\s*)(\\b[\\w\\W]*)$", "$2");

tmt_globalFilters.rtrim = tmt_filterInfo("^([\\w\\W]*)(\\b\\s*)$", "$1");

tmt_globalFilters.nospaces = tmt_filterInfo("\\s*", "");

tmt_globalFilters.nocommas = tmt_filterInfo(",", "");

tmt_globalFilters.nodots = tmt_filterInfo("\\.", "");

tmt_globalFilters.noquotes = tmt_filterInfo("'", "");

tmt_globalFilters.nodoublequotes = tmt_filterInfo('"', "");

tmt_globalFilters.nohtml = tmt_filterInfo("<[^>]*>", "");

tmt_globalFilters.alphanumericonly = tmt_filterInfo("[^\\w]", "");

tmt_globalFilters.numbersonly = tmt_filterInfo("[^\\d]", "");

tmt_globalFilters.lettersonly = tmt_filterInfo("[^a-zA-Z]", "");

tmt_globalFilters.commastodots = tmt_filterInfo(",", ".");

tmt_globalFilters.dotstocommas = tmt_filterInfo("\\.", ",");

tmt_globalFilters.numberscommas = tmt_filterInfo("[^\\d,]", "");

tmt_globalFilters.numbersdots = tmt_filterInfo("[^\\d\\.]", "");



// Create an object that stores filters's info

function tmt_filterInfo(rex, replaceStr){

	var infoObj = new Object;

	infoObj.rex = new RegExp(rex, "g");

	infoObj.str = replaceStr;

	return infoObj;

}



// Clean up the field based on filter's info

function tmt_filterField(fieldNode){

	var filtersArray = fieldNode.getAttribute("tmt:filters").split(",");

	for(var i=0; i<filtersArray.length; i++){

		var filtObj = tmt_globalFilters[filtersArray[i]];

		// Be sure we have the filter's data, then clean up

		if(filtObj){

			fieldNode.value = fieldNode.value.replace(filtObj.rex, filtObj.str)

		}

		// We handle demoroziner as a special case

		if(filtersArray[i] == "demoronizer"){

			fieldNode.value = tmt_filterDemoronizer(fieldNode.value);

		}

	}

}



// Replace MS Word's non-ISO characters with plausible substitutes

function tmt_filterDemoronizer(str){

	str = str.replace(new RegExp(String.fromCharCode(710), "g"), "^");

	str = str.replace(new RegExp(String.fromCharCode(732), "g"), "~");

	// Evil "smarty" quotes

	str = str.replace(new RegExp(String.fromCharCode(8216), "g"), "'");

	str = str.replace(new RegExp(String.fromCharCode(8217), "g"), "'");

	str = str.replace(new RegExp(String.fromCharCode(8220), "g"), '"');

	str = str.replace(new RegExp(String.fromCharCode(8221), "g"), '"');

	// More MS Word's garbage

	str = str.replace(new RegExp(String.fromCharCode(8211), "g"), "-");

	str = str.replace(new RegExp(String.fromCharCode(8212), "g"), "--");

	str = str.replace(new RegExp(String.fromCharCode(8218), "g"), ",");

	str = str.replace(new RegExp(String.fromCharCode(8222), "g"), ",,");

	str = str.replace(new RegExp(String.fromCharCode(8226), "g"), "*");

	str = str.replace(new RegExp(String.fromCharCode(8230), "g"), "...");

	str = str.replace(new RegExp(String.fromCharCode(8364), "g"), "€");

	return str;

}



/* Helper functions */



// Get an array of submit button nodes contained inside a given node

function tmt_getSubmitNodes(startNode){

	var submitArray = new Array();

	var inputNodes = startNode.getElementsByTagName("input");

	// Get an array of submit nodes

	for(var i=0; i<inputNodes.length; i++){

		if(inputNodes[i].getAttribute("type").toLowerCase() == "submit"){

			submitArray[submitArray.length] = inputNodes[i];

		}

	}

	return submitArray;

}



// Get an array of input and textarea nodes contained inside a given node

function tmt_getTextfieldNodes(startNode){

	var inputsArray = new Array();

	var inputNodes = startNode.getElementsByTagName("input");

	var areaNodes = startNode.getElementsByTagName("textarea");

	// Get an array of text, password and file nodes

	for(var i=0; i<inputNodes.length; i++){

		if(!inputNodes[i].getAttribute("type")){

			inputNodes[i].setAttribute("type", "text");

		}

		var fieldType = inputNodes[i].getAttribute("type").toLowerCase();

		if((fieldType == "text") || (fieldType == "password") || (fieldType == "file") || (fieldType == "hidden")){

			inputsArray[inputsArray.length] = inputNodes[i];

		}

	}

	// Append textarea nodes too

	for(var j=0; j<areaNodes.length; j++){

	    inputsArray[inputsArray.length] = areaNodes[j];

	}

	return inputsArray;

}



// Return an object (sort of an hashtable) containing checkboxes/radios data

// The returned object has two properties:

// name: the group name

// boxes: an array containing the DOM node of each checkbox/radio that share the same name

function tmt_getNodesTable(formNode, type){

	// This object will store data fields, just as an hash table

	var boxHolder = new Object;

	var boxNodes = formNode.getElementsByTagName("input");

	for(var i=0; i<boxNodes.length; i++){

		if(boxNodes[i].getAttribute("type") && (boxNodes[i].getAttribute("type").toLowerCase() == type)){

			// Store the reference to make it easier to read the code

			var boxName = boxNodes[i].name;

			if(boxHolder[boxName]){

				// We already have an entry with the same name

				// Append the DOM node to the relevant entry inside the object

				boxHolder[boxName].elements[boxHolder[boxName].elements.length] = boxNodes[i];

			}

			else{

				// Create a brand new entry inside the object

				boxHolder[boxName] = new Object;

				boxHolder[boxName].name = boxName;

				// Initialize the array that will store all the DOM nodes that share the same name

				boxHolder[boxName].elements = new Array;

				boxHolder[boxName].elements[0] = boxNodes[i];

			}

		}

	}

	return boxHolder;

}



// The function below was developed by John Resig

// For additional info see:

// http://ejohn.org/projects/flexible-javascript-events

// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html

function addEvent(obj, type, fn){

	if(obj.addEventListener){

		obj.addEventListener(type, fn, false);

	}

	else if(obj.attachEvent){

		obj["e" + type + fn] = fn;

		obj[type + fn] = function(){

				obj["e" + type + fn](window.event);

			}

		obj.attachEvent("on" +type, obj[type+fn]);

	}

}



addEvent(window, "load", tmt_validatorInit);
