Show / Hide Table of Contents

MSDN.WhiteKnight - Stack Overflow answers

Ответ на "Чем в .NET можно проверить орфографию в русском тексте?"

Answer 897845

Link

Windows 8 - 10

Можно использовать стандартный Spell Checking API. Словари для проверки орфографии устанавливаются с языковым пакетом, так что при наличии русскоязычной Windows русский язык будет поддерживаться.

Объявим необходимые интерфейсы:

using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    public class SpellCheckAPI
    {
        public enum WORDLIST_TYPE
        {
            WORDLIST_TYPE_IGNORE,
            WORDLIST_TYPE_ADD,
            WORDLIST_TYPE_EXCLUDE,
            WORDLIST_TYPE_AUTOCORRECT,
        }

        public enum CORRECTIVE_ACTION
        {
            CORRECTIVE_ACTION_NONE,
            CORRECTIVE_ACTION_GET_SUGGESTIONS,
            CORRECTIVE_ACTION_REPLACE,
            CORRECTIVE_ACTION_DELETE,
        }

        [Guid("B7C82D61-FBE8-4B47-9B27-6C0D2E0DE0A3")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [ComImport]
        public interface ISpellingError
        {
            uint StartIndex { get; }

            uint Length { get; }

            SpellCheckAPI.CORRECTIVE_ACTION CorrectiveAction { get; }

            string Replacement { [return: MarshalAs(UnmanagedType.LPWStr)] get; }
        }

        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("803E3BD4-2828-4410-8290-418D1D73C762")]
        [ComImport]
        public interface IEnumSpellingError
        {
            [return: MarshalAs(UnmanagedType.Interface)]
            SpellCheckAPI.ISpellingError Next();
        }

        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("00000101-0000-0000-C000-000000000046")]
        [ComImport]
        public interface IEnumString
        {
            void Next([In] uint celt, [MarshalAs(UnmanagedType.LPWStr)] out string rgelt, out uint pceltFetched);

            void Skip([In] uint celt);

            void Reset();

            void Clone([MarshalAs(UnmanagedType.Interface)] out SpellCheckAPI.IEnumString ppenum);
        }

        [Guid("432E5F85-35CF-4606-A801-6F70277E1D7A")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [ComImport]
        public interface IOptionDescription
        {
            string Id { [return: MarshalAs(UnmanagedType.LPWStr)] get; }

            string Heading { [return: MarshalAs(UnmanagedType.LPWStr)] get; }

            string Description { [return: MarshalAs(UnmanagedType.LPWStr)] get; }

            SpellCheckAPI.IEnumString Labels { [return: MarshalAs(UnmanagedType.Interface)] get; }
        }

        [Guid("0B83A5B0-792F-4EAB-9799-ACF52C5ED08A")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [ComImport]
        public interface ISpellCheckerChangedEventHandler
        {
            void Invoke([MarshalAs(UnmanagedType.Interface), In] SpellCheckAPI.ISpellChecker sender);
        }

        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("B6FD0B71-E2BC-4653-8D05-F197E412770B")]
        [ComImport]
        public interface ISpellChecker
        {
            string languageTag { [return: MarshalAs(UnmanagedType.LPWStr)] get; }

            [return: MarshalAs(UnmanagedType.Interface)]
            SpellCheckAPI.IEnumSpellingError Check([MarshalAs(UnmanagedType.LPWStr), In] string text);

            [return: MarshalAs(UnmanagedType.Interface)]
            SpellCheckAPI.IEnumString Suggest([MarshalAs(UnmanagedType.LPWStr), In] string word);

            void Add([MarshalAs(UnmanagedType.LPWStr), In] string word);

            void Ignore([MarshalAs(UnmanagedType.LPWStr), In] string word);

            void AutoCorrect([MarshalAs(UnmanagedType.LPWStr), In] string from, [MarshalAs(UnmanagedType.LPWStr), In] string to);

            byte GetOptionValue([MarshalAs(UnmanagedType.LPWStr), In] string optionId);

            SpellCheckAPI.IEnumString OptionIds { [return: MarshalAs(UnmanagedType.Interface)] get; }

            string Id { [return: MarshalAs(UnmanagedType.LPWStr)] get; }

            string LocalizedName { [return: MarshalAs(UnmanagedType.LPWStr)] get; }

            uint add_SpellCheckerChanged([MarshalAs(UnmanagedType.Interface), In] SpellCheckAPI.ISpellCheckerChangedEventHandler handler);


            void remove_SpellCheckerChanged([In] uint eventCookie);

            [return: MarshalAs(UnmanagedType.Interface)]
            SpellCheckAPI.IOptionDescription GetOptionDescription([MarshalAs(UnmanagedType.LPWStr), In] string optionId);

            [return: MarshalAs(UnmanagedType.Interface)]
            SpellCheckAPI.IEnumSpellingError ComprehensiveCheck([MarshalAs(UnmanagedType.LPWStr), In] string text);
        }

        [Guid("8E018A9D-2415-4677-BF08-794EA61F94BB")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [ComImport]
        public interface ISpellCheckerFactory
        {
            SpellCheckAPI.IEnumString SupportedLanguages { [return: MarshalAs(UnmanagedType.Interface)] get; }


            int IsSupported([MarshalAs(UnmanagedType.LPWStr), In] string languageTag);


            [return: MarshalAs(UnmanagedType.Interface)]
            SpellCheckAPI.ISpellChecker CreateSpellChecker([MarshalAs(UnmanagedType.LPWStr), In] string languageTag);
        }

        [Guid("AA176B85-0E12-4844-8E1A-EEF1DA77F586")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [ComImport]
        public interface IUserDictionariesRegistrar
        {
            void RegisterUserDictionary([MarshalAs(UnmanagedType.LPWStr), In] string dictionaryPath, [MarshalAs(UnmanagedType.LPWStr), In] string languageTag);

            void UnregisterUserDictionary([MarshalAs(UnmanagedType.LPWStr), In] string dictionaryPath, [MarshalAs(UnmanagedType.LPWStr), In] string languageTag);
        }



        [Guid("7AB36653-1796-484B-BDFA-E74F1DB7C1DC")]
        [ComImport]
        public class SpellCheckerFactoryClass
        {
        }
    }
} 

Тогда метод для проверки правописания можно реализовать так:

public static string SpellCheck(string s)
{
    SpellCheckAPI.SpellCheckerFactoryClass factory = null;
    SpellCheckAPI.ISpellCheckerFactory ifactory = null;
    SpellCheckAPI.ISpellChecker checker = null;
    SpellCheckAPI.ISpellingError error = null;
    SpellCheckAPI.IEnumSpellingError errors = null;
    SpellCheckAPI.IEnumString suggestions = null;
    StringBuilder sb = new StringBuilder(s.Length * 10);

    try
    {

        factory = new SpellCheckAPI.SpellCheckerFactoryClass();
        ifactory = (SpellCheckAPI.ISpellCheckerFactory)factory;

        //проверим поддержку русского языка
        int res = ifactory.IsSupported("ru-RU");
        if (res == 0) { throw new Exception("Fatal error: russian language not supported!"); }

        checker = ifactory.CreateSpellChecker("ru-RU");

        errors = checker.Check(s);
        while (true)
        {
            if (error != null) { Marshal.ReleaseComObject(error); error = null; }

            error = errors.Next();
            if (error == null) break;

            //получаем слово с ошибкой
            string word = s.Substring((int)error.StartIndex, (int)error.Length);
            sb.AppendLine("Ошибка в слове: " + word);

            //получаем рекомендуемое действие
            switch (error.CorrectiveAction)
            {
                case SpellCheckAPI.CORRECTIVE_ACTION.CORRECTIVE_ACTION_DELETE:
                    sb.AppendLine("Рекомендуемое действие: удалить");
                    break;

                case SpellCheckAPI.CORRECTIVE_ACTION.CORRECTIVE_ACTION_REPLACE:
                    sb.AppendLine("Рекомендуемое действие: заменить на " + error.Replacement);
                    break;

                case SpellCheckAPI.CORRECTIVE_ACTION.CORRECTIVE_ACTION_GET_SUGGESTIONS:
                    sb.AppendLine("Рекомендуемое действие: заменить на одно из следующих слов");

                    if (suggestions != null) { Marshal.ReleaseComObject(suggestions); suggestions = null; }

                    //получаем список слов, предложенных для замены
                    suggestions = checker.Suggest(word);

                    sb.Append("[ ");
                    while (true)
                    {
                        string suggestion;
                        uint count = 0;
                        suggestions.Next(1, out suggestion, out count);
                        if (count == 1) sb.Append(suggestion + " ");
                        else break;
                    }
                    sb.Append("] ");
                    sb.AppendLine();
                    break;
            }
            sb.AppendLine();

        }


    }
    finally
    {
        if (suggestions != null) { Marshal.ReleaseComObject(suggestions); }
        if (factory != null) { Marshal.ReleaseComObject(factory); }
        if (ifactory != null) { Marshal.ReleaseComObject(ifactory); }
        if (checker != null) { Marshal.ReleaseComObject(checker); }
        if (error != null) { Marshal.ReleaseComObject(error); }
        if (errors != null) { Marshal.ReleaseComObject(errors); }
    }

    return sb.ToString();

}

Windows Vista - 7

Можно использовать TextBox из WPF, тесты показали, что он отлично работает в не-WPF проекте без необходимости создания цикла обработки сообщений и добавления его в окно. Нужно лишь добавить ссылки на PresentationCore, PresentationFramework, WindowsBase и System.Xaml, а также пометить поток STAThread. Из коробки русский язык не поддерживается, поэтому придется скачать словарь, например здесь, и добавить его как нестандартный. Файл словаря желательно перекодировать в UTF-16 LE с BOM (эта кодировка в блокноте обозначена как "Юникод"), так как словари в других кодировках похоже обрабатываются некорректно.

Пример:

using System.Windows.Controls;

//...

public static string SpellCheckWPF(string s)
{
    StringBuilder sb = new StringBuilder(s.Length * 10);

    TextBox textbox = new TextBox(); 
    textbox.Text = s;        
    textbox.Language = System.Windows.Markup.XmlLanguage.GetLanguage("en-US");
    textbox.SpellCheck.IsEnabled = true;

    //добавим нестандартный словарь из файла
    textbox.SpellCheck.CustomDictionaries.Add(new Uri(@"ru-RU.dic", UriKind.Relative));

    int index = 0;
    while (true)
    {
        //находим ошибку            
        index = textbox.GetNextSpellingErrorCharacterIndex(index, System.Windows.Documents.LogicalDirection.Forward);
        if (index > s.Length || index < 0) break;

        var error = textbox.GetSpellingError(index);
        int len = textbox.GetSpellingErrorLength(index);

        string word = textbox.Text.Substring(index, len);

        sb.AppendFormat("Ошибка в слове {0}, рекомендуется заменить на одно из следующих слов: ", word);

        //выводим список предлагаемых замен
        foreach (string x in error.Suggestions)
        {
            sb.Append(x + "; ");
        }
        sb.AppendLine();

        //переход к следующему слову
        index += len;
    }
    return sb.ToString();
}

Поскольку загрузка большого словаря занимает существенное время, нужно создать один TextBox, загрузить в него словарь один раз при запуске приложения и в последующем коде использовать его (а не грузить словарь каждый раз, когда нужно что-то проверить).

Данный код не будет работать в Windows 8.1+ и .NET 4.6.1+, так как в этих версиях WPF также использует вышеописанный стандартный API. Механизм загрузки словарей в связи с этим сильно изменился, и большие словари не поддерживаются. Чтобы все нормально заработало в этом случае, нужно убрать нестандартный словарь и устанавливать в TextBox русский язык, а не английский.

Источники

Using the C++ Spell Checking API

WPF in .NET 4.6.1

Spelling checker isn't supported in the .NET 4.6.1 in some conditions


Content is retrieved from StackExchange API.

Auto-generated by ruso-archive tools.

Back to top Stack Overflow answers (published from sources in GitHub repository). Copyright (c) 2020, MSDN.WhiteKnight. Content licensed under BSD 3-Clause License.
Generated by DocFX