<!--

////////////////////////////////////////////////////////////////////////
//
//  Javascript Package for spell checking
//
//	These classes are invoked from helper functions which populate the
//	document with results.
//	Soundex can be calculated without a dictionary, but spell-Checking requires
//	one of these dictionaries to be imported:
//
//	(DictionaryListClass-Small.js || DictionaryListClass-Medium.js || DictionaryListClass-Large.js).
//
//	These classes are imported by the following statements:
//
//	<script language="JavaScript" src="DictionaryListClass-Small.js"></script>
//	<script language="JavaScript" src="SpellCheckerClass.js"></script>
//
//	They are instantiated by the following statements:
//
//				myDictionaryList = new DictionaryListClassSmall();
//				myDictionary     = new DictionaryClass(myDictionaryList);
//				mySpellChecker   = new SpellCheckerClass(myDictionary);
//
//
////////////////////////////////////////////////////////////////////////

// begin-PACKAGE


function SpellCheckerClass(objDictionary)
	{
	////////////////////////////////////
	//
	//  SpellCheckerClass (Constructor)
	//
	//	Create an object consisting of
	//	the properties:
	//			strPara, a paragraph of text to check
	//			arrInputWords, an array to hold the split values of the paragraph
	//			iterations, a counter
	//
	//	the methods:
	//			getWordIndex, gets the index of one word in dictionary
	//			paraCheck, checks a paragraph
	//			setStrPara, sets the value of strValue
	//			wordCheck, checks one word
	//
	//
	//	requires: 	DictionaryClass-(Large||Medium||Small), SoundexClass (external JS files)
	//
	//	inputs:  	objDictionary, an instance of the DictionaryClass object
	//	returns: 	void
	//
	//
	//////////////////////////////////////

	// declare methods and properties
	/////////////////////////////////

	this.getParaIndex		= getParaIndex;
	this.getWordIndex		= getWordIndex;
	this.paraCheck			= paraCheck;
	this.rejoin			= rejoin;
	this.replace			= replace;
	this.setIndexToSkip		= setIndexToSkip;
	this.setStrPara			= setStrPara;
	this.wordCheck			= wordCheck;
	this.wordSimilarity		= wordSimilarity;
	this.dictionary			= objDictionary;

	this.arrIndexsToSkip		= new Array();
	this.arrPunctuation		= new Array();
	this.arrSoundexCodes		= new Array("AEHIOUWY","BFPV","CGJKQSXZ","DT","L","MN","R");
	this.inputArray			= null;
	this.iterations			= 0;
	this.paraIndex			= 0;
	this.strPara			= null;
  	this.Soundex      		= new SoundexClass(this.arrSoundexCodes);



	// define methods
	//////////////////



	function getParaIndex()
		{
		//
		// INNER-method
		//
		//
		// Returns the index of the current word
		//
		// inputs:	void
		//
		// returns: intResult, an integer representing the array index of
		//		the word in the paragraph upon which we are operating
		//
		//////////////////////////////////////

		var intResult	= this.paraIndex;
		return intResult;
		}
	//end-INNER-Method




	function getWordIndex(strWord)
		{
		//
		// INNER-method
		//
		//
		// Seeks a word in the dictionary
		//
		// inputs: strWord, the word to be located
		//
		// returns: arrResult, an array of 5 elements
		//            arrResult[0]: boolean indicating search success.
		//            arrResult[1]: the sought word.
		//            arrResult[2]: the index.
		//            arrResult[3]: the word at the index.
		//            arrResult[4]: if success, null. if failure, an array of suggestions.
		//
		//////////////////////////////////////

		var arrResult	= this.dictionary.search(strWord);
		return arrResult;
		}
	//end-INNER-Method



	function paraCheck(strPara)
		{
		//
		// INNER-Method
		//
		//
		// Scans for words in our paragraph, tests each one
		// to detect spelling errors.  Stops to process the first
		// error detected.
		//
		// inputs: 	strPara, the paragraph to be checked
		//
		// returns: arrResult, an array of 5 elements
		//            arrResult[0]: boolean indicating search success.
		//            arrResult[1]: the sought word.
		//            arrResult[2]: the index.
		//            arrResult[3]: the word at the index.
		//            arrResult[4]: if success, null. if failure, an array of suggestions.
		//
		//////////////////////////////////////

		this.iterations		= 0;
		var strValue;

		// create the result array
		var arrResult = new Array(true,"",0,"",null);

		// optimize  don't recreate arrays

		//  split strPara by pattern matching
		var myPattern= /[\s,!\.\?]/;
		this.arrInputWords = strPara.split(myPattern);

		// capture punctuation for later
		this.arrPunctuation = new Array();



		for(var i=0;i<strPara.length;i++)
			{
			if(strPara.charAt(i)==" ")
				{
				var charCheck = strPara.charAt(i-1);
				if(charCheck=="," || charCheck=="." || charCheck=="!" || charCheck=="?")
					{
					this.arrPunctuation[this.arrPunctuation.length] = charCheck+" ";
					}
				else
					{
					this.arrPunctuation[this.arrPunctuation.length] = " ";
					}
				//endIf
				}
			//endIf
			}
		//endFor


		if(this.arrPunctuation.length<strPara.length)
			{
			// capture final punctuation
			var charCheck = strPara.charAt(strPara.length-1);
			if(charCheck=="," || charCheck=="." || charCheck=="!" || charCheck=="?")
				{
				this.arrPunctuation[this.arrPunctuation.length] = charCheck+" ";
				}
			else
				{
				this.arrPunctuation[this.arrPunctuation.length] = " ";
				}
			//endIf
			}
		//endIf


		for(var i=0;i<this.arrInputWords.length;i++)
			{
			// skip this entry if requested
			var boolSkip = false;
			for(var k=0;k<this.arrIndexsToSkip.length;k++)
				{
				if(this.arrIndexsToSkip[k]==i)
					boolSkip = true;
				//endIf
				}
			//endFor


			if(boolSkip)
				{
				continue;
				}
			else
				{
				strValue	= this.arrInputWords[i].toLowerCase();
				arrResult	= this.dictionary.search(strValue);

				if(arrResult[0] == false)
					{
					// the word was not found in the dictionary
					// create a suggestions array based on soundex comparisons
					// test all words in dictionary with same beginning letter

					var txtSeekSoundex=this.Soundex.doSoundex(strValue)[0];

					var charLetter		= arrResult[1].charAt(0);
					var intNewFloor		= this.getWordIndex(charLetter+"1")[2];
					var intNewCeiling	= this.getWordIndex(charLetter+"zzzzz")[2];

					for(var j=intNewFloor;j<intNewCeiling;j++)
						{
						var txtSoundex	= this.Soundex.doSoundex(this.dictionary.words[j])[0];

						if(txtSeekSoundex==txtSoundex)
							{
							arrResult[4][arrResult[4].length] = this.dictionary.words[j];
							}
						//endIf
						}
					//endFor

					// select the 10 best matches based upon the kirchmeir similarity algorithm
					arrResult[4] = this.wordSimilarity(strValue,arrResult[4]);

					this.paraIndex=i;
					break;
					}
				//endIf
				}
			//endIf-else
			}
		//endFor

		return arrResult;
		}
	//end-INNER-Method



	function rejoin()
		{
		//
		// INNER-Method
		//
		//
		// Rejoins the arrInputWords elements into a string, replacing the
		// original punctuation.
		//
		// inputs: 	void
		//		strResult, a string constructed from arrInputWords
		//
		// returns: 	void
		//
		//////////////////////////////////////

		var strResult = "";

		for(var i=0;i<this.arrInputWords.length;i++)
			{
			strResult = strResult + this.arrInputWords[i] + this.arrPunctuation[i];
			}
		//endFor
		return strResult;
		}
	//end-INNER-Method



	function replace(strValue,nodeName)
		{
		//
		// INNER-method
		//
		//
		// Replaces text in our document, and reconstructs it
		//
		// inputs: 	strValue, the string to be inserted
		//		nodeName, the name of the document node containing the paragraph
		//
		// returns: void
		//
		//////////////////////////////////////

		var txtOldValue = this.arrInputWords[this.paraIndex];

		// preserve capitalization
		if(txtOldValue.toUpperCase().charAt(0) == txtOldValue.charAt(0))
			{
			var strValueUc = strValue.toUpperCase();
			strValue=strValueUc.charAt(0)+strValue.substr(1);
			}
		//endIf

		this.arrInputWords[this.paraIndex]			= strValue;
		document.getElementById(nodeName).value = this.rejoin();
		}
	//end-INNER-Method



	function setIndexToSkip(intIndex)
		{
		//
		// INNER-method
		//
		//
		// Adds an index to the array of words to be skipped in paragraph
		//
		// inputs: intIndex, the index to be ignored
		//
		// returns: void
		//
		//////////////////////////////////////

		this.arrIndexsToSkip[this.arrIndexsToSkip.length] = intIndex;
		}
	//end-INNER-Method



	function setSoundexCodes(arrSoundexCodes)
		{
		//
		// INNER-method
		//
		//
		// Sets (overrides) the arrSoundexCodes which will used for soundex
		//
		// inputs: 	arrSoundexCodes, the new array of soundex values
		//
		// returns: 	void
		//
		//////////////////////////////////////

		this.arrSoundexCodes = arrSoundexCodes;
		}
	//end-INNER-Method



	function setStrPara(strPara)
		{
		//
		// INNER-method
		//
		//
		// Sets the paragraph text which will be checked
		//
		// inputs:	strPara, the text to be checked
		//
		// returns:	void
		//
		//////////////////////////////////////

		this.strPara = strPara;
		}
	//end-INNER-Method



	function wordCheck(strWord)
		{
		//
		// INNER-method
		//
		//
		// Seeks a word in the dictionary
		//
		// inputs: 	strWord, the word to be located
		//
		// returns: 	arrResult, an array of 5 elements
		//           		arrResult[0]: boolean indicating search success.
		//            		arrResult[1]: the sought word.
		//            		arrResult[2]: the index.
		//            		arrResult[3]: the word at the index.
		//            		arrResult[4]: if success, null. if failure, an array of suggestions.
		//
		//////////////////////////////////////

		var arrResult	= this.dictionary.search(strWord.toLowerCase());
		return arrResult;
		}
	//end-INNER-Method



	function wordSimilarity (strWord, arrTryWords)
		{
		//
		// INNER-method
		//
		//	Applies the Kirchmeier similarity algorithm (www.kirchmeier.org)
		// 	to a resultset, and returns the 10 best suggestions.
		//
		//
		// inputs:	strWord, the word which was mis-spelled
		//		arrTryWords, an array of possible spellings
		//
		//
		// returns:	arrResult, an array of the 10 best suggestions
		//
		//////////////////////////////////////

		var arrSorted				= new Array();
		var arrResult       = new Array();

		var intPerfectScore	= (strWord.length*2)+3;


		function sortNumbers(a, b)
			{
			// INNER-Method
			return  b[0]-a[0];
			}
		//end-INNER-Method


		for(var i=0, len = arrTryWords.length;i<len;i++)
			{
			var strLongWord;
			var strShortWord;
			var strTryWord	= arrTryWords[i];
			var intScore	= 3-(Math.abs(strTryWord.length-strWord.length));

			if(strTryWord.length>strWord.length)
				{
				strShortWord	= strWord;
				strLongWord	= strTryWord;
				}
			else
				{
				strShortWord	= strTryWord;
				strLongWord	= strWord;
				}
			//endIf-else


			// scan forward for shared position letters
			for(var j=0, len2=strShortWord.length;j<len2;j++)
				{
				if(strShortWord.charAt(j)==strLongWord.charAt(j))
					intScore = intScore + 1;
				//endIf
				}
			//endFor


			// scan backward for shared position letters
			for(var j=strShortWord.length-1;j>-1;j--)
				{
				if(strShortWord.charAt(j)==strLongWord.charAt(j))
					intScore = intScore + 1;
				//endIf
				}
			//endFor

			var similarity = intScore/intPerfectScore;
			arrSorted[arrSorted.length] = new Array (similarity,strTryWord);
			}
		//endFor

		arrSorted.sort(sortNumbers);

		// prune list to 10 best suggestions
		if(arrSorted.length>10)
			arrSorted.length=10;
		//endIf


		for(var i=0, len=arrSorted.length;i<len;i++)
			{
			arrResult[i] = arrSorted[i][1];
			}
		//endFor

		return arrResult;
		}
	//end-INNER-Method
	}
