Click here to Skip to main content
15,881,882 members
Articles / Artificial Intelligence / Neural Networks

Multiple convolution neural networks approach for online handwriting recognition

Rate me:
Please Sign up or sign in to vote.
4.95/5 (37 votes)
9 Apr 2013CPOL8 min read 75.7K   25.1K   74  
The research focuses on the presentation of word recognition technique for an online handwriting recognition system which uses multiple component neural networks (MCNN) as the exchangeable parts of the classifier.
// Copyright (c) 2003, Paul Welter
// All rights reserved.

using System;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Globalization;
using System.Collections.Generic;
using SpellChecker.Forms;
using SpellChecker.Dictionary;
using SpellChecker.Dictionary.Affix;
using SpellChecker.Dictionary.Phonetic;


namespace SpellChecker
{
	/// <summary>
	///		The Spelling class encapsulates the functions necessary to check
	///		the spelling of inputted text.
	/// </summary>
	[ToolboxBitmap(typeof(SpellChecker.NewSpelling), "Spelling.bmp")]
	public class MultipleSpelling : System.ComponentModel.Component
	{

		#region Global Regex
		// Regex are class scope and compiled to improve performance on reuse
		private Regex _digitRegex = new Regex(@"^\d", RegexOptions.Compiled);
		private Regex _htmlRegex = new Regex(@"</[c-g\d]+>|</[i-o\d]+>|</[a\d]+>|</[q-z\d]+>|<[cg]+[^>]*>|<[i-o]+[^>]*>|<[q-z]+[^>]*>|<[a]+[^>]*>|<(\[^\]*\|'[^']*'|[^'\>])*>", RegexOptions.IgnoreCase & RegexOptions.Compiled);
		private MatchCollection _htmlTags;
		private Regex _letterRegex = new Regex(@"\D", RegexOptions.Compiled);
		private Regex _upperRegex = new Regex(@"[^A-Z]", RegexOptions.Compiled);
		private Regex _wordEx = new Regex(@"\b[A-Za-z0-9_'�-�]+\b", RegexOptions.Compiled);
		private MatchCollection _words;
		#endregion

		#region private variables
		private System.ComponentModel.Container components = null;
		#endregion

		#region Events

		/// <summary>
		///     This event is fired when a word is deleted
		/// </summary>
		/// <remarks>
		///		Use this event to update the parent text
		/// </remarks>
		public event DeletedWordEventHandler DeletedWord;

		/// <summary>
		///     This event is fired when word is detected two times in a row
		/// </summary>
		public event DoubledWordEventHandler DoubledWord;

		/// <summary>
		///     This event is fired when the spell checker reaches the end of
		///     the text in the Text property
		/// </summary>
		public event EndOfTextEventHandler EndOfText;

		/// <summary>
		///     This event is fired when a word is skipped
		/// </summary>
		public event IgnoredWordEventHandler IgnoredWord;

		/// <summary>
		///     This event is fired when the spell checker finds a word that 
		///     is not in the dictionaries
		/// </summary>
		public event MisspelledWordEventHandler MisspelledWord;

		/// <summary>
		///     This event is fired when a word is replace
		/// </summary>
		/// <remarks>
		///		Use this event to update the parent text
		/// </remarks>
		public event ReplacedWordEventHandler ReplacedWord;


		/// <summary>
		///     This represents the delegate method prototype that
		///     event receivers must implement
		/// </summary>
		public delegate void DeletedWordEventHandler(object sender, SpellingEventArgs e);

		/// <summary>
		///     This represents the delegate method prototype that
		///     event receivers must implement
		/// </summary>
		public delegate void DoubledWordEventHandler(object sender, SpellingEventArgs e);

		/// <summary>
		///     This represents the delegate method prototype that
		///     event receivers must implement
		/// </summary>
		public delegate void EndOfTextEventHandler(object sender, System.EventArgs e);

		/// <summary>
		///     This represents the delegate method prototype that
		///     event receivers must implement
		/// </summary>
		public delegate void IgnoredWordEventHandler(object sender, SpellingEventArgs e);

		/// <summary>
		///     This represents the delegate method prototype that
		///     event receivers must implement
		/// </summary>
		public delegate void MisspelledWordEventHandler(object sender, SpellingEventArgs e);

		/// <summary>
		///     This represents the delegate method prototype that
		///     event receivers must implement
		/// </summary>
		public delegate void ReplacedWordEventHandler(object sender, ReplaceWordEventArgs e);

		/// <summary>
		///     This is the method that is responsible for notifying
		///     receivers that the event occurred
		/// </summary>
		protected virtual void OnDeletedWord(SpellingEventArgs e)
		{
			if (DeletedWord != null)
			{
				DeletedWord(this, e);
			}
		}

		/// <summary>
		///     This is the method that is responsible for notifying
		///     receivers that the event occurred
		/// </summary>
		protected virtual void OnDoubledWord(SpellingEventArgs e)
		{
			if (DoubledWord != null)
			{
				DoubledWord(this, e);
			}
		}

		/// <summary>
		///     This is the method that is responsible for notifying
		///     receivers that the event occurred
		/// </summary>
		protected virtual void OnEndOfText(System.EventArgs e)
		{
			if (EndOfText != null)
			{
				EndOfText(this, e);
			}
		}

		/// <summary>
		///     This is the method that is responsible for notifying
		///     receivers that the event occurred
		/// </summary>
		protected virtual void OnIgnoredWord(SpellingEventArgs e)
		{
			if (IgnoredWord != null)
			{
				IgnoredWord(this, e);
			}
		}

		/// <summary>
		///     This is the method that is responsible for notifying
		///     receivers that the event occurred
		/// </summary>
		protected virtual void OnMisspelledWord(SpellingEventArgs e)
		{
			if (MisspelledWord != null)
			{
				MisspelledWord(this, e);
			}
		}

