Mercurial > hg > lxmldump
annotate lxmldump.py @ 19:7c6eb57798bd
Ja niin.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 11 May 2021 16:15:16 +0300 |
parents | ff959de0f6c8 |
children | f274504eafd0 |
rev | line source |
---|---|
0 | 1 #!/usr/bin/python3 -B |
2 # coding=utf-8 | |
3 ### | |
4 | 4 ### lxmldump - Dump ISO/FDIS 1951 XML file data |
5 ### Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> | |
6 ### (C) Copyright 2021 Tecnic Software productions (TNSP) | |
7 ### | |
8 ### Python 3.7+ required! | |
0 | 9 ### |
10 import sys | |
11 import signal | |
12 import re | |
13 from pathlib import Path | |
14 import xml.etree.ElementTree as xmlET | |
5
274b2091137c
Some more work on cleaning this up.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
15 import unicodedata |
0 | 16 |
17 assert sys.version_info >= (3, 7) | |
18 | |
19 | |
20 ### | |
21 ### Default settings | |
22 ### | |
23 pkk_cfg = { | |
8 | 24 "verbosity": 3, |
25 "annotate": False, | |
10 | 26 "mode": 0, |
5
274b2091137c
Some more work on cleaning this up.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
27 "normalize": False, |
7 | 28 "debug": False, |
0 | 29 } |
30 | |
31 | |
7 | 32 pkk_str_fmap = { |
33 "Fragment" : ["<", ">"], | |
34 } | |
35 | |
36 | |
37 pkk_debug_list = [ | |
38 "ahas", | |
39 "ahavakkaine", | |
40 "ahavakala", | |
41 "ahavakoittuo", | |
42 "ahvaliha", | |
9 | 43 "aloilleh", |
18 | 44 "hanjahtoakseh", |
19 | 45 "akkalisto", |
7 | 46 ] |
47 | |
48 | |
0 | 49 ### |
50 ### Misc. helper functions, etc | |
51 ### | |
52 def pkk_cleanup(): | |
53 return 0 | |
54 | |
55 | |
13 | 56 ## Print string to stdout using normalized Unicode if enabled |
5
274b2091137c
Some more work on cleaning this up.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
57 def pkk_print(smsg): |
12 | 58 try: |
59 if pkk_cfg["normalize"]: | |
60 sys.stdout.write(unicodedata.normalize("NFC", smsg)) | |
61 else: | |
62 sys.stdout.write(smsg) | |
63 | |
64 except (BrokenPipeError, IOError) as e: | |
65 sys.stderr.close() | |
66 | |
0 | 67 |
13 | 68 ## Print string with indentation |
7 | 69 def pkk_printi(indent, smsg): |
70 pkk_print((" " * indent) + smsg) | |
71 | |
0 | 72 |
13 | 73 ## Check value against current verbosity level |
8 | 74 def pkk_verbosity(lvl): |
75 return pkk_cfg["verbosity"] >= lvl | |
76 | |
77 | |
0 | 78 ## Fatal error handler |
79 def pkk_fatal(smsg): | |
80 print(u"ERROR: "+ smsg) | |
81 sys.exit(1) | |
82 | |
83 | |
84 ## Handler for SIGINT signals | |
85 def pkk_signal_handler(signal, frame): | |
86 pkk_cleanup() | |
87 print(u"\nQuitting due to SIGINT / Ctrl+C!") | |
88 sys.exit(1) | |
89 | |
90 | |
13 | 91 ## Clean string by removing tabs and newlines |
10 | 92 def pkk_str_clean(mstr): |
93 return re.sub(r'[\n\r\t]', '', mstr) | |
94 | |
95 | |
13 | 96 ## Format "Ptr" node as text |
9 | 97 def pkk_ptr_to_text(pnode): |
16 | 98 return "<PTR:{}>{}</PTR>".format( |
10 | 99 pnode.attrib["{http://www.w3.org/TR/xlink}href"], |
100 ("".join(pnode.itertext())).strip()) | |
9 | 101 |
102 | |
13 | 103 ## Get text inside a given node |
16 | 104 def pkk_node_to_text(lnode): |
7 | 105 stmp = "" |
106 for pnode in lnode.iter(): | |
9 | 107 if pnode.tag == "Ptr": |
108 stmp += pkk_ptr_to_text(pnode) | |
109 else: | |
110 if isinstance(pnode.text, str): | |
10 | 111 ptext = pkk_str_clean(pnode.text).strip() |
9 | 112 if pkk_cfg["annotate"] and isinstance(pnode.tag, str) and pnode.tag in pkk_str_fmap: |
113 stmp += pkk_str_fmap[pnode.tag][0] + ptext + pkk_str_fmap[pnode.tag][1] | |
114 else: | |
115 stmp += ptext | |
7 | 116 |
9 | 117 if isinstance(pnode.tail, str): |
10 | 118 stmp += pkk_str_clean(pnode.tail) |
7 | 119 |
120 return stmp.strip() | |
121 | |
122 | |
13 | 123 ## Simple recursive dump starting at given node |
7 | 124 def pkk_dump_recursive(indent, lnode): |
125 if lnode.tag in ["Example"]: | |
16 | 126 stmp = pkk_node_to_text(lnode) |
9 | 127 pkk_printi(indent, "{} \"{}\"\n".format(lnode.tag, stmp)) |
6 | 128 else: |
7 | 129 if isinstance(lnode.text, str): |
10 | 130 stmp = pkk_str_clean(lnode.text).strip() |
7 | 131 if stmp != "": |
132 stmp = " \""+ stmp +"\"" | |
133 else: | |
134 stmp = "" | |
6 | 135 |
136 if len(lnode.attrib) > 0: | |
10 | 137 atmp = " "+ str(lnode.attrib) |
6 | 138 else: |
139 atmp = "" | |
140 | |
7 | 141 pkk_printi(indent, "{}{}{}\n".format(lnode.tag, atmp, stmp)) |
6 | 142 for qnode in lnode.findall("./*"): |
7 | 143 pkk_dump_recursive(indent + 1, qnode) |
6 | 144 |
145 | |
17 | 146 ## Output item(s) under given node with given format string |
13 | 147 def pkk_output_subs_fmt(indent, dnode, dsub, dname, dfmt): |
7 | 148 for qnode in dnode.findall(dsub): |
16 | 149 pkk_printi(indent, dfmt.format(dname, pkk_node_to_text(qnode))) |
7 | 150 |
17 | 151 ## Output item(s) under given node with a prefixed name string |
13 | 152 def pkk_output_subs_prefix(indent, dnode, dsub, dname): |
153 pkk_output_subs_fmt(indent, dnode, dsub, dname, "{0} \"{1}\"\n") | |
7 | 154 |
155 | |
17 | 156 ## Output a main "Headword" or "Sense" node under it |
7 | 157 def pkk_output_sense(indent, dnode): |
17 | 158 # Search form and definition |
13 | 159 pkk_output_subs_prefix(indent, dnode, "./SearchForm", "srch") |
160 pkk_output_subs_prefix(indent, dnode, "./Definition", "defn") | |
7 | 161 |
17 | 162 # Examples |
7 | 163 for wnode in dnode.findall("./ExampleBlock/ExampleCtn"): |
16 | 164 sstr = pkk_node_to_text(wnode.find("./Example")) |
8 | 165 lstr = "" |
6 | 166 |
8 | 167 if pkk_verbosity(1): |
168 ltmp = [] | |
169 for qnode in wnode.findall("./FreeTopic[@type='levikki']/GeographicalUsage"): | |
16 | 170 ltmp.append("{} [{}]".format(pkk_node_to_text(qnode), qnode.attrib["class"])) |
8 | 171 |
172 if len(ltmp) > 0: | |
173 lstr = " ({})".format(", ".join(ltmp)) | |
7 | 174 |
175 pkk_printi(indent + 1, "{} \"{}\"{}\n".format("exmp", sstr, lstr)) | |
176 | |
177 | |
17 | 178 ## Output one "DictionaryEntry" node |
7 | 179 def pkk_output_node(indent, dnode): |
6 | 180 |
7 | 181 for wnode in dnode.findall("./HeadwordCtn"): |
18 | 182 # Create list with grammatical attributes (noun, verb, etc.) |
183 tmpl = [] | |
184 for pnode in wnode.findall("./PartOfSpeechCtn/PartOfSpeech"): | |
185 tmpl.append(pnode.attrib["freeValue"]) | |
186 | |
187 for pnode in wnode.findall("./GrammaticalNote"): | |
19 | 188 tmpl.append(pkk_node_to_text(pnode)) |
18 | 189 |
190 # Remove duplicates and sort the list | |
191 tmpl = list(set(tmpl)) | |
192 tmpl.sort(reverse=False, key=lambda attr: (attr, len(attr))) | |
193 | |
194 # Print the headword and attributes if any | |
195 pkk_output_subs_fmt(indent, wnode, "./Headword", "", "\"{1}\"") | |
196 if len(tmpl) > 0: | |
197 pkk_print(" ({})\n".format(" ; ".join(tmpl))) | |
198 else: | |
199 pkk_print("\n") | |
200 | |
201 # Print main "sense" | |
7 | 202 pkk_output_sense(indent + 1, wnode) |
6 | 203 |
18 | 204 # Print any other "senses" |
7 | 205 index = 1 |
206 for wnode in dnode.findall("./SenseGrp"): | |
8 | 207 pkk_printi(indent + 1, "sense #{}\n".format(index)) |
7 | 208 pkk_output_sense(indent + 2, wnode) |
209 index += 1 | |
6 | 210 |
211 | |
0 | 212 ### |
213 ### Main program starts | |
214 ### | |
215 signal.signal(signal.SIGINT, pkk_signal_handler) | |
216 | |
217 | |
218 ### Check if we have arguments | |
219 pkk_show_help = False | |
220 pkk_filenames = [] | |
221 argc = 1 | |
222 while argc < len(sys.argv): | |
223 arg = sys.argv[argc] | |
224 | |
225 needs_param = False | |
226 if argc + 1 < len(sys.argv): | |
227 param = sys.argv[argc + 1] | |
228 else: | |
229 param = None | |
230 | |
231 # Check for option type arg | |
232 if arg[0:1] == "-": | |
233 oarg = arg | |
234 arg = arg.lstrip("-") | |
235 | |
236 if arg == "help" or arg == "h": | |
237 pkk_show_help = True | |
238 elif arg == "dump" or arg == "d": | |
10 | 239 pkk_cfg["mode"] = 1 |
240 elif arg == "xml" or arg == "x": | |
241 pkk_cfg["mode"] = 2 | |
5
274b2091137c
Some more work on cleaning this up.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
242 elif arg == "normalize" or arg == "n": |
274b2091137c
Some more work on cleaning this up.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
243 pkk_cfg["normalize"] = True |
8 | 244 elif arg == "annotate" or arg == "a": |
245 pkk_cfg["annotate"] = True | |
7 | 246 elif arg == "p": |
247 pkk_cfg["debug"] = True | |
8 | 248 elif arg == "verbosity" or arg == "v": |
249 needs_param = True | |
250 pkk_cfg["verbosity"] = param | |
0 | 251 else: |
252 pkk_fatal(u"Invalid option argument '{0}'.".format(oarg)) | |
253 | |
254 if needs_param and param == None: | |
255 pkk_fatal(u"Option '{0}' requires an argument.".format(oarg)) | |
256 else: | |
257 # Non-option argument | |
258 pkk_filenames.append(arg) | |
259 | |
260 if needs_param: | |
261 argc += 2 | |
262 else: | |
263 argc += 1 | |
264 | |
265 | |
266 ### Show help if requested | |
267 if pkk_show_help or len(pkk_filenames) == 0: | |
268 print(u"lxmldump - Dump ISO/FDIS 1951 XML file data") | |
269 print(u"Usage: {0} <options> <input xml file(s)>". | |
270 format(str(Path(sys.argv[0]).name))) | |
271 print(u"") | |
272 print(u" --help Show this help") | |
15 | 273 print(u" -d, --dump Output as simple dump") |
274 print(u" -x, --xml Output as XML") | |
6 | 275 print(u" -n, --normalize Output NFC normalized Unicode") |
8 | 276 print(u" -a, --annotate Annotate strings") |
277 print(u" -v, --verbosity <n> Set verbosity level (0 - 3)") | |
0 | 278 print(u"") |
279 sys.exit(0) | |
280 | |
281 | |
8 | 282 ### Validate settings |
283 try: | |
284 pkk_cfg["verbosity"] = int(pkk_cfg["verbosity"]) | |
285 except Exception as e: | |
286 pkk_fatal(u"Verbosity level is not a valid integer.") | |
287 if pkk_cfg["verbosity"] < 0 or pkk_cfg["verbosity"] > 3: | |
288 pkk_fatal(u"Invalid verbosity level value {0}.".format(pkk_cfg["verbosity"])) | |
289 | |
290 | |
6 | 291 ### Handle each input file |
0 | 292 for filename in pkk_filenames: |
293 # Parse XML file into element tree | |
294 try: | |
295 uxml = xmlET.parse(filename) | |
296 except Exception as e: | |
297 pkk_fatal(u"SVG/XML parsing failed: {0}".format(str(e))) | |
298 | |
299 # Dump output | |
300 try: | |
301 xroot = uxml.getroot() | |
302 for dnode in xroot.findall("./DictionaryEntry"): | |
7 | 303 |
304 if pkk_cfg["debug"] and dnode.attrib["identifier"] not in pkk_debug_list: | |
305 continue | |
306 | |
10 | 307 if pkk_cfg["mode"] == 0: |
19 | 308 try: |
309 pkk_output_node(0, dnode) | |
310 except Exception as e: | |
311 pkk_dump_recursive(0, dnode) | |
312 print(str(e)) | |
313 sys.exit(0) | |
10 | 314 elif pkk_cfg["mode"] == 1: |
7 | 315 pkk_dump_recursive(0, dnode) |
10 | 316 elif pkk_cfg["mode"] == 2: |
317 pkk_print(str(xmlET.tostring(dnode, encoding="utf8")) + "\n") | |
0 | 318 else: |
10 | 319 pkk_fatal("Invalid operation mode?") |
7 | 320 |
321 print("\n") | |
0 | 322 |
323 except (BrokenPipeError, IOError) as e: | |
324 sys.stderr.close() | |
325 sys.exit(1) | |
326 | |
327 pkk_cleanup() | |
328 sys.exit(0) |