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