├── dicts └── .gitignore ├── Images ├── demo.gif ├── plugin.png └── demo │ ├── cn_to_en.png │ ├── en_to_cn.png │ ├── phonetic.png │ ├── synonym.png │ ├── definition.png │ ├── exchanges.png │ ├── translation.png │ └── spelling_correction.png ├── plugin.json ├── generate_dist.bat ├── src ├── Word.cs ├── Settings.cs ├── WordCorrection.cs ├── Synonyms.cs ├── Downloader.cs ├── ECDict.cs ├── DictionarySettings.xaml ├── DictionarySettings.xaml.cs ├── iciba.cs ├── EditDistance.cs ├── Dictionary.cs └── SymSpell.cs ├── packages.config ├── licenses ├── LICENSE_ECDICT.txt └── LICENSE_SymSpell.txt ├── Dictionary.sln ├── Properties └── AssemblyInfo.cs ├── app.config ├── README.md ├── Dictionary.csproj ├── .gitignore └── LICENSE /dicts/.gitignore: -------------------------------------------------------------------------------- 1 | ecdict.db -------------------------------------------------------------------------------- /Images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/demo.gif -------------------------------------------------------------------------------- /Images/plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/plugin.png -------------------------------------------------------------------------------- /Images/demo/cn_to_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/demo/cn_to_en.png -------------------------------------------------------------------------------- /Images/demo/en_to_cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/demo/en_to_cn.png -------------------------------------------------------------------------------- /Images/demo/phonetic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/demo/phonetic.png -------------------------------------------------------------------------------- /Images/demo/synonym.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/demo/synonym.png -------------------------------------------------------------------------------- /Images/demo/definition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/demo/definition.png -------------------------------------------------------------------------------- /Images/demo/exchanges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/demo/exchanges.png -------------------------------------------------------------------------------- /Images/demo/translation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/demo/translation.png -------------------------------------------------------------------------------- /Images/demo/spelling_correction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrynull/WoxDictionary/HEAD/Images/demo/spelling_correction.png -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID":"c3406b5c-22f0-4984-b018-3dae897cab3f", 3 | "ActionKeyword":"d", 4 | "Name":"Dictionary", 5 | "Description":"English dictionary, word correction and synonym.", 6 | "Author":"Harry Yu", 7 | "Version":"1.0", 8 | "Language":"csharp", 9 | "Website":"https://github.com/harrynull/WoxDictionary", 10 | "IcoPath":"Images\\plugin.png", 11 | "ExecuteFileName":"Dictionary.dll" 12 | } -------------------------------------------------------------------------------- /generate_dist.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | IF NOT EXIST dicts\ecdict.db echo Dictionary file missing. It may not work. Please download it! 4 | mkdir dist 5 | mkdir dist\config 6 | mkdir dist\x64 7 | mkdir dist\x86 8 | mkdir dist\Images 9 | xcopy dicts dist\dicts /i /s /y 10 | copy Images\plugin.png dist\Images 11 | copy bin\Release\Dictionary.dll dist 12 | copy bin\Release\Newtonsoft.Json.dll dist 13 | copy bin\Release\x64\SQLite.Interop.dll dist\x64\ 14 | copy bin\Release\x86\SQLite.Interop.dll dist\x86\ 15 | copy bin\Release\System.Data.SQLite.dll dist 16 | copy plugin.json dist 17 | pause -------------------------------------------------------------------------------- /src/Word.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SQLite; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Wox.Plugin; 8 | 9 | namespace Dictionary 10 | { 11 | class Word 12 | { 13 | public string word, translation, exchange, phonetic, definition; 14 | public Word(SQLiteDataReader reader) 15 | { 16 | word = reader["word"].ToString(); 17 | translation = reader["translation"].ToString(); 18 | exchange = reader["exchange"].ToString(); 19 | phonetic = reader["phonetic"].ToString(); 20 | definition = reader["definition"].ToString(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Settings.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Dictionary 10 | { 11 | public class Settings 12 | { 13 | public string ConfigFile; 14 | public string ICIBAToken = "BEBC0A981CB63ED5198597D732BD8956"; 15 | public string BighugelabsToken = ""; 16 | public int MaxEditDistance = 3; 17 | public bool ShowEnglishDefinition = false; 18 | public string WordWebsite = ""; 19 | 20 | public void Save() 21 | { 22 | File.WriteAllText(ConfigFile, JsonConvert.SerializeObject(this)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/WordCorrection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Dictionary 8 | { 9 | class WordCorrection 10 | { 11 | private SymSpell symSpell; 12 | public WordCorrection(string path, int MaxEditDistance) 13 | { 14 | symSpell = new SymSpell(82765, MaxEditDistance, MaxEditDistance + 1); 15 | symSpell.LoadDictionary(path, 0, 1); 16 | } 17 | 18 | public List Correct(string query) 19 | { 20 | if(query == "") return new List(); 21 | return symSpell.Lookup(query, SymSpell.Verbosity.Closest); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /licenses/LICENSE_ECDICT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Linwei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Dictionary.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dictionary", "Dictionary.csproj", "{7686BAC8-1691-4502-986B-B73FB60D79E8}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {7686BAC8-1691-4502-986B-B73FB60D79E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {7686BAC8-1691-4502-986B-B73FB60D79E8}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {7686BAC8-1691-4502-986B-B73FB60D79E8}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {7686BAC8-1691-4502-986B-B73FB60D79E8}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {DEE45AE2-C87F-4CF2-AFEA-08A91FCF1263} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/Synonyms.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Dictionary 10 | { 11 | class Synonyms 12 | { 13 | private string ApiToken; 14 | public Synonyms(string apiToken) 15 | { 16 | ApiToken = apiToken; 17 | } 18 | public List Query(string vocab) 19 | { 20 | List ret = new List(); 21 | try 22 | { 23 | WebRequest request = WebRequest.Create( 24 | String.Format("http://words.bighugelabs.com/api/2/{0}/{1}/", ApiToken, vocab)); 25 | WebResponse response = request.GetResponse(); 26 | Stream dataStream = response.GetResponseStream(); 27 | StreamReader reader = new StreamReader(dataStream); 28 | string strResponse = reader.ReadToEnd(); 29 | 30 | foreach (var line in strResponse.Split('\n')) 31 | { 32 | if (line == "") continue; 33 | var parts = line.Split('|'); 34 | if (parts[1] == "syn") ret.Add(parts[2]); 35 | } 36 | reader.Close(); 37 | response.Close(); 38 | } 39 | catch (Exception) { } 40 | return ret; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Dictionary")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Dictionary")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7686bac8-1691-4502-986b-b73fb60d79e8")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Downloader.cs: -------------------------------------------------------------------------------- 1 | using SevenZip; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | 11 | namespace Dictionary 12 | { 13 | class Downloader 14 | { 15 | const string URL = "https://coding.net/u/nullptr_t/p/ECDICT-sqlite/git/raw/master/ecdict.7z"; 16 | string rootPath; 17 | 18 | public Downloader(string rootPath) 19 | { 20 | this.rootPath = rootPath; 21 | } 22 | 23 | public void Download() 24 | { 25 | if (!File.Exists(rootPath + "dicts\\dict.7z.tmp")) 26 | { 27 | WebClient webClient = new WebClient(); 28 | webClient.DownloadFileCompleted += WebClient_DownloadFileCompleted; 29 | webClient.DownloadFileAsync(new Uri(URL), rootPath + "dicts\\dict.7z.tmp"); 30 | } 31 | else 32 | { 33 | Decompress7Zip(rootPath + "dicts\\", "dict.7z.tmp"); 34 | File.Delete(rootPath + "dicts\\dict.7z.tmp"); 35 | } 36 | } 37 | 38 | private void WebClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) 39 | { 40 | Decompress7Zip(rootPath + "dicts\\", "dict.7z.tmp"); 41 | File.Delete(rootPath + "dicts\\dict.7z.tmp"); 42 | } 43 | 44 | private void Decompress7Zip(string path, string fileName) 45 | { 46 | SevenZipExtractor.SetLibraryPath("7zxa.dll"); 47 | new SevenZipExtractor(path + fileName).ExtractArchive(path); 48 | MessageBox.Show("Download successfully", "Download successfully. Restart Wox to apply. 下载成功!请重启 Wox 来使用词典。"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ECDict.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.SQLite; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Dictionary 10 | { 11 | class ECDict 12 | { 13 | SQLiteConnection conn; 14 | public ECDict(string filename) 15 | { 16 | conn = new SQLiteConnection("Data Source=" + filename + ";Version=3;"); 17 | conn.Open(); 18 | } 19 | 20 | // This will only return exact match. 21 | // Return null if not found. 22 | public Word Query(string word) 23 | { 24 | if (word == "") return null; 25 | 26 | string sql = "select * from stardict where word = '" + word + "'"; 27 | 28 | Word ret = null; 29 | using (SQLiteCommand cmd = new SQLiteCommand(sql, conn)) 30 | { 31 | using (SQLiteDataReader reader = cmd.ExecuteReader()) 32 | { 33 | if (reader.Read()) 34 | ret = new Word(reader); 35 | } 36 | } 37 | return ret; 38 | } 39 | 40 | // This will include exact match and words beginning with it 41 | public List QueryBeginningWith(string word, int limit = 20) 42 | { 43 | if (word == "") return new List(); 44 | 45 | string sql = "select * from stardict where word like '" + word + 46 | "%' order by frq = 0, frq asc limit " + limit; 47 | 48 | List ret = new List(); 49 | using (SQLiteCommand cmd = new SQLiteCommand(sql, conn)) 50 | { 51 | using (SQLiteDataReader reader = cmd.ExecuteReader()) 52 | { 53 | while (reader.Read()) 54 | { 55 | ret.Add(new Word(reader)); 56 | } 57 | } 58 | } 59 | return ret; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/DictionarySettings.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Get one 21 | 22 | 23 | Get one 24 | 25 | 26 | Show English definition instead of Chinese translation 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/DictionarySettings.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace Dictionary 17 | { 18 | public partial class DictionarySettings : UserControl 19 | { 20 | private readonly Settings settings; 21 | 22 | public DictionarySettings(Settings settings) 23 | { 24 | InitializeComponent(); 25 | this.settings = settings; 26 | } 27 | 28 | private void View_Loaded(object sender, RoutedEventArgs re) 29 | { 30 | BighugelabsToken.Text = settings.BighugelabsToken; 31 | BighugelabsToken.TextChanged += (o, e) => 32 | { 33 | settings.BighugelabsToken = BighugelabsToken.Text; 34 | settings.Save(); 35 | }; 36 | 37 | ICIBAToken.Text = settings.ICIBAToken; 38 | ICIBAToken.TextChanged += (o, e) => 39 | { 40 | settings.ICIBAToken = ICIBAToken.Text; 41 | settings.Save(); 42 | }; 43 | 44 | WordWebsite.Text = settings.WordWebsite; 45 | WordWebsite.TextChanged += (o, e) => 46 | { 47 | settings.WordWebsite = WordWebsite.Text; 48 | settings.Save(); 49 | }; 50 | 51 | MaxEditDistance.Text = settings.MaxEditDistance.ToString(); 52 | MaxEditDistance.TextChanged += (o, e) => 53 | { 54 | try 55 | { 56 | settings.MaxEditDistance = Convert.ToInt32(MaxEditDistance.Text); 57 | } 58 | catch (Exception) { } 59 | settings.Save(); 60 | }; 61 | 62 | EnglishDefinition.IsChecked = settings.ShowEnglishDefinition; 63 | EnglishDefinition.Click += (o, e) => 64 | { 65 | settings.ShowEnglishDefinition = EnglishDefinition.IsChecked ?? false; 66 | settings.Save(); 67 | }; 68 | } 69 | 70 | private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) 71 | { 72 | System.Diagnostics.Process.Start(e.Uri.ToString()); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/iciba.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Dictionary 11 | { 12 | class Iciba 13 | { 14 | public class Mean 15 | { 16 | public string mean_id { get; set; } 17 | public string part_id { get; set; } 18 | public string word_mean { get; set; } 19 | public string has_mean { get; set; } 20 | public int split { get; set; } 21 | } 22 | 23 | public class Part 24 | { 25 | public string part_name { get; set; } 26 | public List means { get; set; } 27 | } 28 | 29 | public class Symbol 30 | { 31 | public string symbol_id { get; set; } 32 | public string word_id { get; set; } 33 | public string word_symbol { get; set; } 34 | public string symbol_mp3 { get; set; } 35 | public List parts { get; set; } 36 | public string ph_am_mp3 { get; set; } 37 | public string ph_en_mp3 { get; set; } 38 | public string ph_tts_mp3 { get; set; } 39 | public string ph_other { get; set; } 40 | } 41 | 42 | public class ServerResponse 43 | { 44 | public string word_id { get; set; } 45 | public string word_name { get; set; } 46 | public List symbols { get; set; } 47 | } 48 | 49 | private string token; 50 | public Iciba(string key) { token = key; } 51 | 52 | // Chinese to English. Internet access needed. 53 | public List Query(string word) 54 | { 55 | List ret = new List(); 56 | try 57 | { 58 | WebRequest request = WebRequest.Create( 59 | String.Format("http://dict-co.iciba.com/api/dictionary.php?w={0}&key={1}&type=json", word, token)); 60 | WebResponse response = request.GetResponse(); 61 | Stream dataStream = response.GetResponseStream(); 62 | StreamReader reader = new StreamReader(dataStream); 63 | //dynamic rsp = JsonConvert.DeserializeObject(reader.ReadToEnd()); 64 | var rsp = JsonConvert.DeserializeObject(reader.ReadToEnd()); 65 | 66 | if (rsp.symbols == null) return ret; 67 | foreach (var symbol in rsp.symbols) 68 | { 69 | if (symbol.parts == null) continue; 70 | foreach (var part in symbol.parts) 71 | { 72 | if (part.means == null) continue; 73 | foreach (var mean in part.means) 74 | { 75 | ret.Add(mean.word_mean.ToString()); 76 | } 77 | } 78 | } 79 | } 80 | catch (Exception) { } 81 | return ret; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WoxDictionary 2 | ![Demonstration](Images/demo.gif) 3 | 4 | 这是一个支持中英词语翻译,自动纠正,近义词查找的词典 [Wox](https://github.com/Wox-launcher/Wox) 插件。其大部分功能都支持离线使用。 5 | 6 | This is a [Wox](https://github.com/Wox-launcher/Wox) plugin that supports English/Chinese word translation, word correction and synonym. Most of its functions can work offline. 7 | 8 | 如果你是 [Flow Launcher](https://github.com/Flow-Launcher/Flow.Launcher) 的用户的话,请移步至 [Flow.Launcher.Dictionary](https://github.com/harrynull/Flow.Launcher.Dictionary)。 9 | 10 | If you are an user of [Flow Launcher](https://github.com/Flow-Launcher/Flow.Launcher), there is a port of this plugin at [Flow.Launcher.Dictionary](https://github.com/harrynull/Flow.Launcher.Dictionary). 11 | 12 | ## 功能 / Features 13 | 14 | * 中英互译 English/Chinese word translation 15 | 16 | ![cn_to_en](Images/demo/cn_to_en.png) 17 | 18 | ![en_to_cn](Images/demo/en_to_cn.png) 19 | 20 | * 音标 Phonetic 21 | 22 | ![Phonetic](Images/demo/phonetic.png) 23 | 24 | * 中文释义 Translation 25 | 26 | 单词结尾加!t即可触发. Add !t at the end of the word to trigger. 27 | 28 | ![Translation](Images/demo/translation.png) 29 | 30 | * 英文定义 Definition 31 | 32 | 单词结尾加!d即可触发. Add !d at the end of the word to trigger. 33 | 34 | ![Definition](Images/demo/definition.png) 35 | 36 | * 近义词 Synonym 37 | 38 | 单词结尾加!s即可触发. Add !s at the end of the word to trigger. 39 | 40 | ![Synonym](Images/demo/synonym.png) 41 | 42 | * 变型 Exchanges 43 | 44 | 单词结尾加!e即可触发. Add !e at the end of the word to trigger. 45 | 46 | ![Exchanges](Images/demo/exchanges.png) 47 | 48 | * 拼写修正 Spelling correction 49 | 50 | ![Spelling correction](Images/demo/spelling_correction.png) 51 | 52 | * 快捷复制 Word copy 53 | 54 | 按 Alt+Enter 即可复制单词 Pressing Alt+Enter can copy the word. 55 | 56 | * 单词发音 Pronunciation 57 | 58 | 按 Ctrl+Enter 即可读出选中的单词 Pressing Ctrl+Enter can read the selected word. 59 | 60 | * 搜索单词 Search Words 61 | 62 | 选中任何一个!结果即可跳转到设置好的网站 Select any results in the `!` mode can jump to the configured website. 63 | 64 | 65 | ## 编译 / Compilation 66 | 67 | 1. 使用 Visual Studio 编译此项目。 Compile it using Visual Studio. 68 | 2. 运行`generate_dist.bat` 以自动打包。 Pack it using `generate_dist.bat` 69 | 70 | ## 安装 / Installation 71 | 72 | ### 73 | 74 | 1. 你需要先安装 [Wox](https://github.com/Wox-launcher/Wox)。 75 | 76 | You need to have [Wox](https://github.com/Wox-launcher/Wox) installed first. 77 | 78 | 2. (手动安装) 复制 dist 文件夹到 `C:\Users\\{User Name}\AppData\Local\Wox\app-{Version Number}\Plugins\` 79 | 80 | (Manual Installation) Copy dist folder to `C:\Users\\{User Name}\AppData\Local\Wox\app-{Version Number}\Plugins\` 81 | 82 | (WPM 安装) 在 Wox 中输入 `wpm install Dictionary`。 83 | 84 | (Wox Plugin Manager) Type `wpm install Dictionary` in your Wox. 85 | 86 | 3. **下载,解压,复制 [ecdict.db](https://github.com/harrynull/WoxDictionary/releases/tag/dict) 到 `C:\Users\用户名\AppData\Roaming\Wox\Plugins\Dictionary-随机串\dicts`。** 87 | 88 | **Download, uncompress and copy [ecdict.db](https://github.com/harrynull/WoxDictionary/releases/tag/dict) to `C:\Users\{your_user_name}\AppData\Roaming\Wox\Plugins\Dictionary-{random_characters}\dicts`.** 89 | 90 | 4. 打开设置,配置 API tokens. 91 | 92 | Open settings and configure API tokens. 93 | 94 | 95 | ## 致谢 / Acknowledgment 96 | 97 | 这个项目没有以下伟大项目的帮助是不可能完成的: 98 | 99 | The project won't be possible without the help of the following great projects: 100 | 101 | * [Wox](https://github.com/Wox-launcher/Wox) Launcher for Windows, an alternative to Alfred and Launchy. 102 | * [ECDICT](https://github.com/skywind3000/ECDICT) Free English to Chinese Dictionary Database. By Linwei. 103 | * [SymSpell](https://github.com/wolfgarbe/SymSpell) 1 million times faster through Symmetric Delete spelling correction algorithm. By Wolf Garbe. 104 | * [Big Huge Thesaurus](https://words.bighugelabs.com/api.php) A very simple API for retrieving the synonyms for any word. 105 | * [iciba](http://open.iciba.com/?c=api) Chinese traslation API. 106 | 107 | ## License 108 | 109 | This project is released under LGPL 3.0 License. 110 | 111 | This program is free software: you can redistribute it and/or modify 112 | it under the terms of the GNU General Public License as published by 113 | the Free Software Foundation, either version 3 of the License, or 114 | (at your option) any later version. 115 | 116 | This program is distributed in the hope that it will be useful, 117 | but WITHOUT ANY WARRANTY; without even the implied warranty of 118 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 119 | GNU General Public License for more details. 120 | 121 | You should have received a copy of the GNU General Public License 122 | along with this program. If not, see . 123 | -------------------------------------------------------------------------------- /Dictionary.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7686BAC8-1691-4502-986B-B73FB60D79E8} 8 | Library 9 | Properties 10 | Dictionary 11 | Dictionary 12 | v4.6 13 | 512 14 | 15 | 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | packages\EntityFramework.6.0.0\lib\net45\EntityFramework.dll 38 | 39 | 40 | packages\EntityFramework.6.0.0\lib\net45\EntityFramework.SqlServer.dll 41 | 42 | 43 | 44 | packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | packages\System.Data.SQLite.Core.1.0.106.0\lib\net46\System.Data.SQLite.dll 53 | 54 | 55 | packages\System.Data.SQLite.EF6.1.0.106.0\lib\net46\System.Data.SQLite.EF6.dll 56 | 57 | 58 | packages\System.Data.SQLite.Linq.1.0.106.0\lib\net46\System.Data.SQLite.Linq.dll 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | packages\Wox.Plugin.1.3.159\lib\net452\Wox.Plugin.dll 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | DictionarySettings.xaml 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | Designer 92 | MSBuild:Compile 93 | 94 | 95 | 96 | 97 | 98 | 99 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | 290 | # distribution 291 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /licenses/LICENSE_SymSpell.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017 Wolf Garbe 2 | Version: 6.0 3 | Author: Wolf Garbe 4 | Maintainer: Wolf Garbe 5 | URL: https://github.com/wolfgarbe/symspell 6 | Description: http://blog.faroo.com/2012/06/07/improved-edit-distance-based-spelling-correction/ 7 | License: 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU Lesser General Public License, 10 | version 3.0 (LGPL-3.0) as published by the Free Software Foundation. 11 | http://www.opensource.org/licenses/LGPL-3.0 12 | 13 | GNU LESSER GENERAL PUBLIC LICENSE 14 | Version 3, 29 June 2007 15 | 16 | Copyright (C) 2007 Free Software Foundation, Inc. 17 | Everyone is permitted to copy and distribute verbatim copies 18 | of this license document, but changing it is not allowed. 19 | 20 | 21 | This version of the GNU Lesser General Public License incorporates 22 | the terms and conditions of version 3 of the GNU General Public 23 | License, supplemented by the additional permissions listed below. 24 | 25 | 0. Additional Definitions. 26 | 27 | As used herein, "this License" refers to version 3 of the GNU Lesser 28 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 29 | General Public License. 30 | 31 | "The Library" refers to a covered work governed by this License, 32 | other than an Application or a Combined Work as defined below. 33 | 34 | An "Application" is any work that makes use of an interface provided 35 | by the Library, but which is not otherwise based on the Library. 36 | Defining a subclass of a class defined by the Library is deemed a mode 37 | of using an interface provided by the Library. 38 | 39 | A "Combined Work" is a work produced by combining or linking an 40 | Application with the Library. The particular version of the Library 41 | with which the Combined Work was made is also called the "Linked 42 | Version". 43 | 44 | The "Minimal Corresponding Source" for a Combined Work means the 45 | Corresponding Source for the Combined Work, excluding any source code 46 | for portions of the Combined Work that, considered in isolation, are 47 | based on the Application, and not on the Linked Version. 48 | 49 | The "Corresponding Application Code" for a Combined Work means the 50 | object code and/or source code for the Application, including any data 51 | and utility programs needed for reproducing the Combined Work from the 52 | Application, but excluding the System Libraries of the Combined Work. 53 | 54 | 1. Exception to Section 3 of the GNU GPL. 55 | 56 | You may convey a covered work under sections 3 and 4 of this License 57 | without being bound by section 3 of the GNU GPL. 58 | 59 | 2. Conveying Modified Versions. 60 | 61 | If you modify a copy of the Library, and, in your modifications, a 62 | facility refers to a function or data to be supplied by an Application 63 | that uses the facility (other than as an argument passed when the 64 | facility is invoked), then you may convey a copy of the modified 65 | version: 66 | 67 | a) under this License, provided that you make a good faith effort to 68 | ensure that, in the event an Application does not supply the 69 | function or data, the facility still operates, and performs 70 | whatever part of its purpose remains meaningful, or 71 | 72 | b) under the GNU GPL, with none of the additional permissions of 73 | this License applicable to that copy. 74 | 75 | 3. Object Code Incorporating Material from Library Header Files. 76 | 77 | The object code form of an Application may incorporate material from 78 | a header file that is part of the Library. You may convey such object 79 | code under terms of your choice, provided that, if the incorporated 80 | material is not limited to numerical parameters, data structure 81 | layouts and accessors, or small macros, inline functions and templates 82 | (ten or fewer lines in length), you do both of the following: 83 | 84 | a) Give prominent notice with each copy of the object code that the 85 | Library is used in it and that the Library and its use are 86 | covered by this License. 87 | 88 | b) Accompany the object code with a copy of the GNU GPL and this license 89 | document. 90 | 91 | 4. Combined Works. 92 | 93 | You may convey a Combined Work under terms of your choice that, 94 | taken together, effectively do not restrict modification of the 95 | portions of the Library contained in the Combined Work and reverse 96 | engineering for debugging such modifications, if you also do each of 97 | the following: 98 | 99 | a) Give prominent notice with each copy of the Combined Work that 100 | the Library is used in it and that the Library and its use are 101 | covered by this License. 102 | 103 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 104 | document. 105 | 106 | c) For a Combined Work that displays copyright notices during 107 | execution, include the copyright notice for the Library among 108 | these notices, as well as a reference directing the user to the 109 | copies of the GNU GPL and this license document. 110 | 111 | d) Do one of the following: 112 | 113 | 0) Convey the Minimal Corresponding Source under the terms of this 114 | License, and the Corresponding Application Code in a form 115 | suitable for, and under terms that permit, the user to 116 | recombine or relink the Application with a modified version of 117 | the Linked Version to produce a modified Combined Work, in the 118 | manner specified by section 6 of the GNU GPL for conveying 119 | Corresponding Source. 120 | 121 | 1) Use a suitable shared library mechanism for linking with the 122 | Library. A suitable mechanism is one that (a) uses at run time 123 | a copy of the Library already present on the user's computer 124 | system, and (b) will operate properly with a modified version 125 | of the Library that is interface-compatible with the Linked 126 | Version. 127 | 128 | e) Provide Installation Information, but only if you would otherwise 129 | be required to provide such information under section 6 of the 130 | GNU GPL, and only to the extent that such information is 131 | necessary to install and execute a modified version of the 132 | Combined Work produced by recombining or relinking the 133 | Application with a modified version of the Linked Version. (If 134 | you use option 4d0, the Installation Information must accompany 135 | the Minimal Corresponding Source and Corresponding Application 136 | Code. If you use option 4d1, you must provide the Installation 137 | Information in the manner specified by section 6 of the GNU GPL 138 | for conveying Corresponding Source.) 139 | 140 | 5. Combined Libraries. 141 | 142 | You may place library facilities that are a work based on the 143 | Library side by side in a single library together with other library 144 | facilities that are not Applications and are not covered by this 145 | License, and convey such a combined library under terms of your 146 | choice, if you do both of the following: 147 | 148 | a) Accompany the combined library with a copy of the same work based 149 | on the Library, uncombined with any other library facilities, 150 | conveyed under the terms of this License. 151 | 152 | b) Give prominent notice with the combined library that part of it 153 | is a work based on the Library, and explaining where to find the 154 | accompanying uncombined form of the same work. 155 | 156 | 6. Revised Versions of the GNU Lesser General Public License. 157 | 158 | The Free Software Foundation may publish revised and/or new versions 159 | of the GNU Lesser General Public License from time to time. Such new 160 | versions will be similar in spirit to the present version, but may 161 | differ in detail to address new problems or concerns. 162 | 163 | Each version is given a distinguishing version number. If the 164 | Library as you received it specifies that a certain numbered version 165 | of the GNU Lesser General Public License "or any later version" 166 | applies to it, you have the option of following the terms and 167 | conditions either of that published version or of any later version 168 | published by the Free Software Foundation. If the Library as you 169 | received it does not specify a version number of the GNU Lesser 170 | General Public License, you may choose any version of the GNU Lesser 171 | General Public License ever published by the Free Software Foundation. 172 | 173 | If the Library as you received it specifies that a proxy can decide 174 | whether future versions of the GNU Lesser General Public License shall 175 | apply, that proxy's public statement of acceptance of any version is 176 | permanent authorization for you to choose that version for the 177 | Library. -------------------------------------------------------------------------------- /src/EditDistance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class EditDistance 4 | { 5 | /// 6 | /// Computes and returns the Damerau-Levenshtein edit distance between two strings, 7 | /// i.e. the number of insertion, deletion, sustitution, and transposition edits 8 | /// required to transform one string to the other. This value will be >= 0, where 0 9 | /// indicates identical strings. Comparisons are case sensitive, so for example, 10 | /// "Fred" and "fred" will have a distance of 1. This algorithm is basically the 11 | /// Levenshtein algorithm with a modification that considers transposition of two 12 | /// adjacent characters as a single edit. 13 | /// http://blog.softwx.net/2015/01/optimizing-damerau-levenshtein_15.html 14 | /// https://github.com/softwx/SoftWx.Match 15 | /// 16 | /// See http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance 17 | /// This is inspired by Sten Hjelmqvist'string1 "Fast, memory efficient" algorithm, described 18 | /// at http://www.codeproject.com/Articles/13525/Fast-memory-efficient-Levenshtein-algorithm. 19 | /// This version differs by adding additiona optimizations, and extending it to the Damerau- 20 | /// Levenshtein algorithm. 21 | /// Note that this is the simpler and faster optimal string alignment (aka restricted edit) distance 22 | /// that difers slightly from the classic Damerau-Levenshtein algorithm by imposing the restriction 23 | /// that no substring is edited more than once. So for example, "CA" to "ABC" has an edit distance 24 | /// of 2 by a complete application of Damerau-Levenshtein, but a distance of 3 by this method that 25 | /// uses the optimal string alignment algorithm. See wikipedia article for more detail on this 26 | /// distinction. 27 | /// 28 | /// 29 | /// The MIT License (MIT) 30 | /// 31 | ///Copyright(c) 2015 Steve Hatchett 32 | /// 33 | ///Permission is hereby granted, free of charge, to any person obtaining a copy 34 | ///of this software and associated documentation files(the "Software"), to deal 35 | ///in the Software without restriction, including without limitation the rights 36 | ///to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | ///copies of the Software, and to permit persons to whom the Software is 38 | ///furnished to do so, subject to the following conditions: 39 | /// 40 | ///The above copyright notice and this permission notice shall be included in all 41 | ///copies or substantial portions of the Software. 42 | /// 43 | ///THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | ///IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | ///FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 46 | ///AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | ///LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | ///OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | ///SOFTWARE. 50 | /// 51 | 52 | //Supported edit distance algorithms. 53 | public enum DistanceAlgorithm 54 | { 55 | Damerau 56 | } 57 | private string baseString; 58 | private DistanceAlgorithm algorithm; 59 | private int[] v0; 60 | private int[] v2; 61 | /// Create a new EditDistance object. 62 | /// The base string to which other strings will be compared. 63 | /// The desired edit distance algorithm. 64 | public EditDistance(string baseString, DistanceAlgorithm algorithm) 65 | { 66 | this.baseString = baseString; 67 | this.algorithm = algorithm; 68 | if (this.baseString == "") 69 | { 70 | this.baseString = null; 71 | return; 72 | } 73 | if (algorithm == DistanceAlgorithm.Damerau) 74 | { 75 | v0 = new int[baseString.Length]; 76 | v2 = new int[baseString.Length]; // stores one level further back (offset by +1 position) 77 | } 78 | } 79 | /// Compare a string to the base string to determine the edit distance, 80 | /// using the previously selected algorithm. 81 | /// The string to compare. 82 | /// The maximum distance allowed. 83 | /// The edit distance (or -1 if maxDistance exceeded). 84 | public int Compare(string string2, int maxDistance) 85 | { 86 | switch (algorithm) 87 | { 88 | case DistanceAlgorithm.Damerau: return DamerauLevenshteinDistance(string2, maxDistance); 89 | } 90 | throw new ArgumentException("unknown DistanceAlgorithm"); 91 | } 92 | // stores one level further back (offset by +1 position) 93 | /// String being compared for distance. 94 | /// String being compared against other string. 95 | /// The maximum edit distance of interest. 96 | /// int edit distance, >= 0 representing the number of edits required 97 | /// to transform one string to the other, or -1 if the distance is greater than the specified maxDistance. 98 | public int DamerauLevenshteinDistance(string string2, int maxDistance) 99 | { 100 | if (baseString == null) return (string2 ?? "").Length; 101 | if (String.IsNullOrEmpty(string2)) return baseString.Length; 102 | 103 | // if strings of different lengths, ensure shorter string is in string1. This can result in a little 104 | // faster speed by spending more time spinning just the inner loop during the main processing. 105 | string string1; 106 | if (baseString.Length > string2.Length) 107 | { 108 | string1 = string2; 109 | string2 = baseString; 110 | } 111 | else 112 | { 113 | string1 = baseString; 114 | } 115 | int sLen = string1.Length; // this is also the minimun length of the two strings 116 | int tLen = string2.Length; 117 | 118 | // suffix common to both strings can be ignored 119 | while ((sLen > 0) && (string1[sLen - 1] == string2[tLen - 1])) { sLen--; tLen--; } 120 | 121 | int start = 0; 122 | if ((string1[0] == string2[0]) || (sLen == 0)) 123 | { // if there'string1 a shared prefix, or all string1 matches string2'string1 suffix 124 | // prefix common to both strings can be ignored 125 | while ((start < sLen) && (string1[start] == string2[start])) start++; 126 | sLen -= start; // length of the part excluding common prefix and suffix 127 | tLen -= start; 128 | 129 | // if all of shorter string matches prefix and/or suffix of longer string, then 130 | // edit distance is just the delete of additional characters present in longer string 131 | if (sLen == 0) return tLen; 132 | 133 | string2 = string2.Substring(start, tLen); // faster than string2[start+j] in inner loop below 134 | } 135 | int lenDiff = tLen - sLen; 136 | if ((maxDistance < 0) || (maxDistance > tLen)) 137 | { 138 | maxDistance = tLen; 139 | } 140 | else if (lenDiff > maxDistance) return -1; 141 | 142 | if (tLen > v0.Length) 143 | { 144 | v0 = new int[tLen]; 145 | v2 = new int[tLen]; 146 | } 147 | else 148 | { 149 | Array.Clear(v2, 0, tLen); 150 | } 151 | int j; 152 | for (j = 0; j < maxDistance; j++) v0[j] = j + 1; 153 | for (; j < tLen; j++) v0[j] = maxDistance + 1; 154 | 155 | int jStartOffset = maxDistance - (tLen - sLen); 156 | bool haveMax = maxDistance < tLen; 157 | int jStart = 0; 158 | int jEnd = maxDistance; 159 | char sChar = string1[0]; 160 | int current = 0; 161 | for (int i = 0; i < sLen; i++) 162 | { 163 | char prevsChar = sChar; 164 | sChar = string1[start + i]; 165 | char tChar = string2[0]; 166 | int left = i; 167 | current = left + 1; 168 | int nextTransCost = 0; 169 | // no need to look beyond window of lower right diagonal - maxDistance cells (lower right diag is i - lenDiff) 170 | // and the upper left diagonal + maxDistance cells (upper left is i) 171 | jStart += (i > jStartOffset) ? 1 : 0; 172 | jEnd += (jEnd < tLen) ? 1 : 0; 173 | for (j = jStart; j < jEnd; j++) 174 | { 175 | int above = current; 176 | int thisTransCost = nextTransCost; 177 | nextTransCost = v2[j]; 178 | v2[j] = current = left; // cost of diagonal (substitution) 179 | left = v0[j]; // left now equals current cost (which will be diagonal at next iteration) 180 | char prevtChar = tChar; 181 | tChar = string2[j]; 182 | if (sChar != tChar) 183 | { 184 | if (left < current) current = left; // insertion 185 | if (above < current) current = above; // deletion 186 | current++; 187 | if ((i != 0) && (j != 0) 188 | && (sChar == prevtChar) 189 | && (prevsChar == tChar)) 190 | { 191 | thisTransCost++; 192 | if (thisTransCost < current) current = thisTransCost; // transposition 193 | } 194 | } 195 | v0[j] = current; 196 | } 197 | if (haveMax && (v0[i + lenDiff] > maxDistance)) return -1; 198 | } 199 | return (current <= maxDistance) ? current : -1; 200 | } 201 | } -------------------------------------------------------------------------------- /src/Dictionary.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Speech.Synthesis; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows; 12 | using System.Windows.Controls; 13 | using Wox.Plugin; 14 | 15 | namespace Dictionary 16 | { 17 | public class Main : IPlugin, ISettingProvider 18 | { 19 | private ECDict ecdict; 20 | private WordCorrection wordCorrection; 21 | private Synonyms synonyms; 22 | private Iciba iciba; 23 | private PluginInitContext context; 24 | private Settings settings; 25 | private SpeechSynthesizer synth; 26 | 27 | // These two are only for jumping in MakeResultItem 28 | private string ActionWord; 29 | private string QueryWord; 30 | 31 | public Control CreateSettingPanel() 32 | { 33 | return new DictionarySettings(settings); 34 | } 35 | 36 | public void Init(PluginInitContext context) 37 | { 38 | string CurrentPath = context.CurrentPluginMetadata.PluginDirectory; 39 | string ConfigFile = CurrentPath + "/config/config.json"; 40 | if (File.Exists(ConfigFile)) 41 | settings = JsonConvert.DeserializeObject(File.ReadAllText(ConfigFile)); 42 | else 43 | settings = new Settings(); 44 | settings.ConfigFile = ConfigFile; 45 | 46 | ecdict = new ECDict(CurrentPath + "/dicts/ecdict.db"); 47 | wordCorrection = new WordCorrection(CurrentPath + "/dicts/frequency_dictionary_en_82_765.txt", settings.MaxEditDistance); 48 | synonyms = new Synonyms(settings.BighugelabsToken); 49 | iciba = new Iciba(settings.ICIBAToken); 50 | this.context = context; 51 | } 52 | 53 | Result MakeResultItem(string title, string subtitle, string extraAction = null, string word = null) 54 | { 55 | string getWord() { return (word ?? QueryWord).Replace("!", ""); } 56 | // Return true if the user tries to copy (regradless of the result) 57 | bool CopyIfNeeded(ActionContext e) 58 | { 59 | if (!e.SpecialKeyState.AltPressed) return false; 60 | try 61 | { 62 | Clipboard.SetText(getWord()); 63 | } 64 | catch (ExternalException ee) 65 | { 66 | context.API.ShowMsg("Copy failed, please try later", ee.Message); 67 | } 68 | return true; 69 | } 70 | bool ReadWordIfNeeded(ActionContext e) 71 | { 72 | if (!e.SpecialKeyState.CtrlPressed) return false; 73 | if (synth == null) 74 | { 75 | synth = new SpeechSynthesizer(); 76 | synth.SetOutputToDefaultAudioDevice(); 77 | } 78 | synth.SpeakAsync(getWord()); 79 | return true; 80 | } 81 | 82 | Func ActionFunc; 83 | if (extraAction != null) 84 | { 85 | ActionFunc = e => 86 | { 87 | if (CopyIfNeeded(e)) return true; 88 | if (ReadWordIfNeeded(e)) return false; 89 | context.API.ChangeQuery(ActionWord + " " + (word ?? QueryWord) + extraAction); 90 | return false; 91 | }; 92 | } 93 | else 94 | { 95 | ActionFunc = e => { 96 | if(CopyIfNeeded(e)) return true; 97 | if(ReadWordIfNeeded(e)) return false; 98 | if(settings.WordWebsite!="") System.Diagnostics.Process.Start(string.Format(settings.WordWebsite, getWord())); 99 | return true; 100 | }; 101 | } 102 | return new Result() 103 | { 104 | Title = title, 105 | SubTitle = subtitle, 106 | IcoPath = "Images\\plugin.png", 107 | Action = ActionFunc 108 | }; 109 | } 110 | 111 | private Result MakeWordResult(Word word) => 112 | MakeResultItem(word.word, (word.phonetic != "" ? ("/" + word.phonetic + "/ ") : "") + 113 | (settings.ShowEnglishDefinition ? word.definition.Replace("\n", "; ") : word.translation.Replace("\n", "; ")), 114 | "!", word.word); 115 | 116 | // First-level query. 117 | // English -> Chinese, supports fuzzy search. 118 | private List FirstLevelQuery(Query query) 119 | { 120 | bool IsExistsInResults(List res, string word) 121 | { 122 | foreach (var item in res) 123 | { 124 | if (item.Title == word) return true; 125 | } 126 | return false; 127 | } 128 | 129 | string queryWord = query.Search; 130 | List results = new List(); 131 | 132 | // Pull fully match first. 133 | Word fullMatch = ecdict.Query(query.Search); 134 | if (fullMatch != null) results.Add(MakeWordResult(fullMatch)); 135 | 136 | // Then fuzzy search results. (since it's usually only a few) 137 | List suggestions = wordCorrection.Correct(queryWord); 138 | foreach (var suggestion in suggestions) 139 | { 140 | Word word = ecdict.Query(suggestion.term); 141 | 142 | if(!IsExistsInResults(results, word.word)) // to avoid repetitive results 143 | results.Add(MakeWordResult(word)); 144 | } 145 | 146 | // Lastly, the words beginning with the query. 147 | var result_begin = ecdict.QueryBeginningWith(queryWord); 148 | foreach (var word in result_begin) 149 | { 150 | if (!IsExistsInResults(results, word.word)) 151 | results.Add(MakeWordResult(word)); 152 | } 153 | 154 | return results; 155 | } 156 | 157 | // Detailed information of a word. 158 | // English -> Phonetic, Translation, Definition, Exchanges, Synonym 159 | // Fuzzy search disabled. 160 | private List DetailedQuery(Query query) 161 | { 162 | string queryWord = query.Search.Substring(0, query.Search.Length - 1); // Remove the ! 163 | 164 | List results = new List(); 165 | 166 | var word = ecdict.Query(queryWord); 167 | 168 | if (word.phonetic != "") 169 | results.Add(MakeResultItem(word.phonetic, "Phonetic")); 170 | if (word.translation != "") 171 | results.Add(MakeResultItem("Translation", word.translation.Replace("\n", "; "), "t")); 172 | if (word.definition != "") 173 | results.Add(MakeResultItem("Definition", word.definition.Replace("\n", "; "), "d")); 174 | if (word.exchange != "") 175 | results.Add(MakeResultItem("Exchanges", word.exchange, "e")); 176 | var synonymsResult = String.Join("; ", synonyms.Query(word.word)); 177 | if (synonymsResult != "") 178 | results.Add(MakeResultItem("Synonym", synonymsResult, "s")); 179 | return results; 180 | } 181 | 182 | // Translations of a word. 183 | // English -> Translations 184 | // Fuzzy search disabled. 185 | private List TranslationQuery(Query query) 186 | { 187 | string queryWord = query.Search.Substring(0, query.Search.Length - 2); // Get the word 188 | 189 | List results = new List(); 190 | 191 | var word = ecdict.Query(queryWord); 192 | 193 | foreach (var translation in word.translation.Split('\n')) 194 | { 195 | results.Add(MakeResultItem(translation, "Translation")); 196 | } 197 | 198 | return results; 199 | } 200 | 201 | // Definitions of a word. 202 | // English -> Definitions 203 | // Fuzzy search disabled. 204 | private List DefinitionQuery(Query query) 205 | { 206 | string queryWord = query.Search.Substring(0, query.Search.Length - 2); // Get the word 207 | 208 | List results = new List(); 209 | 210 | var word = ecdict.Query(queryWord); 211 | 212 | foreach (var definition in word.definition.Split('\n')) 213 | { 214 | results.Add(MakeResultItem(definition, "Definitions")); 215 | } 216 | 217 | return results; 218 | } 219 | 220 | // Exchanges of a word. 221 | // English -> Exchanges 222 | // Fuzzy search disabled. 223 | private List ExchangeQuery(Query query) 224 | { 225 | string queryWord = query.Search.Substring(0, query.Search.Length - 2); // Get the word 226 | 227 | List results = new List(); 228 | 229 | var word = ecdict.Query(queryWord); 230 | 231 | foreach (var exchange in word.exchange.Split('/')) 232 | { 233 | results.Add(MakeResultItem(exchange, "Exchanges")); 234 | } 235 | 236 | return results; 237 | } 238 | 239 | // Synonyms of a word. 240 | // English -> Synonyms 241 | // Fuzzy search disabled. 242 | // Internet access needed. 243 | private List SynonymQuery(Query query) 244 | { 245 | string queryWord = query.Search.Substring(0, query.Search.Length - 2); // Get the word 246 | 247 | List results = new List(); 248 | 249 | var syns = synonyms.Query(queryWord); 250 | 251 | foreach (var syn in syns) 252 | { 253 | results.Add(MakeWordResult(ecdict.Query(syn))); 254 | } 255 | 256 | return results; 257 | } 258 | 259 | // Chinese translation of a word. 260 | // English -> Synonyms 261 | // Fuzzy search disabled. 262 | // Internet access needed. 263 | private List ChineseQuery(Query query) 264 | { 265 | string queryWord = query.Search; // Get the word 266 | 267 | List results = new List(); 268 | 269 | var translations = iciba.Query(queryWord); 270 | 271 | if (translations.Count == 0) 272 | { 273 | results.Add(MakeResultItem("No Results Found", queryWord)); 274 | } 275 | else 276 | { 277 | foreach (var translation in translations) 278 | { 279 | results.Add(MakeResultItem(translation, queryWord, "!", translation)); 280 | } 281 | } 282 | 283 | return results; 284 | } 285 | 286 | private bool IsChinese(string cn) 287 | { 288 | foreach (char c in cn) { 289 | UnicodeCategory cat = char.GetUnicodeCategory(c); 290 | if (cat == UnicodeCategory.OtherLetter) 291 | return true; 292 | } 293 | return false; 294 | } 295 | 296 | public List Query(Query query) 297 | { 298 | ActionWord = query.ActionKeyword; 299 | string queryWord = query.Search; 300 | if (queryWord == "") return new List(); 301 | QueryWord = queryWord; 302 | if (queryWord.Last() == '!') // An '!' at the end enables detailed query 303 | return DetailedQuery(query); 304 | else if (queryWord.Length >= 2 && queryWord.Substring(queryWord.Length - 2, 2) == "!d") 305 | return DefinitionQuery(query); 306 | else if (queryWord.Length >= 2 && queryWord.Substring(queryWord.Length - 2, 2) == "!t") 307 | return TranslationQuery(query); 308 | else if (queryWord.Length >= 2 && queryWord.Substring(queryWord.Length - 2, 2) == "!e") 309 | return ExchangeQuery(query); 310 | else if (queryWord.Length >= 2 && queryWord.Substring(queryWord.Length - 2, 2) == "!s") 311 | return SynonymQuery(query); 312 | else if (IsChinese(queryWord)) 313 | return ChineseQuery(query); 314 | else return FirstLevelQuery(query); // First-level query 315 | } 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/SymSpell.cs: -------------------------------------------------------------------------------- 1 | // SymSpell: 1 million times faster through Symmetric Delete spelling correction algorithm 2 | // 3 | // The Symmetric Delete spelling correction algorithm reduces the complexity of edit candidate generation and dictionary lookup 4 | // for a given Damerau-Levenshtein distance. It is six orders of magnitude faster and language independent. 5 | // Opposite to other algorithms only deletes are required, no transposes + replaces + inserts. 6 | // Transposes + replaces + inserts of the input term are transformed into deletes of the dictionary term. 7 | // Replaces and inserts are expensive and language dependent: e.g. Chinese has 70,000 Unicode Han characters! 8 | // 9 | // Copyright (C) 2017 Wolf Garbe 10 | // Version: 6.0 11 | // Author: Wolf Garbe wolf.garbe@faroo.com 12 | // Maintainer: Wolf Garbe wolf.garbe@faroo.com 13 | // URL: https://github.com/wolfgarbe/symspell 14 | // Description: http://blog.faroo.com/2012/06/07/improved-edit-distance-based-spelling-correction/ 15 | // 16 | // License: 17 | // This program is free software; you can redistribute it and/or modify 18 | // it under the terms of the GNU Lesser General Public License, 19 | // version 3.0 (LGPL-3.0) as published by the Free Software Foundation. 20 | // http://www.opensource.org/licenses/LGPL-3.0 21 | using System; 22 | using System.Collections.Generic; 23 | using System.IO; 24 | using System.Text.RegularExpressions; 25 | public class SymSpell 26 | { 27 | /// Controls the closeness/quantity of returned spelling suggestions. 28 | public enum Verbosity 29 | { 30 | /// Top suggestion with the highest term frequency of the suggestions of smallest edit distance found. 31 | Top, 32 | /// All suggestions of smallest edit distance found, suggestions ordered by term frequency. 33 | Closest, 34 | /// All suggestions within maxEditDistance, suggestions ordered by edit distance 35 | /// , then by term frequency (slower, no early termination). 36 | All 37 | }; 38 | 39 | const int defaultMaxEditDistance = 2; 40 | const int defaultPrefixLength = 7; 41 | const int defaultCountThreshold = 1; 42 | const int defaultInitialCapacity = 16; 43 | const int defaultCompactLevel = 5; 44 | 45 | private readonly int initialCapacity; 46 | private readonly int maxDictionaryEditDistance; 47 | private readonly int prefixLength; //prefix length 5..7 48 | private readonly Int64 countThreshold; //a treshold might be specifid, when a term occurs so frequently in the corpus that it is considered a valid word for spelling correction 49 | private readonly uint compactMask; 50 | private readonly EditDistance.DistanceAlgorithm distanceAlgorithm = EditDistance.DistanceAlgorithm.Damerau; 51 | private int maxLength; //maximum dictionary term length 52 | 53 | // Dictionary that contains a mapping of lists of suggested correction words to the hashCodes 54 | // of the original words and the deletes derived from them. Collisions of hashCodes is tolerated, 55 | // because suggestions are ultimately verified via an edit distance function. 56 | // A list of suggestions might have a single suggestion, or multiple suggestions. 57 | private Dictionary deletes; 58 | // Dictionary of unique correct spelling words, and the frequency count for each word. 59 | private readonly Dictionary words; 60 | // Dictionary of unique words that are below the count threshold for being considered correct spellings. 61 | private Dictionary belowThresholdWords = new Dictionary(); 62 | 63 | /// Spelling suggestion returned from Lookup. 64 | public class SuggestItem : IComparable 65 | { 66 | /// The suggested correctly spelled word. 67 | public string term = ""; 68 | /// Edit distance between searched for word and suggestion. 69 | public int distance = 0; 70 | /// Frequency of suggestion in the dictionary (a measure of how common the word is). 71 | public Int64 count = 0; 72 | 73 | /// Create a new instance of SuggestItem. 74 | /// The suggested word. 75 | /// Edit distance from search word. 76 | /// Frequency of suggestion in dictionary. 77 | public SuggestItem(string term, int distance, Int64 count) 78 | { 79 | this.term = term; 80 | this.distance = distance; 81 | this.count = count; 82 | } 83 | public int CompareTo(SuggestItem other) 84 | { 85 | // order by distance ascending, then by frequency count descending 86 | if (this.distance == other.distance) return other.count.CompareTo(this.count); 87 | return this.distance.CompareTo(other.distance); 88 | } 89 | public override bool Equals(object obj) 90 | { 91 | return Equals(term, ((SuggestItem)obj).term); 92 | } 93 | 94 | public override int GetHashCode() 95 | { 96 | return term.GetHashCode(); 97 | } 98 | public override string ToString() 99 | { 100 | return "{" + term + ", " + distance + ", " + count + "}"; 101 | } 102 | } 103 | 104 | /// Maximum edit distance for dictionary precalculation. 105 | public int MaxDictionaryEditDistance { get { return this.maxDictionaryEditDistance; } } 106 | 107 | /// Length of prefix, from which deletes are generated. 108 | public int PrefixLength { get { return this.prefixLength; } } 109 | 110 | /// Length of longest word in the dictionary. 111 | public int MaxLength { get { return this.maxLength; } } 112 | 113 | /// Count threshold for a word to be considered a valid word for spelling correction. 114 | public long CountThreshold { get { return this.countThreshold; } } 115 | 116 | /// Number of unique words in the dictionary. 117 | public int WordCount { get { return this.words.Count; } } 118 | 119 | /// Number of word prefixes and intermediate word deletes encoded in the dictionary. 120 | public int EntryCount { get { return this.deletes.Count; } } 121 | 122 | /// Create a new instanc of SymSpell. 123 | /// Specifying ann accurate initialCapacity is not essential, 124 | /// but it can help speed up processing by aleviating the need for 125 | /// data restructuring as the size grows. 126 | /// The expected number of words in dictionary. 127 | /// Maximum edit distance for doing lookups. 128 | /// The length of word prefixes used for spell checking.. 129 | /// The minimum frequency count for dictionary words to be considered correct spellings. 130 | /// Degree of favoring lower memory use over speed (0=fastest,most memory, 16=slowest,least memory). 131 | public SymSpell(int initialCapacity = defaultInitialCapacity, int maxDictionaryEditDistance = defaultMaxEditDistance 132 | , int prefixLength = defaultPrefixLength, int countThreshold = defaultCountThreshold 133 | , byte compactLevel = defaultCompactLevel) 134 | { 135 | if (initialCapacity < 0) throw new ArgumentOutOfRangeException(nameof(initialCapacity)); 136 | if (maxDictionaryEditDistance < 0) throw new ArgumentOutOfRangeException(nameof(maxDictionaryEditDistance)); 137 | if (prefixLength < 1 || prefixLength <= maxDictionaryEditDistance) throw new ArgumentOutOfRangeException(nameof(prefixLength)); 138 | if (countThreshold < 0) throw new ArgumentOutOfRangeException(nameof(countThreshold)); 139 | if (compactLevel > 16) throw new ArgumentOutOfRangeException(nameof(compactLevel)); 140 | 141 | this.initialCapacity = initialCapacity; 142 | this.words = new Dictionary(initialCapacity); 143 | this.maxDictionaryEditDistance = maxDictionaryEditDistance; 144 | this.prefixLength = prefixLength; 145 | this.countThreshold = countThreshold; 146 | if (compactLevel > 16) compactLevel = 16; 147 | this.compactMask = (uint.MaxValue >> (3 + compactLevel)) << 2; 148 | } 149 | 150 | /// Create/Update an entry in the dictionary. 151 | /// For every word there are deletes with an edit distance of 1..maxEditDistance created and added to the 152 | /// dictionary. Every delete entry has a suggestions list, which points to the original term(s) it was created from. 153 | /// The dictionary may be dynamically updated (word frequency and new words) at any time by calling CreateDictionaryEntry 154 | /// The word to add to dictionary. 155 | /// The frequency count for word. 156 | /// Optional staging object to speed up adding many entries by staging them to a temporary structure. 157 | /// True if the word was added as a new correctly spelled word, 158 | /// or false if the word is added as a below threshold word, or updates an 159 | /// existing correctly spelled word. 160 | public bool CreateDictionaryEntry(string key, Int64 count, SuggestionStage staging = null) 161 | { 162 | if (count <= 0) 163 | { 164 | if (this.countThreshold > 0) return false; // no point doing anything if count is zero, as it can't change anything 165 | count = 0; 166 | } 167 | Int64 countPrevious = -1; 168 | 169 | // look first in below threshold words, update count, and allow promotion to correct spelling word if count reaches threshold 170 | // threshold must be >1 for there to be the possibility of low threshold words 171 | if (countThreshold > 1 && belowThresholdWords.TryGetValue(key, out countPrevious)) 172 | { 173 | // calculate new count for below threshold word 174 | count = (Int64.MaxValue - countPrevious > count) ? countPrevious + count : Int64.MaxValue; 175 | // has reached threshold - remove from below threshold collection (it will be added to correct words below) 176 | if (count >= countThreshold) 177 | { 178 | belowThresholdWords.Remove(key); 179 | } 180 | else 181 | { 182 | belowThresholdWords[key] = count; 183 | return false; 184 | } 185 | } 186 | else if (words.TryGetValue(key, out countPrevious)) 187 | { 188 | // just update count if it's an already added above threshold word 189 | count = (Int64.MaxValue - countPrevious > count) ? countPrevious + count : Int64.MaxValue; 190 | words[key] = count; 191 | return false; 192 | } 193 | else if (count < CountThreshold) 194 | { 195 | // new or existing below threshold word 196 | belowThresholdWords[key] = count; 197 | return false; 198 | } 199 | 200 | // what we have at this point is a new, above threshold word 201 | words.Add(key, count); 202 | 203 | //edits/suggestions are created only once, no matter how often word occurs 204 | //edits/suggestions are created only as soon as the word occurs in the corpus, 205 | //even if the same term existed before in the dictionary as an edit from another word 206 | if (key.Length > maxLength) maxLength = key.Length; 207 | 208 | //create deletes 209 | var edits = EditsPrefix(key); 210 | // if not staging suggestions, put directly into main data structure 211 | if (staging != null) 212 | { 213 | foreach (string delete in edits) staging.Add(GetStringHash(delete), key); 214 | } 215 | else 216 | { 217 | if (deletes == null) this.deletes = new Dictionary(initialCapacity); //initialisierung 218 | foreach (string delete in edits) 219 | { 220 | int deleteHash = GetStringHash(delete); 221 | if (deletes.TryGetValue(deleteHash, out string[] suggestions)) 222 | { 223 | var newSuggestions = new string[suggestions.Length + 1]; 224 | Array.Copy(suggestions, newSuggestions, suggestions.Length); 225 | deletes[deleteHash] = suggestions = newSuggestions; 226 | } 227 | else 228 | { 229 | suggestions = new string[1]; 230 | deletes.Add(deleteHash, suggestions); 231 | } 232 | suggestions[suggestions.Length - 1] = key; 233 | } 234 | } 235 | return true; 236 | } 237 | 238 | /// Load multiple dictionary entries from a file of word/frequency count pairs 239 | /// Merges with any dictionary data already loaded. 240 | /// The path+filename of the file. 241 | /// The column position of the word. 242 | /// The column position of the frequency count. 243 | /// True if file loaded, or false if file not found. 244 | public bool LoadDictionary(string corpus, int termIndex, int countIndex) 245 | { 246 | if (!File.Exists(corpus)) return false; 247 | var staging = new SuggestionStage(16384); 248 | using (StreamReader sr = new StreamReader(File.OpenRead(corpus))) 249 | { 250 | String line; 251 | 252 | //process a single line at a time only for memory efficiency 253 | while ((line = sr.ReadLine()) != null) 254 | { 255 | string[] lineParts = line.Split(null); 256 | if (lineParts.Length >= 2) 257 | { 258 | string key = lineParts[termIndex]; 259 | //Int64 count; 260 | if (Int64.TryParse(lineParts[countIndex], out Int64 count)) 261 | { 262 | CreateDictionaryEntry(key, count, staging); 263 | } 264 | } 265 | } 266 | } 267 | if (this.deletes == null) this.deletes = new Dictionary(staging.DeleteCount); 268 | CommitStaged(staging); 269 | return true; 270 | } 271 | 272 | //create a frequency dictionary from a corpus (merges with any dictionary data already loaded) 273 | /// Load multiple dictionary words from a file containing plain text. 274 | /// The path+filename of the file. 275 | /// True if file loaded, or false if file not found. 276 | public bool CreateDictionary(string corpus) 277 | { 278 | if (!File.Exists(corpus)) return false; 279 | var staging = new SuggestionStage(16384); 280 | using (StreamReader sr = new StreamReader(File.OpenRead(corpus))) 281 | { 282 | String line; 283 | //process a single line at a time only for memory efficiency 284 | while ((line = sr.ReadLine()) != null) 285 | { 286 | foreach (string key in ParseWords(line)) 287 | { 288 | CreateDictionaryEntry(key, 1, staging); 289 | } 290 | } 291 | } 292 | if (this.deletes == null) this.deletes = new Dictionary(staging.DeleteCount); 293 | CommitStaged(staging); 294 | return true; 295 | } 296 | 297 | /// Remove all below threshold words from the dictionary. 298 | /// This can be used to reduce memory consumption after populating the dictionary from 299 | /// a corpus using CreateDictionary. 300 | public void PurgeBelowThresholdWords() 301 | { 302 | belowThresholdWords = new Dictionary(); 303 | } 304 | 305 | /// Commit staged dictionary additions. 306 | /// Used when you write your own process to load multiple words into the 307 | /// dictionary, and as part of that process, you first created a SuggestionsStage 308 | /// object, and passed that to CreateDictionaryEntry calls. 309 | /// The SuggestionStage object storing the staged data. 310 | public void CommitStaged(SuggestionStage staging) 311 | { 312 | staging.CommitTo(deletes); 313 | } 314 | 315 | /// Find suggested spellings for a given input word, using the maximum 316 | /// edit distance specified during construction of the SymSpell dictionary. 317 | /// The word being spell checked. 318 | /// The value controlling the quantity/closeness of the retuned suggestions. 319 | /// A List of SuggestItem object representing suggested correct spellings for the input word, 320 | /// sorted by edit distance, and secondarily by count frequency. 321 | public List Lookup(string input, Verbosity verbosity) 322 | { 323 | return Lookup(input, verbosity, this.maxDictionaryEditDistance); 324 | } 325 | 326 | /// Find suggested spellings for a given input word. 327 | /// The word being spell checked. 328 | /// The value controlling the quantity/closeness of the retuned suggestions. 329 | /// The maximum edit distance between input and suggested words. 330 | /// A List of SuggestItem object representing suggested correct spellings for the input word, 331 | /// sorted by edit distance, and secondarily by count frequency. 332 | public List Lookup(string input, Verbosity verbosity, int maxEditDistance) 333 | { 334 | //verbosity=Top: the suggestion with the highest term frequency of the suggestions of smallest edit distance found 335 | //verbosity=Closest: all suggestions of smallest edit distance found, the suggestions are ordered by term frequency 336 | //verbosity=All: all suggestions <= maxEditDistance, the suggestions are ordered by edit distance, then by term frequency (slower, no early termination) 337 | 338 | // maxEditDistance used in Lookup can't be bigger than the maxDictionaryEditDistance 339 | // used to construct the underlying dictionary structure. 340 | if (maxEditDistance > MaxDictionaryEditDistance) throw new ArgumentOutOfRangeException(nameof(maxEditDistance)); 341 | 342 | List suggestions = new List(); 343 | int inputLen = input.Length; 344 | // early exit - word is too big to possibly match any words 345 | if (inputLen - maxEditDistance > maxLength) return suggestions; 346 | 347 | // deletes we've considered already 348 | HashSet hashset1 = new HashSet(); 349 | // suggestions we've considered already 350 | HashSet hashset2 = new HashSet(); 351 | 352 | // quick look for exact match 353 | long suggestionCount = 0; 354 | if (words.TryGetValue(input, out suggestionCount)) 355 | { 356 | suggestions.Add(new SuggestItem(input, 0, suggestionCount)); 357 | // early exit - return exact match, unless caller wants all matches 358 | if (verbosity != Verbosity.All) return suggestions; 359 | } 360 | hashset2.Add(input); // we considered the input already in the word.TryGetValue above 361 | 362 | int maxEditDistance2 = maxEditDistance; 363 | int candidatePointer = 0; 364 | var singleSuggestion = new string[1] { string.Empty }; 365 | List candidates = new List(); 366 | 367 | //add original prefix 368 | int inputPrefixLen = inputLen; 369 | if (inputPrefixLen > prefixLength) 370 | { 371 | inputPrefixLen = prefixLength; 372 | candidates.Add(input.Substring(0, inputPrefixLen)); 373 | } 374 | else 375 | { 376 | candidates.Add(input); 377 | } 378 | var distanceComparer = new EditDistance(input, this.distanceAlgorithm); 379 | while (candidatePointer < candidates.Count) 380 | { 381 | string candidate = candidates[candidatePointer++]; 382 | int candidateLen = candidate.Length; 383 | int lengthDiff = inputPrefixLen - candidateLen; 384 | 385 | //save some time - early termination 386 | //if canddate distance is already higher than suggestion distance, than there are no better suggestions to be expected 387 | if (lengthDiff > maxEditDistance2) 388 | { 389 | // skip to next candidate if Verbosity.All, look no further if Verbosity.Top or Closest 390 | // (candidates are ordered by delete distance, so none are closer than current) 391 | if (verbosity == Verbosity.All) continue; 392 | break; 393 | } 394 | 395 | //read candidate entry from dictionary 396 | if (deletes.TryGetValue(GetStringHash(candidate), out string[] dictSuggestions)) 397 | { 398 | //iterate through suggestions (to other correct dictionary items) of delete item and add them to suggestion list 399 | for (int i = 0; i < dictSuggestions.Length; i++) 400 | { 401 | var suggestion = dictSuggestions[i]; 402 | int suggestionLen = suggestion.Length; 403 | if (suggestion == input) continue; 404 | if ((Math.Abs(suggestionLen - inputLen) > maxEditDistance2) // input and sugg lengths diff > allowed/current best distance 405 | || (suggestionLen < candidateLen) // sugg must be for a different delete string, in same bin only because of hash collision 406 | || (suggestionLen == candidateLen && suggestion != candidate)) // if sugg len = delete len, then it either equals delete or is in same bin only because of hash collision 407 | continue; 408 | var suggPrefixLen = Math.Min(suggestionLen, prefixLength); 409 | if (suggPrefixLen > inputPrefixLen && (suggPrefixLen - candidateLen) > maxEditDistance2) continue; 410 | 411 | //True Damerau-Levenshtein Edit Distance: adjust distance, if both distances>0 412 | //We allow simultaneous edits (deletes) of maxEditDistance on on both the dictionary and the input term. 413 | //For replaces and adjacent transposes the resulting edit distance stays <= maxEditDistance. 414 | //For inserts and deletes the resulting edit distance might exceed maxEditDistance. 415 | //To prevent suggestions of a higher edit distance, we need to calculate the resulting edit distance, if there are simultaneous edits on both sides. 416 | //Example: (bank==bnak and bank==bink, but bank!=kanb and bank!=xban and bank!=baxn for maxEditDistance=1) 417 | //Two deletes on each side of a pair makes them all equal, but the first two pairs have edit distance=1, the others edit distance=2. 418 | int distance = 0; 419 | int min = 0; 420 | if (candidateLen == 0) 421 | { 422 | //suggestions which have no common chars with input (inputLen<=maxEditDistance && suggestionLen<=maxEditDistance) 423 | distance = Math.Max(inputLen, suggestionLen); 424 | if (distance > maxEditDistance2 || !hashset2.Add(suggestion)) continue; 425 | } 426 | else if (suggestionLen == 1) 427 | { 428 | if (input.IndexOf(suggestion[0]) < 0) distance = inputLen; else distance = inputLen - 1; 429 | if (distance > maxEditDistance2 || !hashset2.Add(suggestion)) continue; 430 | } 431 | else 432 | //number of edits in prefix ==maxediddistance AND no identic suffix 433 | //, then editdistance>maxEditDistance and no need for Levenshtein calculation 434 | // (inputLen >= prefixLength) && (suggestionLen >= prefixLength) 435 | if ((prefixLength - maxEditDistance == candidateLen) 436 | && (((min = Math.Min(inputLen, suggestionLen) - prefixLength) > 1) 437 | && (input.Substring(inputLen + 1 - min) != suggestion.Substring(suggestionLen + 1 - min))) 438 | || ((min > 0) && (input[inputLen - min] != suggestion[suggestionLen - min]) 439 | && ((input[inputLen - min - 1] != suggestion[suggestionLen - min]) 440 | || (input[inputLen - min] != suggestion[suggestionLen - min - 1])))) 441 | { 442 | continue; 443 | } 444 | else 445 | { 446 | // DeleteInSuggestionPrefix is somewhat expensive, and only pays off when verbosity is Top or Closest. 447 | if ((verbosity != Verbosity.All && !DeleteInSuggestionPrefix(candidate, candidateLen, suggestion, suggestionLen)) 448 | || !hashset2.Add(suggestion)) continue; 449 | distance = distanceComparer.Compare(suggestion, maxEditDistance2); 450 | if (distance < 0) continue; 451 | } 452 | 453 | //save some time 454 | //do not process higher distances than those already found, if verbosity 0) 460 | { 461 | switch (verbosity) 462 | { 463 | case Verbosity.Closest: 464 | { 465 | //we will calculate DamLev distance only to the smallest found distance so far 466 | if (distance < maxEditDistance2) suggestions.Clear(); 467 | break; 468 | } 469 | case Verbosity.Top: 470 | { 471 | if (distance < maxEditDistance2 || suggestionCount > suggestions[0].count) 472 | { 473 | maxEditDistance2 = distance; 474 | suggestions[0] = si; 475 | } 476 | continue; 477 | } 478 | } 479 | } 480 | if (verbosity != Verbosity.All) maxEditDistance2 = distance; 481 | suggestions.Add(si); 482 | } 483 | }//end foreach 484 | }//end if 485 | 486 | //add edits 487 | //derive edits (deletes) from candidate (input) and add them to candidates list 488 | //this is a recursive process until the maximum edit distance has been reached 489 | if ((lengthDiff < maxEditDistance) && (candidateLen <= prefixLength)) 490 | { 491 | //save some time 492 | //do not create edits with edit distance smaller than suggestions already found 493 | if (verbosity != Verbosity.All && lengthDiff >= maxEditDistance2) continue; 494 | 495 | for (int i = 0; i < candidateLen; i++) 496 | { 497 | string delete = candidate.Remove(i, 1); 498 | 499 | if (hashset1.Add(delete)) { candidates.Add(delete); } 500 | } 501 | } 502 | }//end while 503 | 504 | //sort by ascending edit distance, then by descending word frequency 505 | if (suggestions.Count > 1) suggestions.Sort(); 506 | return suggestions; 507 | }//end if 508 | 509 | /// An intentionally opacque class used to temporarily stage 510 | /// dictionary data during the adding of many words. By staging the 511 | /// data during the building of the dictionary data, significant savings 512 | /// of time can be achieved, as well as a reduction in final memory usage. 513 | public class SuggestionStage 514 | { 515 | private struct Node 516 | { 517 | public string suggestion; 518 | public int next; 519 | } 520 | private struct Entry 521 | { 522 | public int count; 523 | public int first; 524 | } 525 | private Dictionary Deletes { get; set; } 526 | private ChunkArray Nodes { get; set; } 527 | /// Create a new instance of SuggestionStage. 528 | /// Specifying ann accurate initialCapacity is not essential, 529 | /// but it can help speed up processing by aleviating the need for 530 | /// data restructuring as the size grows. 531 | /// The expected number of words that will be added. 532 | public SuggestionStage(int initialCapacity) 533 | { 534 | Deletes = new Dictionary(initialCapacity); 535 | Nodes = new ChunkArray(initialCapacity * 2); 536 | } 537 | /// Gets the count of unique delete words. 538 | public int DeleteCount { get { return Deletes.Count; } } 539 | /// Gets the total count of all suggestions for all deletes. 540 | public int NodeCount { get { return Nodes.Count; } } 541 | /// Clears all the data from the SuggestionStaging. 542 | public void Clear() 543 | { 544 | Deletes.Clear(); 545 | Nodes.Clear(); 546 | } 547 | internal void Add(int deleteHash, string suggestion) 548 | { 549 | if (!Deletes.TryGetValue(deleteHash, out Entry entry)) entry = new Entry { count = 0, first = -1 }; 550 | int next = entry.first; 551 | entry.count++; 552 | entry.first = Nodes.Count; 553 | Deletes[deleteHash] = entry; 554 | Nodes.Add(new Node { suggestion = suggestion, next = next }); 555 | } 556 | internal void CommitTo(Dictionary permanentDeletes) 557 | { 558 | foreach (var keyPair in Deletes) 559 | { 560 | int i; 561 | if (permanentDeletes.TryGetValue(keyPair.Key, out string[] suggestions)) 562 | { 563 | i = suggestions.Length; 564 | var newSuggestions = new string[suggestions.Length + keyPair.Value.count]; 565 | Array.Copy(suggestions, newSuggestions, suggestions.Length); 566 | permanentDeletes[keyPair.Key] = suggestions = newSuggestions; 567 | } 568 | else 569 | { 570 | i = 0; 571 | suggestions = new string[keyPair.Value.count]; 572 | permanentDeletes.Add(keyPair.Key, suggestions); 573 | } 574 | int next = keyPair.Value.first; 575 | while (next >= 0) 576 | { 577 | var node = Nodes[next]; 578 | suggestions[i] = node.suggestion; 579 | next = node.next; 580 | i++; 581 | } 582 | } 583 | } 584 | } 585 | 586 | //check whether all delete chars are present in the suggestion prefix in correct order, otherwise this is just a hash collision 587 | private bool DeleteInSuggestionPrefix(string delete, int deleteLen, string suggestion, int suggestionLen) 588 | { 589 | if (deleteLen == 0) return true; 590 | if (prefixLength < suggestionLen) suggestionLen = prefixLength; 591 | int j = 0; 592 | for (int i = 0; i < deleteLen; i++) 593 | { 594 | char delChar = delete[i]; 595 | while (j < suggestionLen && delChar != suggestion[j]) j++; 596 | if (j == suggestionLen) return false; 597 | } 598 | return true; 599 | } 600 | 601 | //create a non-unique wordlist from sample text 602 | //language independent (e.g. works with Chinese characters) 603 | private string[] ParseWords(string text) 604 | { 605 | // \w Alphanumeric characters (including non-latin characters, umlaut characters and digits) plus "_" 606 | // \d Digits 607 | // Compatible with non-latin characters, does not split words at apostrophes 608 | MatchCollection mc = Regex.Matches(text.ToLower(), @"['’\w-[_]]+"); 609 | 610 | //for benchmarking only: with CreateDictionary("big.txt","") and the text corpus from http://norvig.com/big.txt the Regex below provides the exact same number of dictionary items as Norvigs regex "[a-z]+" (which splits words at apostrophes & incompatible with non-latin characters) 611 | //MatchCollection mc = Regex.Matches(text.ToLower(), @"[\w-[\d_]]+"); 612 | 613 | var matches = new string[mc.Count]; 614 | for (int i = 0; i < matches.Length; i++) matches[i] = mc[i].ToString(); 615 | return matches; 616 | } 617 | 618 | //inexpensive and language independent: only deletes, no transposes + replaces + inserts 619 | //replaces and inserts are expensive and language dependent (Chinese has 70,000 Unicode Han characters) 620 | private HashSet Edits(string word, int editDistance, HashSet deleteWords) 621 | { 622 | editDistance++; 623 | if (word.Length > 1) 624 | { 625 | for (int i = 0; i < word.Length; i++) 626 | { 627 | string delete = word.Remove(i, 1); 628 | if (deleteWords.Add(delete)) 629 | { 630 | //recursion, if maximum edit distance not yet reached 631 | if (editDistance < maxDictionaryEditDistance) Edits(delete, editDistance, deleteWords); 632 | } 633 | } 634 | } 635 | return deleteWords; 636 | } 637 | 638 | private HashSet EditsPrefix(string key) 639 | { 640 | HashSet hashSet = new HashSet(); 641 | if (key.Length <= maxDictionaryEditDistance) hashSet.Add(""); 642 | if (key.Length > prefixLength) key = key.Substring(0, prefixLength); 643 | hashSet.Add(key); 644 | return Edits(key, 0, hashSet); 645 | } 646 | 647 | private int GetStringHash(string s) 648 | { 649 | //return s.GetHashCode(); 650 | 651 | int len = s.Length; 652 | int lenMask = len; 653 | if (lenMask > 3) lenMask = 3; 654 | 655 | uint hash = 2166136261; 656 | for (var i = 0; i < len; i++) 657 | { 658 | unchecked 659 | { 660 | hash ^= s[i]; 661 | hash *= 16777619; 662 | } 663 | } 664 | 665 | hash &= this.compactMask; 666 | hash |= (uint)lenMask; 667 | return (int)hash; 668 | } 669 | 670 | // A growable list of elements that's optimized to support adds, but not deletes, 671 | // of large numbers of elements, storing data in a way that's friendly to the garbage 672 | // collector (not backed by a monolithic array object), and can grow without needing 673 | // to copy the entire backing array contents from the old backing array to the new. 674 | private class ChunkArray 675 | { 676 | private const int ChunkSize = 4096; //this must be a power of 2, otherwise can't optimize Row and Col functions 677 | private const int DivShift = 12; // number of bits to shift right to do division by ChunkSize (the bit position of ChunkSize) 678 | public T[][] Values { get; private set; } 679 | public int Count { get; private set; } 680 | public ChunkArray(int initialCapacity) 681 | { 682 | int chunks = (initialCapacity + ChunkSize - 1) / ChunkSize; 683 | Values = new T[chunks][]; 684 | for (int i = 0; i < Values.Length; i++) Values[i] = new T[ChunkSize]; 685 | } 686 | public int Add(T value) 687 | { 688 | if (Count == Capacity) 689 | { 690 | var newValues = new T[Values.Length + 1][]; 691 | // only need to copy the list of array blocks, not the data in the blocks 692 | Array.Copy(Values, newValues, Values.Length); 693 | newValues[Values.Length] = new T[ChunkSize]; 694 | Values = newValues; 695 | } 696 | Values[Row(Count)][Col(Count)] = value; 697 | Count++; 698 | return Count - 1; 699 | } 700 | public void Clear() 701 | { 702 | Count = 0; 703 | } 704 | public T this[int index] 705 | { 706 | get { return Values[Row(index)][Col(index)]; } 707 | set { Values[Row(index)][Col(index)] = value; } 708 | } 709 | private int Row(int index) { return index >> DivShift; } // same as index / ChunkSize 710 | private int Col(int index) { return index & (ChunkSize - 1); } //same as index % ChunkSize 711 | private int Capacity { get { return Values.Length * ChunkSize; } } 712 | } 713 | } --------------------------------------------------------------------------------