		/// <summary>
		///     This is the method that is responsible for notifying
		///     receivers that the event occurred
		/// </summary>
		protected virtual void OnReplacedWord(ReplaceWordEventArgs e)
		{
			if (ReplacedWord != null)
			{
				ReplacedWord(this, e);
			}
		}

		#endregion

		#region Constructors
		/// <summary>
		///     Initializes a new instance of the SpellCheck class
		/// </summary>
		public MultipleSpelling()
		{
			InitializeComponent();
		}


		/// <summary>
		///     Required for Windows.Forms Class Composition Designer support
		/// </summary>
        public MultipleSpelling(System.ComponentModel.IContainer container)
		{
			container.Add(this);
			InitializeComponent();
		}

		#endregion

		#region private methods

		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
					components.Dispose();
				
			}
			base.Dispose( disposing );
		}

		/// <summary>
		///     Calculates the words from the Text property
		/// </summary>
		private void CalculateWords()
		{
			// splits the text into words
			_words = _wordEx.Matches(_text.ToString());
			
			// remark html
			this.MarkHtml();
		}

		/// <summary>
		///     Determines if the string should be spell checked
		/// </summary>
		/// <param name="characters" type="string">
		///     <para>
		///         The Characters to check
		///     </para>
		/// </param>
		/// <returns>
		///     Returns true if the string should be spell checked
		/// </returns>
		private bool CheckString(string characters)
		{
			if(_ignoreAllCapsWords && !_upperRegex.IsMatch(characters))
			{
				return false;
			}
			if(_ignoreWordsWithDigits && _digitRegex.IsMatch(characters))
			{
				return false;
			}
			if(!_letterRegex.IsMatch(characters))
			{
				return false;
			}
			if(_ignoreHtml)
			{
				int startIndex = _words[this.WordIndex].Index;
				
				foreach (Match item in _htmlTags) 
				{
					if (startIndex >= item.Index && startIndex <= item.Index + item.Length - 1)
					{
						return false;
					}
				}
			}
			return true;
		}

		private void Initialize()
		{
            if (_dictionaries == null)
            {
                _dictionaries = new DictionaryCollection();
            }
            else
            {
                foreach (WordDictionary _dictionary in _dictionaries)
                {
                    if (!_dictionary.Initialized)
                        _dictionary.Initialize();
                }
                _isDictionariesInitialized = true;
            }

		}
		/// <summary>
		///     Calculates the position of html tags in the Text property
		/// </summary>
		private void MarkHtml()
		{
			// splits the text into words
			_htmlTags = _htmlRegex.Matches(_text.ToString());
		}

		/// <summary>
		///     Resets the public properties
		/// </summary>
		private void Reset()
		{
			_wordIndex = 0; // reset word index
			_replacementWord = "";
			_suggestions.Clear();
		}

		#endregion

		#region ISpell Near Miss Suggetion methods

		/// <summary>
		///		swap out each char one by one and try all the tryme
		///		chars in its place to see if that makes a good word
		/// </summary>
		private void BadChar(ref ArrayList tempSuggestion)
		{
            foreach (WordDictionary dictionary in Dictionaries)
            {
                for (int i = 0; i < this.CurrentWord.Length; i++)
                {
                    StringBuilder tempWord = new StringBuilder(this.CurrentWord);
                    char[] tryme = dictionary.TryCharacters.ToCharArray();
                    for (int x = 0; x < tryme.Length; x++)
                    {
                        tempWord[i] = tryme[x];
                        if (this.TestWord(tempWord.ToString()))
                        {
                            Word ws = new Word();
                            ws.Text = tempWord.ToString().ToLower();
                            ws.EditDistance = this.EditDistance(this.CurrentWord, tempWord.ToString());

                            tempSuggestion.Add(ws);
                        }
                    }
                }
            }
		}

		/// <summary>
		///     try omitting one char of word at a time
		/// </summary>
		private void ExtraChar(ref ArrayList tempSuggestion)
		{
			if (this.CurrentWord.Length > 1) 
			{
				for (int i = 0; i < this.CurrentWord.Length; i++)
				{
					StringBuilder tempWord = new StringBuilder(this.CurrentWord);
					tempWord.Remove(i, 1);

					if (this.TestWord(tempWord.ToString())) 
					{
						Word ws = new Word();
						ws.Text = tempWord.ToString().ToLower(CultureInfo.CurrentUICulture);
						ws.EditDistance = this.EditDistance(this.CurrentWord, tempWord.ToString());
				
						tempSuggestion.Add(ws);
					}
								 
				}
			}
		}

		/// <summary>
		///     try inserting a tryme character before every letter
		/// </summary>
		private void ForgotChar(ref ArrayList tempSuggestion)
		{
            foreach (WordDictionary dictionary in Dictionaries)
            {

                char[] tryme = dictionary.TryCharacters.ToCharArray();

                for (int i = 0; i <= this.CurrentWord.Length; i++)
                {
                    for (int x = 0; x < tryme.Length; x++)
                    {
                        StringBuilder tempWord = new StringBuilder(this.CurrentWord);

                        tempWord.Insert(i, tryme[x]);
                        if (this.TestWord(tempWord.ToString()))
                        {
                            Word ws = new Word();
                            ws.Text = tempWord.ToString().ToLower();
                            ws.EditDistance = this.EditDistance(this.CurrentWord, tempWord.ToString());

                            tempSuggestion.Add(ws);
                        }
                    }
                }
            }
		}

		/// <summary>
		///     suggestions for a typical fault of spelling, that
		///		differs with more, than 1 letter from the right form.
		/// </summary>
		private void ReplaceChars(ref ArrayList tempSuggestion)
		{
            foreach (WordDictionary dictionary in Dictionaries)
            {
                ArrayList replacementChars = dictionary.ReplaceCharacters;
                for (int i = 0; i < replacementChars.Count; i++)
                {
                    int split = ((string)replacementChars[i]).IndexOf(' ');
                    string key = ((string)replacementChars[i]).Substring(0, split);
                    string replacement = ((string)replacementChars[i]).Substring(split + 1);

                    int pos = this.CurrentWord.IndexOf(key);
                    while (pos > -1)
                    {
                        string tempWord = this.CurrentWord.Substring(0, pos);
                        tempWord += replacement;
                        tempWord += this.CurrentWord.Substring(pos + key.Length);

                        if (this.TestWord(tempWord))
                        {
                            Word ws = new Word();
                            ws.Text = tempWord.ToString().ToLower();
                            ws.EditDistance = this.EditDistance(this.CurrentWord, tempWord.ToString());

                            tempSuggestion.Add(ws);
                        }
                        pos = this.CurrentWord.IndexOf(key, pos + 1);
                    }
                }
            }
		}

		/// <summary>
		///     try swapping adjacent chars one by one
		/// </summary>
		private void SwapChar(ref ArrayList tempSuggestion)
		{
			for (int i = 0; i < this.CurrentWord.Length - 1; i++)
			{
				StringBuilder tempWord = new StringBuilder(this.CurrentWord);
				
				char swap = tempWord[i];
				tempWord[i] = tempWord[i+1];
				tempWord[i+1] = swap;

				if (this.TestWord(tempWord.ToString())) 
				{
					
					Word ws = new Word();
					ws.Text = tempWord.ToString().ToLower();
					ws.EditDistance = this.EditDistance(this.CurrentWord, tempWord.ToString());
				
					tempSuggestion.Add(ws);
				}	 
			}
		}
		
		/// <summary>
		///     split the string into two pieces after every char
		///		if both pieces are good words make them a suggestion
		/// </summary>
		private void TwoWords(ref ArrayList tempSuggestion)
		{
			for (int i = 1; i < this.CurrentWord.Length - 1; i++)
			{
				string firstWord = this.CurrentWord.Substring(0,i);
				string secondWord = this.CurrentWord.Substring(i);
				
				if (this.TestWord(firstWord) && this.TestWord(secondWord)) 
				{
					string tempWord = firstWord + " " + secondWord;
					
					Word ws = new Word();
					ws.Text = tempWord.ToString().ToLower();
					ws.EditDistance = this.EditDistance(this.CurrentWord, tempWord.ToString());
				
					tempSuggestion.Add(ws);
				}	 
			}
		}

        /// <summary>
        ///		swap out each char one by one and try all the tryme
        ///		chars in its place to see if that makes a good word
        /// </summary>
        private bool BadChar(out String result)
        {
            return BadChar(this.CurrentWord, out result);
        }
        /// <summary>
        ///		swap out each char one by one and try all the tryme
        ///		chars in its place to see if that makes a good word
        /// </summary>
        private bool BadChar(String word, out String result)
        {
            result = "";
            bool isFoundWord = false;
            foreach (WordDictionary dictionary in Dictionaries)
            {
                for (int i = 0; i < word.Length; i++)
                {
                    StringBuilder tempWord = new StringBuilder(word);
                    char[] tryme = dictionary.TryCharacters.ToCharArray();
                    for (int x = 0; x < tryme.Length; x++)
                    {
                        tempWord[i] = tryme[x];
                        if (this.TestWord(tempWord.ToString()))
                        {
                            result = tempWord.ToString();
                            isFoundWord = true;
                            return isFoundWord;
                        }
                    }
                }
            }
            return isFoundWord;
        }
        /// <summary>
        ///     try omitting one char of word at a time
        /// </summary>
        private bool ExtraChar(out String result)
        {
            return ExtraChar(this.CurrentWord, out result);

        }
        /// <summary>
        ///     try omitting one char of word at a time
        /// </summary>
        private bool ExtraChar(String word, out String result)
        {
            result = "";
            bool isFoundWord = false;
            if (word.Length > 1)
            {
                for (int i = 0; i < word.Length; i++)
                {
                    StringBuilder tempWord = new StringBuilder(word);
                    tempWord.Remove(i, 1);

                    if (this.TestWord(tempWord.ToString()))
                    {
                        result = tempWord.ToString();
                        isFoundWord = true;
                        break;
                    }

                }
            }
            return isFoundWord;
        }
        /// <summary>
        ///     try inserting a tryme character before every letter
        /// </summary>
        private bool ForgotChar(out String result)
        {
            return ForgotChar(this.CurrentWord, out result);
        }
        /// <summary>
        ///     try inserting a tryme character before every letter
        /// </summary>
        private bool ForgotChar(String word, out String result)
        {
            result = "";
            bool isFoundWord = false;
            foreach (WordDictionary dictionary in Dictionaries)
            {
                char[] tryme = dictionary.TryCharacters.ToCharArray();

                for (int i = 0; i <= word.Length; i++)
                {
                    for (int x = 0; x < tryme.Length; x++)
                    {
                        StringBuilder tempWord = new StringBuilder(word);

                        tempWord.Insert(i, tryme[x]);
                        if (this.TestWord(tempWord.ToString()))
                        {

                            result = tempWord.ToString();
                            isFoundWord = true;
                            return isFoundWord;
                        }
                    }
                }
            }
            return isFoundWord;
        }
        /// <summary>
        ///     suggestions for a typical fault of spelling, that
        ///		differs with more, than 1 letter from the right form.
        /// </summary>
        private bool ReplaceChars(out String result)
        {
            return ReplaceChars(this.CurrentWord, out result);
        }
        /// <summary>
        ///     suggestions for a typical fault of spelling, that
        ///		differs with more, than 1 letter from the right form.
        /// </summary>
        private bool ReplaceChars(String word, out String result)
        {
            result = "";
            bool isFoundWord = false;
            foreach (WordDictionary dictionary in Dictionaries)
            {
                ArrayList replacementChars = dictionary.ReplaceCharacters;
                for (int i = 0; i < replacementChars.Count; i++)
                {
                    int split = ((string)replacementChars[i]).IndexOf(' ');
                    string key = ((string)replacementChars[i]).Substring(0, split);
                    string replacement = ((string)replacementChars[i]).Substring(split + 1);

                    int pos = word.IndexOf(key);
                    while (pos > -1)
                    {
                        string tempWord = word.Substring(0, pos);
                        tempWord += replacement;
                        tempWord += word.Substring(pos + key.Length);

                        if (this.TestWord(tempWord))
                        {

                            result = tempWord.ToString();
                            isFoundWord = true;
                            return isFoundWord;
                        }
                        pos = word.IndexOf(key, pos + 1);
                    }
                }
            }
            return isFoundWord;
        }
        /// <summary>
        ///     try swapping adjacent chars one by one
        /// </summary>
        private bool SwapChar(out String result)
        {
            return SwapChar(this.CurrentWord,out result);
        }
        /// <summary>
        ///     try swapping adjacent chars one by one
        /// </summary>
        private bool SwapChar(String word, out String result)
        {
            result = "";
            bool isFoundWord = false;
            foreach (WordDictionary dictionary in Dictionaries)
            {
                for (int i = 0; i < word.Length - 1; i++)
                {
                    StringBuilder tempWord = new StringBuilder(word);

                    char swap = tempWord[i];
                    tempWord[i] = tempWord[i + 1];
                    tempWord[i + 1] = swap;

                    if (this.TestWord(tempWord.ToString()))
                    {

                        result = tempWord.ToString();
                        isFoundWord = true;
                        return isFoundWord;
                    }
                }
            }
            return isFoundWord;
        }
        /// <summary>
        ///     split the string into two pieces after every char
        ///		if both pieces are good words make them a suggestion
        /// </summary>
        private bool TwoWords(out String result)
        {
            return TwoWords(this.CurrentWord,out result);
        }
        /// <summary>
        ///     split the string into two pieces after every char
        ///		if both pieces are good words make them a suggestion
        /// </summary>
        private bool TwoWords(String word, out String result)
        {
            result = "";
            bool isFoundWord = false;
            for (int i = 1; i < word.Length - 1; i++)
            {
                string firstWord = word.Substring(0, i);
                string secondWord = word.Substring(i);

                if (this.TestWord(firstWord) && this.TestWord(secondWord))
                {
                    string tempWord = firstWord + " " + secondWord;
                    result = tempWord;
                    isFoundWord = true;
                    return isFoundWord;
                }
            }
            return isFoundWord;
        }
		#endregion

		#region public methods

		/// <summary>
		///     Deletes the CurrentWord from the Text Property
		/// </summary>
		/// <remarks>
		///		Note, calling ReplaceWord with the ReplacementWord property set to 
		///		an empty string has the same behavior as DeleteWord.
		/// </remarks>
		public void DeleteWord()
		{
			if (_words == null || _words.Count == 0)
			{
				TraceWriter.TraceWarning("No Words to Delete");
				return;
			}
			string replacedWord = this.CurrentWord;
			int replacedIndex = this.WordIndex;

			int index = _words[replacedIndex].Index;
			int length = _words[replacedIndex].Length;
			
			// adjust length to remove extra white space after first word
			if (index == 0 
				&& index + length < _text.Length 
				&& _text[index+length] == ' ')
			{
				length++; //removing trailing space
			}
			// adjust length to remove double white space
			else if (index > 0 
				&& index + length < _text.Length 
				&& _text[index-1] == ' ' 
				&& _text[index+length] == ' ')
			{					
				length++; //removing trailing space
			}
			// adjust index to remove extra white space before punctuation
			else if (index > 0 
				&& index + length < _text.Length 
				&& _text[index-1] == ' ' 
				&& char.IsPunctuation(_text[index+length]))
			{					
				index--;
				length++;
			}
			// adjust index to remove extra white space before last word
			else if (index > 0 
				&& index + length == _text.Length
				&& _text[index-1] == ' ')	
			{				
				index--;
				length++;
			}

			string deletedWord = _text.ToString(index, length);
			_text.Remove(index, length);
			
			this.CalculateWords();
			this.OnDeletedWord(new SpellingEventArgs(deletedWord, replacedIndex, index));
		}

		/// <summary>
		///     Calculates the minimum number of change, inserts or deletes
		///     required to change firstWord into secondWord
		/// </summary>
		/// <param name="source" type="string">
		///     <para>
		///         The first word to calculate
		///     </para>
		/// </param>
		/// <param name="target" type="string">
		///     <para>
		///         The second word to calculate
		///     </para>
		/// </param>
		/// <param name="positionPriority" type="bool">
		///     <para>
		///         set to true if the first and last char should have priority
		///     </para>
		/// </param>
		/// <returns>
		///     The number of edits to make firstWord equal secondWord
		/// </returns>
		public int EditDistance(string source, string target, bool positionPriority)
		{
		
			// i.e. 2-D array
			Array matrix = Array.CreateInstance(typeof(int), source.Length+1, target.Length+1);

			// boundary conditions
			matrix.SetValue(0, 0, 0); 

			for(int j=1; j <= target.Length; j++)
			{
				// boundary conditions
				int val = (int)matrix.GetValue(0,j-1);
				matrix.SetValue(val+1, 0, j);
			}

			// outer loop
			for(int i=1; i <= source.Length; i++)                            
			{ 
				// boundary conditions
				int val = (int)matrix.GetValue(i-1, 0);
				matrix.SetValue(val+1, i, 0); 

				// inner loop
				for(int j=1; j <= target.Length; j++)                         
				{ 
					int diag = (int)matrix.GetValue(i-1, j-1);

					if(source.Substring(i-1, 1) != target.Substring(j-1, 1)) 
					{
						diag++;
					}

					int deletion = (int)matrix.GetValue(i-1, j);
					int insertion = (int)matrix.GetValue(i, j-1);
					int match = Math.Min(deletion+1, insertion+1);		
					matrix.SetValue(Math.Min(diag, match), i, j);
				}//for j
			}//for i

			int dist = (int)matrix.GetValue(source.Length, target.Length);

			// extra edit on first and last chars
			if (positionPriority)
			{
				if (source[0] != target[0]) dist++;
				if (source[source.Length-1] != target[target.Length-1]) dist++;
			}
			return dist;
		}
		
		/// <summary>
		///     Calculates the minimum number of change, inserts or deletes
		///     required to change firstWord into secondWord
		/// </summary>
		/// <param name="source" type="string">
		///     <para>
		///         The first word to calculate
		///     </para>
		/// </param>
		/// <param name="target" type="string">
		///     <para>
		///         The second word to calculate
		///     </para>
		/// </param>
		/// <returns>
		///     The number of edits to make firstWord equal secondWord
		/// </returns>
		/// <remarks>
		///		This method automatically gives priority to matching the first and last char
		/// </remarks>
		public int EditDistance(string source, string target)
		{
			return this.EditDistance(source, target, true);
		}

		/// <summary>
		///		Gets the word index from the text index.  Use this method to 
		///		find a word based on the text position.
		/// </summary>
		/// <param name="textIndex">
		///		<para>
		///         The index to search for
		///     </para>
		/// </param>
		/// <returns>
		///		The word index that the text index falls on
		/// </returns>
		public int GetWordIndexFromTextIndex(int textIndex)
		{
			if (_words == null || _words.Count == 0 || textIndex < 1)
			{
				TraceWriter.TraceWarning("No words to get text index from.");
				return 0;
			}

			if(_words.Count == 1)
				return 0;

			int low=0; 
			int high=_words.Count-1; 

			// binary search
			while(low<=high) 
			{ 
				int mid=(low+high)/2; 
				int wordStartIndex = _words[mid].Index;
				int wordEndIndex = _words[mid].Index + _words[mid].Length - 1;
			
				// add white space to end of word by finding the start of the next word
				if ((mid+1) < _words.Count)
					wordEndIndex = _words[mid+1].Index - 1;

				if(textIndex < wordStartIndex) 
					high=mid-1; 
				else if(textIndex > wordEndIndex) 
					low=mid+1; 
				else if(wordStartIndex <= textIndex && textIndex <= wordEndIndex) 
					return mid; 
			} 

			// return last word if not found
			return _words.Count-1;
		}

		/// <summary>
		///     Ignores all instances of the CurrentWord in the Text Property
		/// </summary>
		public void IgnoreAllWord()
		{
			if (this.CurrentWord.Length == 0)
			{
				TraceWriter.TraceWarning("No current word");
				return;
			}

			// Add current word to ignore list
			_ignoreList.Add(this.CurrentWord);
			this.IgnoreWord();
		}

		/// <summary>
		///     Ignores the instances of the CurrentWord in the Text Property
		/// </summary>
		/// <remarks>
		///		Must call SpellCheck after call this method to resume
		///		spell checking
		/// </remarks>
		public void IgnoreWord()
		{	
			
			if (_words == null || _words.Count == 0 || this.CurrentWord.Length == 0)
			{
				TraceWriter.TraceWarning("No text or current word");
				return;
			}

			this.OnIgnoredWord(new SpellingEventArgs(
				this.CurrentWord, 
				this.WordIndex, 
				_words[this.WordIndex].Index));

			// increment Word Index to skip over this word
			_wordIndex++;
		}

		/// <summary>
		///     Replaces all instances of the CurrentWord in the Text Property
		/// </summary>
		public void ReplaceAllWord()
		{
			if (this.CurrentWord.Length == 0)
			{
				TraceWriter.TraceWarning("No current word");
				return;
			}

			// if not in list and replacement word has length
			if(!_replaceList.ContainsKey(this.CurrentWord) && _replacementWord.Length > 0) 
			{
				_replaceList.Add(this.CurrentWord, _replacementWord);
			}
			
			this.ReplaceWord();
		}

		/// <summary>
		///     Replaces all instances of the CurrentWord in the Text Property
		/// </summary>
		/// <param name="replacementWord" type="string">
		///     <para>
		///         The word to replace the CurrentWord with
		///     </para>
		/// </param>
		public void ReplaceAllWord(string replacementWord)
		{
			this.ReplacementWord = replacementWord;
			this.ReplaceAllWord();
		}


		/// <summary>
		///     Replaces the instances of the CurrentWord in the Text Property
		/// </summary>
		public void ReplaceWord()
		{
			if (_words == null || _words.Count == 0 || this.CurrentWord.Length == 0)
			{
				TraceWriter.TraceWarning("No text or current word");
				return;
			}

			if (_replacementWord.Length == 0) 
			{
				this.DeleteWord();
				return;
			}
			string replacedWord = this.CurrentWord;
			int replacedIndex = this.WordIndex;

			int index = _words[replacedIndex].Index;
			int length = _words[replacedIndex].Length;
            
			_text.Remove(index, length);
			// if first letter upper case, match case for replacement word
			if (char.IsUpper(_words[replacedIndex].ToString(), 0))
			{
				_replacementWord = _replacementWord.Substring(0,1).ToUpper(CultureInfo.CurrentUICulture) 
					+ _replacementWord.Substring(1);
			}
			_text.Insert(index, _replacementWord);
			
			this.CalculateWords();

			this.OnReplacedWord(new ReplaceWordEventArgs(
				_replacementWord, 
				replacedWord, 
				replacedIndex, 
				index));
		}

		/// <summary>
		///     Replaces the instances of the CurrentWord in the Text Property
		/// </summary>
		/// <param name="replacementWord" type="string">
		///     <para>
		///         The word to replace the CurrentWord with
		///     </para>
		/// </param>
		public void ReplaceWord(string replacementWord)
		{
			this.ReplacementWord = replacementWord;
			this.ReplaceWord();
		}

		/// <summary>
		///     Spell checks the words in the <see cref="Text"/> property starting
		///     at the <see cref="WordIndex"/> position.
		/// </summary>
		/// <returns>
		///     Returns true if there is a word found in the text 
		///     that is not in the dictionaries
		/// </returns>
		/// <seealso cref="CurrentWord"/>
		/// <seealso cref="WordIndex"/>
		public bool SpellCheck()
		{
			return SpellCheck(_wordIndex, this.WordCount-1);
		}

		/// <summary>
		///     Spell checks the words in the <see cref="Text"/> property starting
		///     at the <see cref="WordIndex"/> position. This overload takes in the
		///     WordIndex to start checking from.
		/// </summary>
		/// <param name="startWordIndex" type="int">
		///     <para>
		///         The index of the word to start checking from. 
		///     </para>
		/// </param>
		/// <returns>
		///     Returns true if there is a word found in the text 
		///     that is not in the dictionaries
		/// </returns>
		/// <seealso cref="CurrentWord"/>
		/// <seealso cref="WordIndex"/>
		public bool SpellCheck(int startWordIndex)
		{
			_wordIndex = startWordIndex;
			return SpellCheck();
		}

		/// <summary>
		///     Spell checks a range of words in the <see cref="Text"/> property starting
		///     at the <see cref="WordIndex"/> position and ending at endWordIndex. 
		/// </summary>
		/// <param name="startWordIndex" type="int">
		///     <para>
		///         The index of the word to start checking from. 
		///     </para>
		/// </param>
		/// <param name="endWordIndex" type="int">
		///     <para>
		///         The index of the word to end checking with. 
		///     </para>
		/// </param>
		/// <returns>
		///     Returns true if there is a word found in the text 
		///     that is not in the dictionaries
		/// </returns>
		/// <seealso cref="CurrentWord"/>
		/// <seealso cref="WordIndex"/>
		public bool SpellCheck(int startWordIndex, int endWordIndex)
		{
			if(startWordIndex > endWordIndex || _words == null || _words.Count == 0) 
			{
				// make sure end index is not greater then word count
				this.OnEndOfText(System.EventArgs.Empty);	//raise event
				return false;
			}

			this.Initialize();

			string currentWord = "";
			bool misspelledWord = false;

			for (int i = startWordIndex; i <= endWordIndex; i++) 
			{
				_wordIndex = i;		// saving the current word index 
				currentWord = this.CurrentWord;

				if(CheckString(currentWord)) 
				{
					if(!TestWord(currentWord)) 
					{
						if(_replaceList.ContainsKey(currentWord)) 
						{
							this.ReplacementWord = _replaceList[currentWord].ToString();
							this.ReplaceWord();
						}
						else if(!_ignoreList.Contains(currentWord))
						{
							misspelledWord = true;
							this.OnMisspelledWord(new SpellingEventArgs(currentWord, i, _words[i].Index));		//raise event
							break;
						}
					}
					else if(i > 0 && _words[i-1].Value.ToString() == currentWord 
						&& (_words[i-1].Index + _words[i-1].Length + 1) == _words[i].Index)
					{
						misspelledWord = true;
						this.OnDoubledWord(new SpellingEventArgs(currentWord, i, _words[i].Index));		//raise event
						break;
					}
				}
			} // for

			if(_wordIndex >= _words.Count-1 && !misspelledWord) 
			{
				this.OnEndOfText(System.EventArgs.Empty);	//raise event
			}
		
			return misspelledWord;

		} // SpellCheck
		
		/// <summary>
		///     Spell checks the words in the <see cref="Text"/> property starting
		///     at the <see cref="WordIndex"/> position. This overload takes in the 
		///     text to spell check
		/// </summary>
		/// <param name="text" type="string">
		///     <para>
		///         The text to spell check
		///     </para>
		/// </param>
		/// <returns>
		///     Returns true if there is a word found in the text 
		///     that is not in the dictionaries
		/// </returns>
		/// <seealso cref="CurrentWord"/>
		/// <seealso cref="WordIndex"/>
		public bool SpellCheck(string text)
		{
			this.Text = text;
			return SpellCheck();
		}

		/// <summary>
		///     Spell checks the words in the <see cref="Text"/> property starting
		///     at the <see cref="WordIndex"/> position. This overload takes in 
		///     the text to check and the WordIndex to start checking from.
		/// </summary>
		/// <param name="text" type="string">
		///     <para>
		///         The text to spell check
		///     </para>
		/// </param>
		/// <param name="startWordIndex" type="int">
		///     <para>
		///         The index of the word to start checking from
		///     </para>
		/// </param>
		/// <returns>
		///     Returns true if there is a word found in the text 
		///     that is not in the dictionaries
		/// </returns>
		/// <seealso cref="CurrentWord"/>
		/// <seealso cref="WordIndex"/>
		public bool SpellCheck(string text, int startWordIndex)
		{
			this.WordIndex = startWordIndex;
			this.Text = text;
			return SpellCheck();
		}

		/// <summary>
		///     Populates the <see cref="Suggestions"/> property with word suggestions
		///     for the word
		/// </summary>
		/// <param name="word" type="string">
		///     <para>
		///         The word to generate suggestions on
		///     </para>
		/// </param>
		/// <remarks>
		///		This method sets the <see cref="Text"/> property to the word. 
		///		Then calls <see cref="TestWord"/> on the word to generate the need
		///		information for suggestions. Note that the Text, CurrentWord and WordIndex 
		///		properties are set when calling this method.
		/// </remarks>
		/// <seealso cref="CurrentWord"/>
		/// <seealso cref="Suggestions"/>
		/// <seealso cref="TestWord"/>
		public void Suggest(string word)
		{
			this.Text = word;
			if(!this.TestWord(word))
				this.Suggest();
		}
		/// <summary>
		///     Populates the <see cref="Suggestions"/> property with word suggestions
		///     for the <see cref="CurrentWord"/>
		/// </summary>
		/// <remarks>
		///		<see cref="TestWord"/> must have been called before calling this method
		/// </remarks>
		/// <seealso cref="CurrentWord"/>
		/// <seealso cref="Suggestions"/>
		/// <seealso cref="TestWord"/>
		public void Suggest()
		{
			
			
			// can't generate suggestions with out current word
			if (this.CurrentWord.Length == 0)
			{
				TraceWriter.TraceWarning("No current word");
				return;
			}

			this.Initialize();

			ArrayList tempSuggestion = new ArrayList();
            foreach(WordDictionary _dictionary in _dictionaries)
            {
			    if ((_suggestionMode == SuggestionEnum.PhoneticNearMiss 
				    || _suggestionMode == SuggestionEnum.Phonetic)
				    && _dictionary.PhoneticRules.Count > 0)
			    {
				    // generate phonetic code for possible root word
				    Hashtable codes = new Hashtable();
				    foreach (string tempWord in _dictionary.PossibleBaseWords)
				    {
					    string tempCode = _dictionary.PhoneticCode(tempWord);
					    if (tempCode.Length > 0 && !codes.ContainsKey(tempCode)) 
					    {
						    codes.Add(tempCode, tempCode);
					    }
				    }
				
				    if (codes.Count > 0)
				    {
					    // search root words for phonetic codes
					    foreach (Word word in _dictionary.BaseWords.Values)
					    {
						    if (codes.ContainsKey(word.PhoneticCode))
						    {
							    ArrayList words = _dictionary.ExpandWord(word);
							    // add expanded words
							    foreach (string expandedWord in words)
							    {
								    Word newWord = new Word();
								    newWord.Text = expandedWord;
								    newWord.EditDistance = this.EditDistance(this.CurrentWord, expandedWord);
								    tempSuggestion.Add(newWord);
							    }
						    }
					    }
				    }
				    TraceWriter.TraceVerbose("Suggestiongs Found with Phonetic Stratagy: {0}" , tempSuggestion.Count);
			    }
            }
			if (_suggestionMode == SuggestionEnum.PhoneticNearMiss 
				|| _suggestionMode == SuggestionEnum.NearMiss)
			{
				// suggestions for a typical fault of spelling, that
				// differs with more, than 1 letter from the right form.
				this.ReplaceChars(ref tempSuggestion);

				// swap out each char one by one and try all the tryme
				// chars in its place to see if that makes a good word
				this.BadChar(ref tempSuggestion);

				// try omitting one char of word at a time
				this.ExtraChar(ref tempSuggestion);

				// try inserting a tryme character before every letter
				this.ForgotChar(ref tempSuggestion);

				// split the string into two pieces after every char
				// if both pieces are good words make them a suggestion
				this.TwoWords(ref tempSuggestion);

				// try swapping adjacent chars one by one
				this.SwapChar(ref tempSuggestion);
			}

			TraceWriter.TraceVerbose("Total Suggestiongs Found: {0}" , tempSuggestion.Count);

			tempSuggestion.Sort();  // sorts by edit score
			_suggestions.Clear(); 

			for (int i = 0; i < tempSuggestion.Count; i++)
			{
				string word = ((Word)tempSuggestion[i]).Text;
				// looking for duplicates
				if (!_suggestions.Contains(word))
				{
					// populating the suggestion list
					_suggestions.Add(word);
				}

				if (_suggestions.Count >= _maxSuggestions && _maxSuggestions > 0)
				{
					break;
				}
			}

		} // suggest

		/// <summary>
		///     Checks to see if the word is in the dictionary
		/// </summary>
		/// <param name="word" type="string">
		///     <para>
		///         The word to check
		///     </para>
		/// </param>
		/// <returns>
		///     Returns true if word is found in dictionary
		/// </returns>
		public bool TestWord(string word)
		{
            if (!_isDictionariesInitialized)
            {
                this.Initialize();
            }

			TraceWriter.TraceVerbose("Testing Word: {0}" , word);
            foreach (WordDictionary dictionary in Dictionaries)
            {
                if (dictionary.Contains(word))
                {
                    return true;
                }
                else if (dictionary.Contains(word.ToLower()))
                {
                    return true;
                }
            }
			return false;
		}
        public bool IsSimilarSpellCheck(string word)
        {
            bool bResult = false;
            if (_isDictionariesInitialized == false)
            {
                this.Initialize();
            }
    
            String result;
            // check similar word
            if (this.ReplaceChars(word,out result))
            {
                bResult = true;
            }
            else if (this.BadChar(word, out result))
            {
                bResult = true;
            }
            else if (this.ForgotChar(word, out result))
            {
                bResult = true;
            }
            else if (this.SwapChar(word, out result))
            {
                bResult = true;
            }
            else if (this.ExtraChar(word, out result))
            {
                bResult = true;
            }
            else if (this.TwoWords(word, out result))
            {
                bResult = true;
            }
                
            
            return bResult;

        }
		#endregion

		#region public properties

		private bool _alertComplete = true;
        private DictionaryCollection _dictionaries;
		private bool _ignoreAllCapsWords = true;
		private bool _ignoreHtml = true;
		private ArrayList _ignoreList = new ArrayList();
		private bool _ignoreWordsWithDigits = false;
		private int _maxSuggestions = 25;
		private Hashtable _replaceList = new Hashtable();
		private string _replacementWord = "";
		private SuggestionEnum _suggestionMode = SuggestionEnum.PhoneticNearMiss;
		private ArrayList _suggestions = new ArrayList();
		private StringBuilder _text = new StringBuilder();
		private int _wordIndex = 0;
        private bool _isDictionariesInitialized=false;

		/// <summary>
		///     The suggestion strategy to use when generating suggestions
		/// </summary>
		public enum SuggestionEnum
		{
			/// <summary>
			///     Combines the phonetic and near miss strategies
			/// </summary>
			PhoneticNearMiss,
			/// <summary>
			///     The phonetic strategy generates suggestions by word sound
			/// </summary>
			/// <remarks>
			///		This technique was developed by the open source project ASpell.net
			/// </remarks>
			Phonetic,
			/// <summary>
			///     The near miss strategy generates suggestion by replacing, 
			///     removing, adding chars to make words
			/// </summary>
			/// <remarks>
			///     This technique was developed by the open source spell checker ISpell
			/// </remarks>
			NearMiss
		}


		/// <summary>
		///     Display the 'Spell Check Complete' alert.
		/// </summary>
		[Browsable(true)]
		[DefaultValue(true)]
		[CategoryAttribute("Options")]
		[Description("Display the 'Spell Check Complete' alert.")]
		public bool AlertComplete
		{
			get { return _alertComplete; }
			set { _alertComplete = value; }
		}

		/// <summary>
		///     The current word being spell checked from the text property
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string CurrentWord
		{
			get	
			{
				if(_words == null || _words.Count == 0)
					return string.Empty;
				else
					return _words[this.WordIndex].Value;
			}
		}

		/// <summary>
		///     The WordDictionary object to use when spell checking
		/// </summary>
		[Browsable(true)]
        [CategoryAttribute("Dictionary")]
		[Description("The WordDictionary object to use when spell checking")]
        public DictionaryCollection Dictionaries
		{
			get 
			{
				if(!base.DesignMode && _dictionaries == null)
                    _dictionaries = new DictionaryCollection();

				return _dictionaries;
			}
			set 
			{
                if (value != null)
                {
                    _dictionaries = value;
                }
                else
                { 
                    _dictionaries=new DictionaryCollection();
                }
			}
		}


		/// <summary>
		///     Ignore words with all capital letters when spell checking
		/// </summary>
		[DefaultValue(true)]
		[CategoryAttribute("Options")]
		[Description("Ignore words with all capital letters when spell checking")]
		public bool IgnoreAllCapsWords
		{
			get {return _ignoreAllCapsWords;}
			set {_ignoreAllCapsWords = value;}
		}

		/// <summary>
		///     Ignore html tags when spell checking
		/// </summary>
		[DefaultValue(true)]
		[CategoryAttribute("Options")]
		[Description("Ignore html tags when spell checking")]
		public bool IgnoreHtml
		{
			get {return _ignoreHtml;}
			set {_ignoreHtml = value;}
		}

		/// <summary>
		///     List of words to automatically ignore
		/// </summary>
		/// <remarks>
		///		When <see cref="IgnoreAllWord"/> is clicked, the <see cref="CurrentWord"/> is added to this list
		/// </remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ArrayList IgnoreList
		{
			get {return _ignoreList;}
		}

		/// <summary>
		///     Ignore words with digits when spell checking
		/// </summary>
		[DefaultValue(false)]
		[CategoryAttribute("Options")]
		[Description("Ignore words with digits when spell checking")]
		public bool IgnoreWordsWithDigits
		{
			get {return _ignoreWordsWithDigits;}
			set {_ignoreWordsWithDigits = value;}
		}

		/// <summary>
		///     The maximum number of suggestions to generate
		/// </summary>
		[DefaultValue(25)]
		[CategoryAttribute("Options")]
		[Description("The maximum number of suggestions to generate")]
		public int MaxSuggestions
		{
			get {return _maxSuggestions;}
			set {_maxSuggestions = value;}
		}

		/// <summary>
		///     List of words and replacement values to automatically replace
		/// </summary>
		/// <remarks>
		///		When <see cref="ReplaceAllWord"/> is clicked, the <see cref="CurrentWord"/> is added to this list
		/// </remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Hashtable ReplaceList
		{
			get {return _replaceList;}
		}

		/// <summary>
		///     The word to used when replacing the misspelled word
		/// </summary>
		/// <seealso cref="ReplaceAllWord"/>
		/// <seealso cref="ReplaceWord"/>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string ReplacementWord
		{
			get {return _replacementWord;}
			set {_replacementWord = value.Trim();}
		}
		/// <summary>
		///     The suggestion strategy to use when generating suggestions
		/// </summary>
		[DefaultValue(SuggestionEnum.PhoneticNearMiss)]
		[CategoryAttribute("Options")]
		[Description("The suggestion strategy to use when generating suggestions")]
		public SuggestionEnum SuggestionMode
		{
			get {return _suggestionMode;}
			set {_suggestionMode = value;}
		}

		/// <summary>
		///     An array of word suggestions for the correct spelling of the misspelled word
		/// </summary>
		/// <seealso cref="Suggest"/>
		/// <seealso cref="SpellCheck"/>
		/// <seealso cref="MaxSuggestions"/>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ArrayList Suggestions
		{
			get {return _suggestions;}
		}

		/// <summary>
		///     The text to spell check
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string Text
		{
			get {return _text.ToString();}
			set 
			{
				_text = new StringBuilder(value);
				this.CalculateWords();
				this.Reset();
			}
		}

		/// <summary>
		///     TextIndex is the index of the current text being spell checked
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public int TextIndex
		{
			get 
			{
				if (_words == null || _words.Count == 0)
					return 0;
	
				return _words[this.WordIndex].Index;			
			}
		}

		/// <summary>
		///     The number of words being spell checked
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public int WordCount
		{
			get 
			{
				if(_words == null)
					return 0;

				return _words.Count;
			}
		}

		/// <summary>
		///     WordIndex is the index of the current word being spell checked
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public int WordIndex
		{
			get 
			{
				if(_words == null)
					return 0;
				
				// make sure word index can't be higher then word count
				return Math.Max(0, Math.Min(_wordIndex, (this.WordCount-1)));	
			}
			set 
			{	
				_wordIndex = value;	
			}
		}

		#endregion

		#region Component Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			components = new System.ComponentModel.Container();
           _dictionaries = new DictionaryCollection();
		}

		#endregion

	} 
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Vietnam Maritime University
Vietnam Vietnam
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions