在 UWP 应用中可以使用拼写库(例如 hunspell)吗?

5
我正在将一款面向作家的应用程序移植到UWP平台。唯一剩下的问题是NHunspell库。我广泛使用它进行拼写检查和同义词功能,并对其进行了大量定制,为各种事情创建了自定义字典(即每个写作项目都有一个不同的字典)。这个库非常好用。
然而,我似乎无法在我的UWP应用程序中包含这个DLL。
1)是否有办法强制使用此DLL?我真的很喜欢NHunSpell系统的设置方式。它很合理,而且非常快捷易用。
2)如果不能,有没有人可以推荐更好的解决方案,以实现自定义字典、定制拼写检查等功能?
更新3
经过大量的更新和在线阅读,我找到了一篇讨论拼写检查理论的链接。这里是一个快速示例(我使用最多的一个)。

http://www.anotherchris.net/csharp/how-to-write-a-spelling-corrector-in-csharp/

阅读了本文之后,我使用Hunspell .dic文件的基础代码,并剔除其中的英文单词,创建了自己的拼写检查库,该库在UWP中运行良好。一旦我稳定下来,我会将其发布在下面作为答案,以供SO社区使用。 :)
更新2
我放弃使用Hunspell,看起来根本不可能……有其他的库/软件包可以建议吗?
更新:
我可能需要重新表述一下我无法包含DLL的声明:我无法通过NuGet包含DLL。它抱怨该DLL与UAP/UWP平台不兼容。
我能够手动将DLL包含到我的项目中,通过链接到现有的DLL(而不是NuGet)。但是,该DLL确实证明与UAP平台不兼容。在WinForms中简单调用拼写检查一个单词可以正常工作,但立即崩溃并显示“System.IO.FileNotFoundException”。
NHunspell的构造函数确实需要加载相关的.dic和.aff文件。但是,我通过将文件加载到内存中,然后调用替代构造函数来缓解这个问题,该构造函数使用字节数组而不是文件名作为这些文件的参数。它仍然会崩溃,但是会出现一个新的“方法未找到”错误:String System.AppDomain.get_RelativeSearchPath()
我正在寻找任何可以在UAP框架内使用的拼写检查引擎。出于熟悉原因,我更喜欢NHunspell。然而,我也意识到这越来越不可能成为一个选项。
我与之合作的人建议我使用内置的拼写检查选项。然而,我不能使用内置的Windows 10/TextBox拼写检查功能(据我所知),因为我无法控制自定义词典,也无法禁用自动大写和单词替换(如果它认为它已经足够接近正确的猜测,它就会替换掉你的单词)。对于作家来说,这些东西是灾难性的!作家可以在操作系统级别关闭它们,但他们可能希望在其他应用程序中打开它们,只是不想在这个应用程序中打开。
请告诉我是否有NHunspell的解决方法。如果您不知道解决方法,请让我知道在UAP框架内最好的替代自定义拼写检查引擎。
另外,我还使用NHunspell进行同义词功能。它在我的Windows应用程序中运行得非常好。我也需要替换这个功能 - 希望能够使用与拼写检查引擎相同的引擎。但是,如果您知道一个很好的同义词引擎(但不进行拼写检查),那也很好!
谢谢!

你能提供更多细节吗?你是从源代码构建吗? - Peter Torr - MSFT
我没有从源代码构建NHunspell。在之前的版本(非UWP),我使用了NuGet软件包中的NHunspell。但是在我的UWP应用程序中,NuGet告诉我NHunspell不兼容。 - Jerry
NuGet是正确的,你需要使用UWP SDK从源代码构建库。 - yms
3个回答

3
我下载了NHunspell库的源代码,并尝试构建支持UWP的库,但是我在Marshalling(Marshalling.cs)中遇到了问题。
该软件包加载仅适用于x86和x64架构的dll文件,因此在arm(移动设备、平板电脑)上运行应用程序将无法正常工作。
该软件包加载具有系统调用的dll文件:
    [DllImport("kernel32.dll")]
    internal static extern IntPtr LoadLibrary(string fileName);

我认为需要重写代码以在UWP中运行,因为UWP使用沙盒技术。

我的看法有两种选择:
1)按照UWP的限制重写Marshalling类。
2)不在程序中使用Hunspell。

我对UWP中的dll并不了解,但我认为重写可能会非常困难。


非常感谢您的尝试!感谢您的解释。解释得非常好。除了nhunspell,您知道其他用于拼写和同义词库的软件包吗?我在网上搜索了一下,但没有找到有用的信息。(很多参考资料都提到了其他软件包,比如apsell,但这些软件包在UWP中都无法使用) - Jerry
抱歉,我没有找到其他可以替代nhunspell的库。不过我认为你可以使用nhunspell开发一个网站,然后创建一个托管在Web上的UWP应用程序。也许这可以解决问题。 - ganchito55
没问题。看到我的最新更新了吗?不过,非常感谢你的建议。 - Jerry
谢谢,我希望你的库能够很好地运行,并且能够帮助其他遇到同样问题的人。 - ganchito55