//end-CLASS








function DictionaryClass(objDictionaryList)
	{
	////////////////////////////////////
	//
	//  Dictionary class (Constructor)
	//
	//	Create an object consisting of
	//	an array of words and a search method
	//
	//	inputs:		objDictionaryList, an instance of the DictionaryListClass object
	//	returns:	void
	//
	//
	//////////////////////////////////////

	// declare methods and properties
	/////////////////////////////////
	this.addWord	= dictionaryAddWord;
	this.alert	= dictionaryAlert;
	this.set	=	dictionarySet;
	this.search	= dictionarySearch;

	this.iterations	= 0;
	this.words	= objDictionaryList.words;


	// define methods
	//////////////////


	function dictionaryAddWord(strWord)
		{
		//////////////////////////////////
		//
		// INNER-Method
		//
		/////////////////////////////////
		this.words[this.words.length] = strWord;
		}
	//end-INNER-Method



	function dictionaryAlert()
		{
		//////////////////////////////////
		//
		// INNER-Method
		//
		/////////////////////////////////
		alert("dictionaryAlert");
		}
	//end-INNER-Method



	function dictionarySearch(strSeek, indexFloor, indexCeiling)
		{
		//////////////////////////////////
		//
		// INNER-Method
		//
		// perform a binary search
		//
		/////////////////////////////////
		this.iterations		= this.iterations+1;
		if(this.iterations>100) return "exceed iterations";
		if(indexFloor==undefined||indexFloor==null)	indexFloor	= 0;
		if(indexCeiling==undefined||indexCeiling==null)	indexCeiling = this.words.length;

		var found			= false;
		var end				= false;

		var indexCurr = Math.floor(indexFloor+((indexCeiling-indexFloor)/2));

		if(strSeek==this.words[indexCurr])
			found = true;
		else if(indexCurr==this.words.length-1)
			end = true;
		else if(indexCurr==0)
			end = true;
		else if(strSeek<this.words[indexCurr])
			indexCeiling = indexCurr;
		else if(strSeek>this.words[indexCurr])
			indexFloor = indexCurr;
		//endIf-elseIf

		if(indexCeiling-indexFloor<2)
			{
			if(strSeek==this.words[indexCeiling])
				{
				found		= true;
				indexCurr	= indexCeiling;
				}
			else if(strSeek==this.words[indexFloor])
				{
				found		= true;
				indexCurr	= indexFloor;
				}
			else
				{
				end = true;
				}
			//endIf-ElseIf
			}
		//endIf

		if(found == true)
			{
			// found a correct spelling, return it
			var results	= new Array();
			results[0]	= true;
			results[1]	= strSeek;
			results[2]	= indexCurr;
			results[3]	= this.words[indexCurr];

			this.iterations = 0;
			return results;
			}
		else if(end == true)
			{
			// didnt find it, suggest nearest words
			var results	= new Array();
			results[0]	= false;
			results[1]	= strSeek;
			results[2]	= indexCurr;
			results[3]	= this.words[indexCurr];

			var suggest = new Array();
			if(indexCurr>=1)
				suggest[suggest.length] = this.words[indexCurr-1];
			if(indexCurr<this.words.length-1)
				suggest[suggest.length] = this.words[indexCurr+1];

			results[4]	= suggest;
			this.iterations = 0;
			return results;
			}
		else
		  // recursively continue searching
			return this.search(strSeek, indexFloor, indexCeiling);
		//endIf-elseIf
		}
	//end-INNER-Method



	function dictionarySet(arrWords)
		{
		//////////////////////////////////
		//
		// INNER-Method
		//
		/////////////////////////////////
		this.words = arrWords.sort();
		}
	//end-INNER-Method


	}
