-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconverter.py
More file actions
260 lines (217 loc) · 8.34 KB
/
Copy pathconverter.py
File metadata and controls
260 lines (217 loc) · 8.34 KB
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
import argparse
import csv
import json
import re
import shutil
import zipfile
from pathlib import Path
from datetime import datetime
def parse_args():
parser = argparse.ArgumentParser(
description="Convert Hanzi CSV to Yomitan Dictionary"
)
parser.add_argument(
"csv_file",
type=Path,
help="Path to the .csv file"
)
parser.add_argument(
"output",
type=Path,
help="Path to output directory (Dictionary will be built here)"
)
parser.add_argument(
"--chunk-size",
type=int,
default=10_000,
help="Maximum number of entries per kanji_bank file (default: 10000)"
)
parser.add_argument(
"--title",
type=str,
default="Hanzi Dict VN",
help="Dictionary title in index.json"
)
parser.add_argument(
"--no-zip",
action="store_true",
help="Do not create zip file (leave output directory only)"
)
args = parser.parse_args()
return args
def chunk_entries(entries, chunk_size=10_000):
if chunk_size <= 0:
raise ValueError("chunk_size must be a positive integer")
for i in range(0, len(entries), chunk_size):
yield entries[i:i + chunk_size]
def write_kanji_banks(chunks, output_dir):
for idx, chunk in enumerate(chunks, start=1):
filename = f"kanji_bank_{idx}.json"
path = output_dir / filename
with path.open("w", encoding="utf-8") as f:
json.dump(chunk, f, ensure_ascii=False, indent=0, separators=(',', ':'))
def build_index_json(output_dir, title="Hanzi Dictionary"):
"""
Build index.json for output dictionary.
"""
index_data = {
"title": title,
"format": 3,
"revision": datetime.now().strftime("%Y.%m.%d"),
"sequenced": False,
"author": "",
"url": "",
"description": "Từ điển Hán Tự (Hanzi) cho Yomitan"
}
with (output_dir / "index.json").open("w", encoding="utf-8") as f:
json.dump(index_data, f, ensure_ascii=False, indent=2)
def zip_output_directory(output_dir, zip_path):
print(f"Zipping to {zip_path}...")
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for path in output_dir.iterdir():
if path.is_file():
zf.write(path, arcname=path.name)
def process_definition_nom(def_nom_str):
"""
Tách chuỗi defNom chứa <LI> thành list các nghĩa.
Ví dụ: "<LI>nghĩa 1<LI>nghĩa 2" -> ["nghĩa 1", "nghĩa 2"]
"""
if not def_nom_str:
return []
# Tách bằng regex <LI> (case insensitive)
parts = re.split(r'<LI>', def_nom_str, flags=re.IGNORECASE)
# Loại bỏ các chuỗi rỗng và khoảng trắng thừa
return [p.strip() for p in parts if p.strip()]
def clean_han_viet_definition(text):
if not text:
return []
# 1. Thay thế ◎Như: thành Ví dụ:
text = text.replace("◎Như:", "Ví dụ:")
# 2. Xử lý các cụm <ach> liên tiếp để gộp vào (Hán Tự) -
def process_ach_group(match):
full_block = match.group(0)
# Tìm tất cả ký tự nằm trong các thẻ <ach> của cụm này
chars = re.findall(r'">([^<]+)</a>', full_block)
if chars:
return f"({''.join(chars)}) -"
return ""
# Regex tìm các thẻ <ach> đứng sát nhau hoặc cách nhau bởi khoảng trắng
ach_pattern = r'(<ach>[^>]+>[^<]+</a>(?:\s*<ach>[^>]+>[^<]+</a>)*)'
text = re.sub(ach_pattern, process_ach_group, text)
# 3. Loại bỏ tất cả các thẻ HTML còn lại (<B>, <a>,...)
text = re.sub(r'<[^>]+>', '', text)
# 4. Tách các định nghĩa theo từ loại (Danh, Tính, Động, Trạng thanh...)
# Regex hỗ trợ tiếng Việt có dấu cho từ loại
parts = re.split(r'(?=\([A-Z][a-zđ]+\))', text)
cleaned_parts = []
for p in parts:
item = p.strip()
if not item: continue
# Làm sạch khoảng trắng và dấu chấm thừa
item = re.sub(r'\s+', ' ', item)
item = re.sub(r'\.+$', '', item)
# Đảm bảo format chuẩn cho cụm "(Hán Tự) - Nghĩa"
item = item.replace(")-", ") -")
cleaned_parts.append(item)
return cleaned_parts
def create_yomitan_entry(character, row):
"""
Tạo một entry cấu trúc mảng cho Yomitan từ một dòng CSV và ký tự cụ thể.
Structure: [char, on, kun, tags, meanings, stats]
"""
# 1. Readings (Han Viet)
# Xóa dấu phẩy, thay bằng khoảng trắng
han_viet = row.get('hanViet', '').replace(',', ' ').strip()
# Chuẩn hóa khoảng trắng (nếu có nhiều dấu cách liền nhau)
han_viet = re.sub(r'\s+', ' ', han_viet)
# 2. Meanings
meanings = []
# Xử lý defNom
def_nom = row.get('defNom', '')
if def_nom:
meanings.extend(process_definition_nom(def_nom))
# Xử lý defHanViet
def_han_viet = row.get('defHanViet', '')
if def_han_viet:
cleaned_definitions = clean_han_viet_definition(def_han_viet)
meanings.extend(cleaned_definitions)
# Nếu không có nghĩa nào, để trống
if not meanings:
meanings.append("...")
# 3. Stats (Metadata)
stats = {}
if row.get('totalStrokes'):
stats['Strokes'] = row['totalStrokes']
if row.get('pinyin'):
stats['Pinyin'] = row['pinyin']
# Yomitan Kanji Structure
# [character, onyomi, kunyomi, tags, meanings, stats]
# Lưu ý: HanViet ở đây mình map vào Onyomi (vị trí số 2) vì nó tương đương âm Hán
return [
character, # Kanji
han_viet, # Onyomi (Han Viet)
"", # Kunyomi (Empty)
"", # Tags (Empty)
meanings, # Meanings Array
stats # Stats Object
]
def write_tag_bank(output_dir):
"""
Tạo file tag_bank_1.json để hiển thị tiêu đề đẹp cho các trường thông tin.
"""
tag_data = [
["Strokes", "misc", 0, "Tổng nét", 0],
["Pinyin", "misc", 0, "Pinyin", 0]
]
path = output_dir / "tag_bank_1.json"
with path.open("w", encoding="utf-8") as f:
json.dump(tag_data, f, ensure_ascii=False, indent=2)
def main():
args = parse_args()
# Tạo thư mục output nếu chưa tồn tại
if not args.output.exists():
args.output.mkdir(parents=True)
else:
# Clean folder cũ để tránh file rác
for item in args.output.iterdir():
if item.is_file():
item.unlink()
print(f"Reading CSV: {args.csv_file}")
all_entries = []
try:
with args.csv_file.open('r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
# Lấy các ký tự Hán tự
primary_char = row.get('unicode', '').strip()
simp_char = row.get('simplifiedVariant', '').strip()
# Logic: Tạo entry cho unicode
if primary_char:
entry = create_yomitan_entry(primary_char, row)
all_entries.append(entry)
# Logic: Tạo entry cho simplifiedVariant (nếu khác primary)
if simp_char and simp_char != primary_char:
entry = create_yomitan_entry(simp_char, row)
all_entries.append(entry)
except Exception as e:
print(f"Error reading CSV: {e}")
return
print(f"Total entries generated: {len(all_entries)}")
# Chunking và ghi file kanji_bank
chunks = list(chunk_entries(all_entries, args.chunk_size))
print(f"Writing {len(chunks)} bank files...")
write_kanji_banks(chunks, args.output)
# Tạo index.json
print("Writing index.json...")
build_index_json(args.output, title=args.title)
write_tag_bank(args.output)
# Zip file
if not args.no_zip:
# Tên file zip sẽ là tên folder output + .zip
zip_filename = args.output.with_suffix(".zip")
zip_output_directory(args.output, zip_filename)
print(f"Done! Imported dictionary created at: {zip_filename}")
else:
print(f"Done! Dictionary files are in: {args.output}")
if __name__ == "__main__":
main()