1

如承诺的那样,这是我编写的用于拼写检查的类。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Com.HanelDev.HSpell
{
    public class HSpellProcess
    {
        private Dictionary<string, string> _dictionary = new Dictionary<string, string>();

        public int MaxSuggestionResponses { get; set; }

        public HSpellProcess()
        {
            MaxSuggestionResponses = 10;
        }

        public void AddToDictionary(string w)
        {
            if (!_dictionary.ContainsKey(w.ToLower()))
            {
                _dictionary.Add(w.ToLower(), w);
            }
            else
            {
                // Upper case words are more specific (but may be the first word
                // in a sentence.) Lower case words are more generic.
                // If you put an upper-case word in the dictionary, then for
                // it to be "correct" it must match case. This is not true
                // for lower-case words.
                // We want to only replace existing words with their more
                // generic versions, not the other way around.
                if (_dictionary[w.ToLower()].CaseSensitive())
                {
                    _dictionary[w.ToLower()] = w;
                }
            }
        }

        public void LoadDictionary(byte[] dictionaryFile, bool resetDictionary = false)
        {
            if (resetDictionary)
            {
                _dictionary = new Dictionary<string, string>();
            }
            using (MemoryStream ms = new MemoryStream(dictionaryFile))
            {
                using (StreamReader sr = new StreamReader(ms))
                {
                    string tmp = sr.ReadToEnd();
                    tmp = tmp.Replace("\r\n", "\r").Replace("\n", "\r");
                    string [] fileData = tmp.Split("\r".ToCharArray());

                    foreach (string line in fileData)
                    {
                        if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
                        {
                            continue;
                        }

                        string word = line;

                        // I added all of this for file imports (not array imports)
                        // to be able to handle words from Hunspell dictionaries.
                        // I don't get the hunspell derivatives, but at least I get
                        // the root word.
                        if (line.Contains("/"))
                        {
                            string[] arr = line.Split("/".ToCharArray());
                            word = arr[0];
                        }

                        AddToDictionary(word);
                    }
                }
            }
        }

        public void LoadDictionary(Stream dictionaryFileStream, bool resetDictionary = false)
        {
            string s = "";
            using (StreamReader sr = new StreamReader(dictionaryFileStream))
            {
                s = sr.ReadToEnd();
            }

            byte [] bytes = Encoding.UTF8.GetBytes(s);

            LoadDictionary(bytes, resetDictionary);
        }

        public void LoadDictionary(List<string> words, bool resetDictionary = false)
        {
            if (resetDictionary)
            {
                _dictionary = new Dictionary<string, string>();
            }

            foreach (string line in words)
            {
                if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
                {
                    continue;
                }

                AddToDictionary(line);
            }
        }

        public string ExportDictionary()
        {
            StringBuilder sb = new StringBuilder();

            foreach (string k in _dictionary.Keys)
            {
                sb.AppendLine(_dictionary[k]);
            }

            return sb.ToString();
        }

        public HSpellCorrections Correct(string word)
        {
            HSpellCorrections ret = new HSpellCorrections();
            ret.Word = word;

            if (_dictionary.ContainsKey(word.ToLower()))
            {
                string testWord = word;
                string dictWord = _dictionary[word.ToLower()];
                if (!dictWord.CaseSensitive())
                {
                    testWord = testWord.ToLower();
                    dictWord = dictWord.ToLower();
                }

                if (testWord == dictWord)
                {
                    ret.SpelledCorrectly = true;
                    return ret;
                }
            }

            // At this point, we know the word is assumed to be spelled incorrectly. 
            // Go get word candidates.
            ret.SpelledCorrectly = false;

            Dictionary<string, HSpellWord> candidates = new Dictionary<string, HSpellWord>();

            List<string> edits = Edits(word);

            GetCandidates(candidates, edits);

            if (candidates.Count > 0)
            {
                return BuildCandidates(ret, candidates);
            }

            // If we didn't find any candidates by the main word, look for second-level candidates based on the original edits.
            foreach (string item in edits)
            {
                List<string> round2Edits = Edits(item);

                GetCandidates(candidates, round2Edits);
            }

            if (candidates.Count > 0)
            {
                return BuildCandidates(ret, candidates);
            }

            return ret;
        }

        private void GetCandidates(Dictionary<string, HSpellWord> candidates, List<string> edits)
        {
            foreach (string wordVariation in edits)
            {
                if (_dictionary.ContainsKey(wordVariation.ToLower()) &&
                    !candidates.ContainsKey(wordVariation.ToLower()))
                {
                    HSpellWord suggestion = new HSpellWord(_dictionary[wordVariation.ToLower()]);

                    suggestion.RelativeMatch = RelativeMatch.Compute(wordVariation, suggestion.Word);

                    candidates.Add(wordVariation.ToLower(), suggestion);
                }
            }
        }

        private HSpellCorrections BuildCandidates(HSpellCorrections ret, Dictionary<string, HSpellWord> candidates)
        {
            var suggestions = candidates.OrderByDescending(c => c.Value.RelativeMatch);

            int x = 0;

            ret.Suggestions.Clear();
            foreach (var suggest in suggestions)
            {
                x++;
                ret.Suggestions.Add(suggest.Value.Word);

                // only suggest the first X words.
                if (x >= MaxSuggestionResponses)
                {
                    break;
                }
            }

            return ret;
        }

        private List<string> Edits(string word)
        {
            var splits = new List<Tuple<string, string>>();
            var transposes = new List<string>();
            var deletes = new List<string>();
            var replaces = new List<string>();
            var inserts = new List<string>();

            // Splits
            for (int i = 0; i < word.Length; i++)
            {
                var tuple = new Tuple<string, string>(word.Substring(0, i), word.Substring(i));
                splits.Add(tuple);
            }

            // Deletes
            for (int i = 0; i < splits.Count; i++)
            {
                string a = splits[i].Item1;
                string b = splits[i].Item2;
                if (!string.IsNullOrEmpty(b))
                {
                    deletes.Add(a + b.Substring(1));
                }
            }

            // Transposes
            for (int i = 0; i < splits.Count; i++)
            {
                string a = splits[i].Item1;
                string b = splits[i].Item2;
                if (b.Length > 1)
                {
                    transposes.Add(a + b[1] + b[0] + b.Substring(2));
                }
            }

            // Replaces
            for (int i = 0; i < splits.Count; i++)
            {
                string a = splits[i].Item1;
                string b = splits[i].Item2;
                if (!string.IsNullOrEmpty(b))
                {
                    for (char c = 'a'; c <= 'z'; c++)
                    {
                        replaces.Add(a + c + b.Substring(1));
                    }
                }
            }

            // Inserts
            for (int i = 0; i < splits.Count; i++)
            {
                string a = splits[i].Item1;
                string b = splits[i].Item2;
                for (char c = 'a'; c <= 'z'; c++)
                {
                    inserts.Add(a + c + b);
                }
            }

            return deletes.Union(transposes).Union(replaces).Union(inserts).ToList();
        }

        public HSpellCorrections CorrectFrom(string txt, int idx)
        {
            if (idx >= txt.Length)
            {
                return null;
            }

            // Find the next incorrect word.
            string substr = txt.Substring(idx);
            int idx2 = idx;

            List<string> str = substr.Split(StringExtensions.WordDelimiters).ToList();

            foreach (string word in str)
            {
                string tmpWord = word;

                if (string.IsNullOrEmpty(word))
                {
                    idx2++;
                    continue;
                }

                // If we have possessive version of things, strip the 's off before testing
                // the word. THis will solve issues like "My [mother's] favorite ring."
                if (tmpWord.EndsWith("'s"))
                {
                    tmpWord = word.Substring(0, tmpWord.Length - 2);
                }

                // Skip things like ***, #HashTagsThatMakeNoSense and 1,2345.67
                if (!tmpWord.IsWord())
                {
                    idx2 += word.Length + 1;
                    continue;
                }

                HSpellCorrections cor = Correct(tmpWord);

                if (cor.SpelledCorrectly)
                {
                    idx2 += word.Length + 1;
                }
                else
                {
                    cor.Index = idx2;
                    return cor;
                }
            }

            return null;
        }
    }
}

0

您可以直接使用Windows内置的拼写检查器,这样您就可以更好地控制它的行为。然后自己将结果应用于文本框控件。

看一下ISpellChecker。它允许您添加自己的自定义字典,并具有更多选项来控制其行为。是的,它适用于UWP。


有趣的想法。我以前见过这个,但我有两个主要的担忧。1)微软在他们的文档中特别标注了该接口为“不要使用”。虽然不是什么大问题,但让我感到担心;2)更重要的是,即使我构建了这个,我也没有找到明确的方法来告诉我的应用程序使用我的拼写检查类而不是内置的拼写检查类。 - Jerry
文档上说“不要实现”,而不是“不要使用”。这是一个非常大的区别! - Stefan
不确定你为什么需要自己的“class”来进行拼写检查 - 使用此方法,你就不需要它了。 https://msdn.microsoft.com/zh-cn/library/windows/desktop/hh869748(v=vs.85).aspx 这里还有一个例子: https://code.msdn.microsoft.com/windowsdesktop/spell-checking-client-aea0148c - Stefan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接