//end-CLASS








function SoundexClass(arrSoundexCodes)
	{
	//////////////////////////////////////
	//
	// Soundex class (Constructor)
	//
	//
	//	Creates an object with one method: doSoundex()
	//
	//	inputs:		arrSoundexCodes, an array of soundex values
	//
	//	returns:	void
	//
	//
	//////////////////////////////////////

	if(!arrSoundexCodes || arrSoundexCodes == null || arrSoundexCodes == undefined)
		{
		arrSoundexCodes	= new Array("AEHIOUWY","BFPV","CGJKQSXZ","DT","L","MN","R");
		}
	//endIf


	var mySoundex = new Array();
	// Create associative array
	for(var i=0;i<arrSoundexCodes.length;i++)
		{
		var myValue = arrSoundexCodes[i];
		for(var j=0;j<myValue.length;j++)
			{
			var myChar		= myValue.charAt(j);
			mySoundex[""+myChar]	= i;
			}
		//endFor
		}
	//endFor


	// declare methods and properties
	/////////////////////////////////
	this.doSoundex		= doSoundex;
	this.arrSoundexCodes	= arrSoundexCodes;


	// define methods
	//////////////////


	function doSoundex(strInput)
		{
		//
		// INNER-method
		//
		//
		// Calculates a soundex on an input string
		// using the supplied array of soundex values
		//
		// inputs: stringInput, the string to be evaluated
		//
		// returns: result, an array of two elements
		//            result[0]: soundex value
		//            result[1]: a message
		//
		//////////////////////////////////////

		if(!((strInput=="") || (strInput==null)))
			{
			var result	= stringClean(strInput);
			strInput	= result[0];
			var msg		= result[1];
			var soundexCode	= toSoundexCode2(strInput);
			var result	= new Array(soundexCode, msg);
			return result;
			}
		else
			{
			return new Array("1-999","no input to doSoundex");
			}
		//endIf
		}
	//endFunction


	function stringClean(inputString)
		{
		//
		// INNER-method
		//
		var msg						=	"";
		var resultString	= "";
		var isNonAlpha		= false;
		var testString		= "abcdefghijklmnopqrstuvwxyz";

		inputString=inputString.toLowerCase();

		for(var i=0, len=inputString.length;i<len;i++)
			{
			var testChar = inputString.charAt(i);

			if(testString.indexOf(testChar)>-1)
				resultString = resultString+testChar;
			else
				isNonAlpha = true;
			//endIf
			}
		//endFor

		if(isNonAlpha)
				msg = ("Name included non-Alpha.  Proceeding with <u>" +resultString+"</u>");
		//endIf
		var result = new Array(resultString.toUpperCase(), msg);
		return result;
		}
	//end-INNER-method



	function toSoundexCode1(strInput)
		{
		//
		// INNER-method
		//
		var strSoundex		= "";
		var soundexDigit	= null;
		var previousDigit	= null;
		var intSoundexLen	= 0;
		var arrSoundexCodes2	= this.arrSoundexCodes; // create local reference for loop efficiency

		for (i=1, len=strInput.length;(i<len && intSoundexLen<3);i++)
			{
			// find soundex digit code for each char
			var testChar = strInput.charAt(i);

			// find this char in the soundex array
			for(var j=0, len2=arrSoundexCodes2.length;j<len2;j++)
				{
				if(arrSoundexCodes2[j].indexOf(testChar)>-1)
					{
					soundexDigit=j;
					break;
					}
				//endIf
				}
			//endFor

			if(soundexDigit!=0 && (soundexDigit!=previousDigit))
				{
				// non-zero and non-repeating
				strSoundex	= strSoundex + soundexDigit;
				intSoundexLen 	= intSoundexLen + 1;
				previousDigit	= soundexDigit;
				}
			//endIf
			}
		//endFor

		strSoundex	= strInput.charAt(0) + "-" + strSoundex;

		for(var i=0, len = 5-strSoundex.length;i<len;i++)
			{
			// pad with zeroes until length==5
			strSoundex = strSoundex + "0";
			}
		//endIf

		return strSoundex;
		}
	//end-INNER-method



	function toSoundexCode2(strInput)
		{
		//
		// INNER-method
		//
		var strSoundex		= "";
		var soundexDigit	= null;
		var previousDigit	= null;
		var intSoundexLen	= 0;
		var arrSoundexCodes2	= this.arrSoundexCodes; // create local reference for loop efficiency

		for (i=1, len=strInput.length;(i<len && intSoundexLen<3);i++)
			{
			// find soundex code for each char
			var testChar = strInput.charAt(i);

			// find this char in the soundex array
			soundexDigit = mySoundex[testChar.toUpperCase()];


			if(soundexDigit!=0 && (soundexDigit!=previousDigit))
				{
				// non-zero and non-repeating
				strSoundex	= strSoundex + soundexDigit;
				intSoundexLen 	= intSoundexLen + 1;
				previousDigit	= soundexDigit;
				}
			//endIf
			}
		//endFor

		strSoundex	= strInput.charAt(0) + "-" + strSoundex;

		for(var i=0, len = 5-strSoundex.length;i<len;i++)
			{
			// pad with zeroes until length==5
			strSoundex = strSoundex + "0";
			}
		//endIf

		return strSoundex;
		}
	//end-INNER-method

	}
//end-CLASS




// end-PACKAGE



// -->
