|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 import os |
|
4 import io |
|
5 import sys |
|
6 import codecs |
|
7 import tempfile |
|
8 import shutil |
|
9 import signal |
|
10 |
|
11 import gadict |
|
12 |
|
13 |
|
14 FINAME = None |
|
15 FONAME = None |
|
16 if len(sys.argv) >= 2: |
|
17 FINAME = sys.argv[1] |
|
18 if len(sys.argv) >= 3: |
|
19 FONAME = sys.argv[2] |
|
20 LANGS = None |
|
21 if len(sys.argv) >= 4: |
|
22 LANGS = set(sys.argv[3].split(",")) |
|
23 |
|
24 FIN = io.open(FINAME, mode='r', buffering=1, encoding="utf-8") |
|
25 |
|
26 PARSER = gadict.Parser() |
|
27 try: |
|
28 DOM = PARSER.parse(FIN) |
|
29 except gadict.ParseException as ex: |
|
30 sys.stdout.write("{:s}{:s}\n".format(FINAME, repr(ex))) |
|
31 if __debug__: |
|
32 import traceback |
|
33 traceback.print_exc() |
|
34 exit(1) |
|
35 finally: |
|
36 FIN.close() |
|
37 |
|
38 def cleanup(collection, tmpdir): |
|
39 if collection: |
|
40 collection.close() |
|
41 if tmpdir: |
|
42 shutil.rmtree(tmpdir, ignore_errors=True) |
|
43 |
|
44 # signal.signal(signal.SIGINT, lambda signal, frame: cleanup()) |
|
45 |
|
46 |
|
47 if FONAME is None: |
|
48 raise Exception('Missing output file name') |
|
49 # Looks like anki libs change working directory to media directory of current deck |
|
50 # Therefore absolute path should be stored before creating temporary deck |
|
51 FONAME = os.path.abspath(FONAME) |
|
52 FBASENAME, _ = os.path.splitext(os.path.basename(FONAME)) |
|
53 TMPDIR = tempfile.mkdtemp(dir = os.path.dirname(FONAME)) |
|
54 |
|
55 import hashlib |
|
56 |
|
57 import anki |
|
58 from anki.exporting import AnkiPackageExporter |
|
59 |
|
60 |
|
61 class AnkiDbBuilder: |
|
62 |
|
63 def __init__(self, tmpdir, name): |
|
64 self.tmpdir = tmpdir |
|
65 self.name = name |
|
66 |
|
67 self.collection = collection = anki.Collection(os.path.join(self.tmpdir, 'collection.anki2')) |
|
68 |
|
69 deck_id = collection.decks.id(self.name + "_deck") |
|
70 deck = collection.decks.get(deck_id) |
|
71 |
|
72 model = collection.models.new(self.name + "_model") |
|
73 model['tags'].append(self.name + "_tag") |
|
74 model['did'] = deck_id |
|
75 model['css'] = """ |
|
76 .card { |
|
77 font-family: arial; |
|
78 font-size: 20px; |
|
79 text-align: center; |
|
80 color: black; |
|
81 background-color: white; |
|
82 } |
|
83 span.headword { |
|
84 font-style: italic; |
|
85 } |
|
86 .pron { |
|
87 color: magenta; |
|
88 } |
|
89 .pos { |
|
90 color: green; |
|
91 font-style: italic; |
|
92 } |
|
93 .lang { |
|
94 color: red; |
|
95 font-style: italic; |
|
96 } |
|
97 .ant { |
|
98 color: red; |
|
99 } |
|
100 .syn { |
|
101 color: blue; |
|
102 } |
|
103 """ |
|
104 |
|
105 collection.models.addField(model, collection.models.newField('From')) |
|
106 collection.models.addField(model, collection.models.newField('To')) |
|
107 |
|
108 tmpl = collection.models.newTemplate('From -> To') |
|
109 tmpl['qfmt'] = '<div class="from">{{From}}</div>' |
|
110 tmpl['afmt'] = '{{FrontSide}}\n\n<hr id=answer>\n\n<div class="to">{{To}}</div>' |
|
111 collection.models.addTemplate(model, tmpl) |
|
112 |
|
113 # Equivalent of: |
|
114 # collection.models.add(model) |
|
115 # without setting auto-generated ID. It is essential to keep model['id'] |
|
116 # unchanged between upgrades or notes will be skipped!! |
|
117 model['id'] = int(hashlib.sha1(self.name).hexdigest(), 16) % (2**63) |
|
118 collection.models.update(model) |
|
119 collection.models.setCurrent(model) |
|
120 collection.models.save(model) |
|
121 |
|
122 def guid(self, type_, headword): |
|
123 """ |
|
124 :type_ used to generate different notes from same headword |
|
125 """ |
|
126 h = hashlib.md5(":".join((self.name, type_, headword))) |
|
127 return h.hexdigest() |
|
128 |
|
129 def add_note(self, type_, id_, from_, to_): |
|
130 note = self.collection.newNote() |
|
131 note['From'] = from_ |
|
132 # print(from_) |
|
133 note['To'] = to_ |
|
134 # print(to_) |
|
135 note.guid = self.guid(type_, id_) |
|
136 self.collection.addNote(note) |
|
137 |
|
138 def export(self, fname): |
|
139 export = AnkiPackageExporter(self.collection) |
|
140 export.exportInto(fname) |
|
141 |
|
142 def close(self): |
|
143 self.collection.close() |
|
144 |
|
145 try: |
|
146 builder = AnkiDbBuilder(TMPDIR, FBASENAME) |
|
147 |
|
148 for (headwords, translations) in DOM[1:]: |
|
149 identity = headwords[0].headword |
|
150 buf = [] |
|
151 for hw in headwords: |
|
152 buf.append("<div clsas='headword'>") |
|
153 buf.append("<span clsas='headword'>") |
|
154 buf.append(hw.headword) |
|
155 buf.append("</span>") |
|
156 if hw.pron is not None: |
|
157 buf.append(" <span class='pron'>[") |
|
158 buf.append(hw.pron) |
|
159 buf.append("]</span>") |
|
160 if len(hw.attrs) > 0: |
|
161 l = [u"«"+x+u"»" for x in hw.attrs] |
|
162 l.sort() |
|
163 buf.append("<span class='attrs'>") |
|
164 buf.append(", ".join(l)) |
|
165 buf.append("</span>") |
|
166 buf.append("</div>") |
|
167 direct_from = "".join(buf) |
|
168 buf = [] |
|
169 for sense in translations: |
|
170 buf.append("<div class='sense'>") |
|
171 if sense.pos: |
|
172 buf.append("<span class='pos'>") |
|
173 buf.append(sense.pos) |
|
174 buf.append("</span>") |
|
175 if sense.ant_list and len(sense.ant_list) > 0: |
|
176 buf.append("<span class='ant'>ant: ") |
|
177 buf.append("; ".join(["{"+s+"}" for s in sense.ant_list])) |
|
178 buf.append("</span>") |
|
179 if sense.syn_list and len(sense.syn_list) > 0: |
|
180 buf.append("<span class='syn'>syn: ") |
|
181 buf.append("; ".join(["{"+s+"}" for s in sense.syn_list])) |
|
182 buf.append("</span>") |
|
183 if len(sense.tr_list) > 1: |
|
184 for (lang, tr) in sense.tr_list: |
|
185 buf.append("<div class='sense'>") |
|
186 buf.append(" <span class='lang'>") |
|
187 buf.append(lang) |
|
188 buf.append("</span>") |
|
189 buf.append(" <span class='tr'>") |
|
190 buf.append(tr) |
|
191 buf.append("</span>") |
|
192 buf.append("</div>") |
|
193 else: |
|
194 for (lang, tr) in sense.tr_list: |
|
195 buf.append(" <span class='lang'>") |
|
196 buf.append(lang) |
|
197 buf.append("</span>") |
|
198 buf.append(" <span class='tr'>") |
|
199 buf.append(tr) |
|
200 buf.append("</span>") |
|
201 direct_to = "".join(buf) |
|
202 builder.add_note("en->tr", identity, direct_from, direct_to) |
|
203 builder.add_note("tr->en", identity, direct_to, direct_from) |
|
204 |
|
205 builder.export(FONAME) |
|
206 finally: |
|
207 builder.close() |
|
208 shutil.rmtree(TMPDIR, ignore_errors=True) |