feat: 发布4×4候选与Android手表应用内键盘组件
这个提交包含在:
1 父节点
82aeeba816
当前提交
87aa966a51
86 个文件已更改
+2697
-2975
没有与搜索条件相匹配的文件
@@ -0,0 +1,51 @@
|
||||
"""Losslessly build the Android SQLite asset from the licensed original V5 trie."""
|
||||
import argparse,hashlib,json,pathlib,sqlite3
|
||||
APP=pathlib.Path(__file__).resolve().parents[1]
|
||||
|
||||
def build(rtos):
|
||||
source=rtos/'third_party/crabKeyboard/pinyinDictV5_20260416'
|
||||
files={p.name:p.read_bytes()for p in source.glob('*.dat')};entries={}
|
||||
assert len(files)==41,'Expected the complete upstream V5 dictionary'
|
||||
def number(text):
|
||||
result=0
|
||||
for ch in text:assert 33<=ord(ch)<=126;result=result*94+ord(ch)-33
|
||||
return result
|
||||
def walk(key,depth,position,length):
|
||||
for item in files[f'key{depth}.dat'][position:position+length].decode('ascii').split('\x1e'):
|
||||
if not item:continue
|
||||
offset,size=map(number,item[1:].split('\x1f'))
|
||||
if item[0]=='_':entries[key]=[word for word in files[f'value{depth}.dat'][offset:offset+size].decode('utf-8').split(' ')if word]
|
||||
else:walk(key+item[0],depth+1,offset,size)
|
||||
walk('',0,0,len(files['key0.dat']))
|
||||
frequency_path=rtos/'third_party/jieba/dict.txt'
|
||||
frequencies={}
|
||||
for line in frequency_path.read_text(encoding='utf-8').splitlines():
|
||||
parts=line.split(' ')
|
||||
if len(parts)>=2 and parts[1].isdigit():frequencies[parts[0]]=int(parts[1])
|
||||
groups9=['abc','def','ghi','jkl','mno','pqrs','tuv','wxyz'];groups14=['qw','er','ty','ui','op','as','df','gh','jk','l','zx','cv','bn','m']
|
||||
map9={ch:str(i+2)for i,group in enumerate(groups9)for ch in group}
|
||||
map14={ch:'0123456789abcd'[i]for i,group in enumerate(groups14)for ch in group}
|
||||
target=APP/'keyboard/src/main/assets/dictionary.db';target.parent.mkdir(parents=True,exist_ok=True)
|
||||
temporary=target.with_suffix('.tmp')
|
||||
if temporary.exists():temporary.unlink()
|
||||
db=sqlite3.connect(temporary)
|
||||
db.executescript('PRAGMA page_size=4096; PRAGMA journal_mode=OFF; CREATE TABLE entries(id INTEGER PRIMARY KEY,pinyin TEXT NOT NULL UNIQUE,code9 TEXT NOT NULL,code14 TEXT NOT NULL); CREATE TABLE words(entry INTEGER NOT NULL,ordinal INTEGER NOT NULL,word TEXT NOT NULL,frequency INTEGER NOT NULL,PRIMARY KEY(entry,ordinal)) WITHOUT ROWID;')
|
||||
for index,(key,words)in enumerate(sorted(entries.items()),1):
|
||||
db.execute('INSERT INTO entries VALUES(?,?,?,?)',(index,key,''.join(map9[ch]for ch in key),''.join(map14[ch]for ch in key)))
|
||||
db.executemany('INSERT INTO words VALUES(?,?,?,?)',[(index,ordinal,word,frequencies.get(word,0))for ordinal,word in enumerate(words)])
|
||||
db.executescript('CREATE INDEX entries_code9 ON entries(code9);CREATE INDEX entries_code14 ON entries(code14);ANALYZE;PRAGMA user_version=1;')
|
||||
db.commit()
|
||||
restored={}
|
||||
for key,word in db.execute('SELECT e.pinyin,w.word FROM entries e JOIN words w ON w.entry=e.id ORDER BY e.pinyin,w.ordinal'):restored.setdefault(key,[]).append(word)
|
||||
assert restored==entries,'Full dictionary round-trip mismatch'
|
||||
assert db.execute('PRAGMA integrity_check').fetchone()[0]=='ok';db.close();temporary.replace(target)
|
||||
report={'keys':len(entries),'candidates':sum(map(len,entries.values())),'longest_word':max(len(word)for words in entries.values()for word in words),'sha256':hashlib.sha256(target.read_bytes()).hexdigest(),'bytes':target.stat().st_size,'round_trip':True,'source_files':{name:hashlib.sha256(data).hexdigest()for name,data in sorted(files.items())},'jieba_sha256':hashlib.sha256(frequency_path.read_bytes()).hexdigest()}
|
||||
(APP/'keyboard/src/main/assets/dictionary-manifest.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
|
||||
print(json.dumps({k:v for k,v in report.items()if k!='source_files'},ensure_ascii=False))
|
||||
|
||||
if __name__=='__main__':
|
||||
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--rtos-source',type=pathlib.Path)
|
||||
args=parser.parse_args();rtos=args.rtos_source
|
||||
if rtos is None:
|
||||
rtos=next((p for p in [APP.parent/'com.sydxky.rtos.inputmethod',APP.parent/'RTOS/com.sydxky.rtos.inputmethod']if p.exists()),None)
|
||||
assert rtos is not None,'Provide the licensed RTOS source with --rtos-source';build(rtos)
|
||||
在新工单中引用
屏蔽一个用户