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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
# Copyright © 2022, 飞麦 <fitmap@qq.com>, All rights reserved. # frozen_string_literal: true # 说明:极点词库是相对较好用的词库,收纳词的数量比较恰当,太少会导致很多词打不出来,太多导致有些词重码过多。 # 个人因地域、工作、生活等因素也需要一些专用词,可以自行收集整理,然后与极点词库合并再更新到系统词库中。 # 名称:五笔词库合并工具 # 功能:将极点五笔词库与个人词库合并, 生成供微软五笔用的词库, 供 WubiLex 生成并替换系统词库 # 免费软件 极点五笔 地址: https://pc.qq.com/detail/14/detail_214.html 从其导出的词库 freeime.txt 在压缩包中已包含 # 开源软件 WubiLex 地址: https://wubi.aardio.com/ 可执行文件 WubiLex.exe 在压缩包中已包含 # 个人词库分三类: # ⑴ fix_*.txt: 个人需要固定的词汇[不测是否与已有词重复]{如行政区划/朋友/公交站/民族/股票/街道办与社区等等} # ⑵ ins_*.txt: 个人需要增加的词汇[检测是否与已有词重复]{如健康类/户外类/工作类/生活类/理财类等等} # ⑶ rmv_*.txt: 个人需要删除的词汇{如化为(因华为更常用)} # 极点五笔词库: freeime.txt # 运行本程序后的合并词库名称: fitmap.txt (供 WubiLex 导入) @usual_zima_h = {} # 常用汉字编码表 @rare_zima_h = {} # 生僻汉字编码表 @mazici_a_a = [] # 编码字词表 [编码, 字词1, 字词2, ...] @cima_h = {} # 词编码表(用于分辨重复编码词及统计已编码词数量) # 定义字符串的汉字相关函数 class String # 返回汉字个数 def han_size chars.count { |char| char.match(/\p{Han}/) } end # 返回纯汉字串 def han_str chars.select { |char| char.match(/\p{Han}/) }.join end # 是否全为汉字 def han? match(/^\p{Han}+$/) end # 是否全为汉字或全角大写字母[用于股票名称] def han_or_da? match(/^(\p{Han}|[A-Z])+$/) end # 是否全为汉字或全角大写字母或英文字母数字 def han_or_da_or_word? match(/^(\p{Han}|[A-Z]|\p{word})+$/) end end # 导入原始码表文件(安装极点五笔后导出的码表文本文件) def load_freeime(freeime_txt) File.read(freeime_txt, mode: 'rb:bom|utf-16le').encode('utf-8') end # 处理尾(尾), 取消所有拼音的编码并修订四声拼音字符的编码为4位以便使用 def deal_tail(tail) tail.sub(/zzpy .+?\r\n/m, '').gsub(/zzpy(\w)/, 'zzp\1') # ā ō ē ī ū ǖ end # 将极点码表文件分割为3部分(安装极点五笔后导出的码表文本文件): 返回头、身、尾 def split_ime_txt(freeime_txt) ime_content = load_freeime(freeime_txt) # 此文件为原始编码文件, 不得修改 body_loc = ime_content.index(/^\w+\s/) tail_loc = ime_content.index(/^zz/) discard_loc = ime_content.index(%r{^/}) # 抛弃特殊编码 head = ime_content[0...body_loc] body = ime_content[body_loc...tail_loc] tail = deal_tail(ime_content[tail_loc...discard_loc]) [head, body, tail] end # 保存字编码(字, 编码, 字编码表), 其中编码取最长最后的 def save_char_code(char, code, zima_h) if zima_h.key?(char) # 若字已有编码 if zima_h[char].size <= code.size # 若已记录编码较短 # puts "多重编码字: #{char} 编码=#{zima_h[char]} #{code}" if zima_h[char].size == code.size && zima_h[char] != code zima_h[char] = code # 记录字的更长编码 end else # 若字尚无编码 zima_h[char] = code # 记录单字的编码 end end # 保存词编码(词, 编码) def save_word_code(word, code) if @cima_h.key?(word) puts "多重编码词: #{word} 编码=#{@cima_h[word]}#{code}" else @cima_h[word] = code end end # 保存字词编码(字词, 编码) def save_zici_code(zici, code) if zici.size == 1 # 若为单字, 标准极点词汇不含非汉字 save_char_code(zici, code, @usual_zima_h) else # 若为词 save_word_code(zici, code) end end # 解析字词(字词, 编码), 返回字词是否为汉字/全角大写字母/英文字母数字 def parse_zici(zici, code) valid_word = false if zici.han_or_da_or_word? # 若全部为汉字/全角大写字母/英文字母数字 save_zici_code(zici, code) valid_word = true elsif zici[0] == '~' # 带 ~ 为生僻字(后面仅接一个字), 不引入新词库 raise "非法长度: #{zici}" if zici.size != 2 # puts "非法汉字: #{zici}" unless zici[1].han? # 标准极点词库含少量非汉字 save_char_code(zici[1], code, @rare_zima_h) if zici[1].han? end # 上面不处理 ^ 开头的一些极点指令 valid_word end # 解析(身) def parse_body(body) body.each_line do |line| # 每行格式为: 编码 字词1 字词2 ... code_or_word_a = line.split # 按空格拆分 code = code_or_word_a[0] # 获取编码 mazici_a = [code] # 初始长度为 1 code_or_word_a[1..].each do |word| valid_word = parse_zici(word, code) mazici_a << word if valid_word end @mazici_a_a << mazici_a if mazici_a.size >= 2 # 说明至少有一个字词 end end # 获取词中若干指定位置的字的编码(词, 位置列表) def pick_codes(word, idx_a) code_a = [] idx_a.each do |idx| char = word[idx] code = @usual_zima_h[char] || @rare_zima_h[char] raise "缺少 #{char} 的五笔码" unless code code_a << code end code_a end # 计算双字词编码(双字词), 首1码, 首2码, 尾1码, 尾2码 def code_two_han(word) code_a = pick_codes(word, [0, 1]) code_a[0][0, 2] + code_a[1][0, 2] end # 计算三字词编码(三字词), 首1码, 次1码, 尾1码, 尾2码 def code_three_han(word) code_a = pick_codes(word, [0, 1, 2]) code_a[0][0] + code_a[1][0] + code_a[2][0, 2] end # 计算多字词编码(多字词), 首1码, 次1码, 三1码, 尾1码 def code_more_han(word) code_a = pick_codes(word, [0, 1, 2, -1]) code_a[0][0] + code_a[1][0] + code_a[2][0] + code_a[-1][0] end # 根据五笔编码规则生成词的五笔码(词) def calc_word_wbm(word) word = word.han_str # 仅获取其中的汉字 if word.size == 2 code_two_han(word) elsif word.size == 3 code_three_han(word) else code_more_han(word) end end # 合并编码已存在的词(词, 五笔码, 个人词库名, 编码字词表所在行) def merge_exist(word, wbm, jet_txt, mazici_a) if mazici_a[1..].index(word) # 词已存在 puts "#{jet_txt}#{word} 词已存在" unless jet_txt.match(/^fix_/) else mazici_a[2, 0] = word # 设置新词为第二候选词 @cima_h[word] = wbm # 记录词的五笔码 end end # 合并编码不存在的词(词, 五笔码, 编码字词表下标) def merge_lack(word, wbm, ndx) @mazici_a_a[ndx, 0] = [[wbm, word]] # 设置新编码与新词 @cima_h[word] = wbm end # 合入一个词(词, 五笔码, 码字码表下标, 个人词库名) def join_word(word, wbm, ndx, jet_txt) mazici_a = @mazici_a_a[ndx] if mazici_a[0] == wbm # 编码已存在 merge_exist(word, wbm, jet_txt, mazici_a) else # 编码不存在 merge_lack(word, wbm, ndx) end end # 合并一个词(词, 个人词库名) def merge_word(word, jet_txt) wbm = calc_word_wbm(word) # 计算词的五笔码 ndx = @mazici_a_a.bsearch_index { |mazici_a| mazici_a[0] >= wbm } # 二分查找五笔码 join_word(word, wbm, ndx, jet_txt) end # 在编码已存在的词中删除指定词(词, 编码字词表所在行), 返回词数是否为零 def erase_exist(word, mazici_a) if mazici_a[1..].index(word) # 词已存在 mazici_a.delete(word) @cima_h.delete(word) else puts "#{word} 不在当前词库中" end mazici_a.size <= 1 end # 删除一个词(词, 五笔码, 码字码表下标) def erase_word(word, wbm, ndx) mazici_a = @mazici_a_a[ndx] if mazici_a[0] == wbm # 编码已存在 empty = erase_exist(word, mazici_a) @mazici_a_a.delete_at(ndx) if empty else # 编码不存在 puts "#{word} 不在现有词库中" end end # 消除一个词(词) def purge_word(word) wbm = calc_word_wbm(word) # 计算词的五笔码 ndx = @mazici_a_a.bsearch_index { |mazici_a| mazici_a[0] >= wbm } # 二分查找五笔码 erase_word(word, wbm, ndx) end # 插入个人词库 def ins_personal_words all_num = 0 Dir['{fix,ins}_*.txt'].each_with_index do |jet_txt, jet_idx| usr_words = File.read(jet_txt).split(/\s+/) usr_words.each do |word| next unless word.han_or_da_or_word? && word.han_size >= 2 # 消除非汉字/非全角大写字母/非英文字母数字/单汉字 merge_word(word, jet_txt) end all_num += usr_words.size puts "#{jet_idx}\t#{jet_txt}\t词数=#{usr_words.size}" end puts "所有个人词数=#{all_num}(含与已有词重复的)" end # 消除个人词库 def rmv_personal_words Dir['rmv_*.txt'].each_with_index do |jet_txt, jet_idx| rmv_words = File.read(jet_txt).split(/\s+/) rmv_words.each do |word| next unless word.han_or_da_or_word? && word.han_size >= 2 # 消除非汉字/非全角大写字母/非英文字母数字/单汉字 purge_word(word) end puts "#{jet_idx}\t#{jet_txt}\t词数=#{rmv_words.size}" end end # 导入个人词库, 合并进原始码表中 def merge_personal_words ins_personal_words rmv_personal_words end # 根据新字词编码表生成文本 def gen_body mazica_str_a = [] @mazici_a_a.each do |mazici_a| mazica_str_a << mazici_a.join('') end mazica_str_a << '' # 需要在尾部加个回车换行 mazica_str_a.join("\r\n") end # 保存五笔编码文件(输出的码表文件) def save_ime_txt(fitmap_txt, head, new_body, tail) text = ["\ufeff", head, new_body, tail].join.encode(Encoding::UTF_16LE) File.binwrite(fitmap_txt, text) end # 输出字词数量(不同长度的字词的个数) def output_num(len_h) print "长=1 量=#{@usual_zima_h.size}" len_h.keys.sort.each do |len| print " 长=#{len} 量=#{len_h[len]}" end puts end # 统计字词情况(场景名称) def stats_zc(title) puts "#{title}\t字数=#{@usual_zima_h.size}\t词数=#{@cima_h.size}" len_h = {} # 不同长度的字词的个数 @cima_h.each_key do |word| len_h[word.size] ||= 0 # 此时考虑所有字符 len_h[word.size] += 1 end output_num(len_h) end # 转换代码长度与代码编码 def gen_code_pair(code) code_len = code.size u16le_code = code.ljust(4, "\u0000").encode(Encoding::UTF_16LE).force_encoding(Encoding::BINARY) [code_len, u16le_code] end # 转换字词并计算块长 def gen_zici_blklen(zici) u16le_zici = zici.encode(Encoding::UTF_16LE).force_encoding(Encoding::BINARY) block_len = 16 + u16le_zici.bytesize [u16le_zici, block_len] end # 进行 Windows 五笔系统词库单个编码多个字词的转换 def trans_zici_a(code, zici_a) zici_str_a = [] zici_a.each_with_index do |zici, idx| weight = idx + 1 code_len, u16le_code = gen_code_pair(code) u16le_zici, block_len = gen_zici_blklen(zici) zici_str = [block_len, weight, code_len].pack('S3') + u16le_code + u16le_zici + [0].pack('S') zici_str_a << zici_str end zici_str_a.join end # 向前推进字词转换 def step_zicis(mazici_a, mazica_str_a, code, loc) zici_a = mazici_a[1..] mazica_str = trans_zici_a(code, zici_a) loc += mazica_str.bytesize mazica_str_a << mazica_str loc end # 根据新字词编码表生成 Windows 五笔系统词库主体 def gen_lex_main mazica_str_a = [] first = nil loc = 0 loc_a = [] @mazici_a_a.each do |mazici_a| code = mazici_a[0] break if code[0] >= 'z' if code[0] != first first = code[0] loc_a[first.ord - 'a'.ord] = loc end loc = step_zicis(mazici_a, mazica_str_a, code, loc) end loc_a << loc [mazica_str_a, loc_a, loc] end # 根据新字词编码表生成 Windows 五笔系统词库头部 def gen_lex_head(file_len, loc_a) head_a = [] head_a << 'imscwubi'.encode(Encoding::BINARY) head_a << [1, 1].pack('S2') head_a << [0x40, 0xA8, file_len, 0x78563412].pack('L4') head_a << ["\x00"].pack('a36') head_a << loc_a.pack('L26') head_a.join end # 根据新字词编码表生成 Windows 五笔系统词库 def gen_lex mazica_str_a, loc_a, loc = gen_lex_main file_len = 0xA8 + loc lex_head = gen_lex_head(file_len, loc_a) File.open('ChsWubiNew.lex', 'wb') do |file| file.write(lex_head) mazica_str_a.each do |mazica_str| file.write(mazica_str) end end end # 五笔词库合并工具主程序 def main head, body, tail = split_ime_txt('freeime.txt') parse_body(body) stats_zc('原始') merge_personal_words stats_zc('合并后') save_ime_txt('fitmap.txt', head, gen_body, tail) gen_lex end $PROGRAM_NAME == __FILE__ && Dir.chdir('/N/Jet') { main } |
五笔词库合并工具
发布时间 2024-01-09 09:17:38作者: 飞麦