Mercurial > hg > lxmldump
annotate lxmldump.py @ 20:f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 11 May 2021 17:13:38 +0300 |
parents | 7c6eb57798bd |
children | 7ef08e05a5bf |
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 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
16 import argparse |
0 | 17 |
18 assert sys.version_info >= (3, 7) | |
19 | |
20 | |
21 ### | |
22 ### Default settings | |
23 ### | |
7 | 24 pkk_str_fmap = { |
25 "Fragment" : ["<", ">"], | |
26 } | |
27 | |
28 | |
29 pkk_debug_list = [ | |
30 "ahas", | |
31 "ahavakkaine", | |
32 "ahavakala", | |
33 "ahavakoittuo", | |
34 "ahvaliha", | |
9 | 35 "aloilleh", |
18 | 36 "hanjahtoakseh", |
19 | 37 "akkalisto", |
7 | 38 ] |
39 | |
40 | |
0 | 41 ### |
42 ### Misc. helper functions, etc | |
43 ### | |
44 def pkk_cleanup(): | |
45 return 0 | |
46 | |
47 | |
13 | 48 ## 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
|
49 def pkk_print(smsg): |
12 | 50 try: |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
51 if pkk_cfg.normalize: |
12 | 52 sys.stdout.write(unicodedata.normalize("NFC", smsg)) |
53 else: | |
54 sys.stdout.write(smsg) | |
55 | |
56 except (BrokenPipeError, IOError) as e: | |
57 sys.stderr.close() | |
58 | |
0 | 59 |
13 | 60 ## Print string with indentation |
7 | 61 def pkk_printi(indent, smsg): |
62 pkk_print((" " * indent) + smsg) | |
63 | |
0 | 64 |
13 | 65 ## Check value against current verbosity level |
8 | 66 def pkk_verbosity(lvl): |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
67 return pkk_cfg.verbosity >= lvl |
8 | 68 |
69 | |
0 | 70 ## Fatal error handler |
71 def pkk_fatal(smsg): | |
72 print(u"ERROR: "+ smsg) | |
73 sys.exit(1) | |
74 | |
75 | |
76 ## Handler for SIGINT signals | |
77 def pkk_signal_handler(signal, frame): | |
78 pkk_cleanup() | |
79 print(u"\nQuitting due to SIGINT / Ctrl+C!") | |
80 sys.exit(1) | |
81 | |
82 | |
13 | 83 ## Clean string by removing tabs and newlines |
10 | 84 def pkk_str_clean(mstr): |
85 return re.sub(r'[\n\r\t]', '', mstr) | |
86 | |
87 | |
13 | 88 ## Format "Ptr" node as text |
9 | 89 def pkk_ptr_to_text(pnode): |
16 | 90 return "<PTR:{}>{}</PTR>".format( |
10 | 91 pnode.attrib["{http://www.w3.org/TR/xlink}href"], |
92 ("".join(pnode.itertext())).strip()) | |
9 | 93 |
94 | |
13 | 95 ## Get text inside a given node |
16 | 96 def pkk_node_to_text(lnode): |
7 | 97 stmp = "" |
98 for pnode in lnode.iter(): | |
9 | 99 if pnode.tag == "Ptr": |
100 stmp += pkk_ptr_to_text(pnode) | |
101 else: | |
102 if isinstance(pnode.text, str): | |
10 | 103 ptext = pkk_str_clean(pnode.text).strip() |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
104 if pkk_cfg.annotate and isinstance(pnode.tag, str) and pnode.tag in pkk_str_fmap: |
9 | 105 stmp += pkk_str_fmap[pnode.tag][0] + ptext + pkk_str_fmap[pnode.tag][1] |
106 else: | |
107 stmp += ptext | |
7 | 108 |
9 | 109 if isinstance(pnode.tail, str): |
10 | 110 stmp += pkk_str_clean(pnode.tail) |
7 | 111 |
112 return stmp.strip() | |
113 | |
114 | |
13 | 115 ## Simple recursive dump starting at given node |
7 | 116 def pkk_dump_recursive(indent, lnode): |
117 if lnode.tag in ["Example"]: | |
16 | 118 stmp = pkk_node_to_text(lnode) |
9 | 119 pkk_printi(indent, "{} \"{}\"\n".format(lnode.tag, stmp)) |
6 | 120 else: |
7 | 121 if isinstance(lnode.text, str): |
10 | 122 stmp = pkk_str_clean(lnode.text).strip() |
7 | 123 if stmp != "": |
124 stmp = " \""+ stmp +"\"" | |
125 else: | |
126 stmp = "" | |
6 | 127 |
128 if len(lnode.attrib) > 0: | |
10 | 129 atmp = " "+ str(lnode.attrib) |
6 | 130 else: |
131 atmp = "" | |
132 | |
7 | 133 pkk_printi(indent, "{}{}{}\n".format(lnode.tag, atmp, stmp)) |
6 | 134 for qnode in lnode.findall("./*"): |
7 | 135 pkk_dump_recursive(indent + 1, qnode) |
6 | 136 |
137 | |
17 | 138 ## Output item(s) under given node with given format string |
13 | 139 def pkk_output_subs_fmt(indent, dnode, dsub, dname, dfmt): |
7 | 140 for qnode in dnode.findall(dsub): |
16 | 141 pkk_printi(indent, dfmt.format(dname, pkk_node_to_text(qnode))) |
7 | 142 |
17 | 143 ## Output item(s) under given node with a prefixed name string |
13 | 144 def pkk_output_subs_prefix(indent, dnode, dsub, dname): |
145 pkk_output_subs_fmt(indent, dnode, dsub, dname, "{0} \"{1}\"\n") | |
7 | 146 |
147 | |
17 | 148 ## Output a main "Headword" or "Sense" node under it |
7 | 149 def pkk_output_sense(indent, dnode): |
17 | 150 # Search form and definition |
13 | 151 pkk_output_subs_prefix(indent, dnode, "./SearchForm", "srch") |
152 pkk_output_subs_prefix(indent, dnode, "./Definition", "defn") | |
7 | 153 |
17 | 154 # Examples |
7 | 155 for wnode in dnode.findall("./ExampleBlock/ExampleCtn"): |
16 | 156 sstr = pkk_node_to_text(wnode.find("./Example")) |
8 | 157 lstr = "" |
6 | 158 |
8 | 159 if pkk_verbosity(1): |
160 ltmp = [] | |
161 for qnode in wnode.findall("./FreeTopic[@type='levikki']/GeographicalUsage"): | |
16 | 162 ltmp.append("{} [{}]".format(pkk_node_to_text(qnode), qnode.attrib["class"])) |
8 | 163 |
164 if len(ltmp) > 0: | |
165 lstr = " ({})".format(", ".join(ltmp)) | |
7 | 166 |
167 pkk_printi(indent + 1, "{} \"{}\"{}\n".format("exmp", sstr, lstr)) | |
168 | |
169 | |
17 | 170 ## Output one "DictionaryEntry" node |
7 | 171 def pkk_output_node(indent, dnode): |
6 | 172 |
7 | 173 for wnode in dnode.findall("./HeadwordCtn"): |
18 | 174 # Create list with grammatical attributes (noun, verb, etc.) |
175 tmpl = [] | |
176 for pnode in wnode.findall("./PartOfSpeechCtn/PartOfSpeech"): | |
177 tmpl.append(pnode.attrib["freeValue"]) | |
178 | |
179 for pnode in wnode.findall("./GrammaticalNote"): | |
19 | 180 tmpl.append(pkk_node_to_text(pnode)) |
18 | 181 |
182 # Remove duplicates and sort the list | |
183 tmpl = list(set(tmpl)) | |
184 tmpl.sort(reverse=False, key=lambda attr: (attr, len(attr))) | |
185 | |
186 # Print the headword and attributes if any | |
187 pkk_output_subs_fmt(indent, wnode, "./Headword", "", "\"{1}\"") | |
188 if len(tmpl) > 0: | |
189 pkk_print(" ({})\n".format(" ; ".join(tmpl))) | |
190 else: | |
191 pkk_print("\n") | |
192 | |
193 # Print main "sense" | |
7 | 194 pkk_output_sense(indent + 1, wnode) |
6 | 195 |
18 | 196 # Print any other "senses" |
7 | 197 index = 1 |
198 for wnode in dnode.findall("./SenseGrp"): | |
8 | 199 pkk_printi(indent + 1, "sense #{}\n".format(index)) |
7 | 200 pkk_output_sense(indent + 2, wnode) |
201 index += 1 | |
6 | 202 |
203 | |
0 | 204 ### |
205 ### Main program starts | |
206 ### | |
207 signal.signal(signal.SIGINT, pkk_signal_handler) | |
208 | |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
209 optparser = argparse.ArgumentParser( |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
210 description="lxmldump - Dump ISO/FDIS 1951 XML file data", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
211 usage="%(prog)s [options] <input xml file(s)>", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
212 epilog="\n\n" |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
213 ) |
0 | 214 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
215 optparser.add_argument("filenames", action="extend", nargs="*", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
216 type=str, metavar="filename", help="XML filename(s)") |
0 | 217 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
218 optparser.add_argument("-d", "--dump", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
219 action="store_const", const=1, default=0, |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
220 dest="mode", help="output as simple dump") |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
221 |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
222 optparser.add_argument("-x", "--xml", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
223 action="store_const", const=2, |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
224 dest="mode", help="output as XML") |
0 | 225 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
226 optparser.add_argument("-n", "--normalize", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
227 action="store_const", const=True, default=False, |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
228 dest="normalize", help="output NFC normalized Unicode") |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
229 |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
230 optparser.add_argument("-a", "--annotate", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
231 action="store_const", const=True, default=False, |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
232 dest="annotate", help="annotate strings") |
0 | 233 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
234 optparser.add_argument("-v", "--verbosity", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
235 type=int, choices=range(0,4), default=3, |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
236 metavar="n", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
237 dest="verbosity", help='set verbosity level (0-3, default: %(default)s)') |
0 | 238 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
239 optparser.add_argument("-p", "--debug", |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
240 action="store_const", const=True, default=False, |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
241 dest="debug", help=argparse.SUPPRESS) |
0 | 242 |
243 | |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
244 ### Show help if needed |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
245 pkk_cfg = optparser.parse_args() |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
246 if len(pkk_cfg.filenames) == 0: |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
247 optparser.print_help() |
0 | 248 sys.exit(0) |
249 | |
250 | |
6 | 251 ### Handle each input file |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
252 for filename in pkk_cfg.filenames: |
0 | 253 # Parse XML file into element tree |
254 try: | |
255 uxml = xmlET.parse(filename) | |
256 except Exception as e: | |
257 pkk_fatal(u"SVG/XML parsing failed: {0}".format(str(e))) | |
258 | |
259 # Dump output | |
260 try: | |
261 xroot = uxml.getroot() | |
262 for dnode in xroot.findall("./DictionaryEntry"): | |
7 | 263 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
264 if pkk_cfg.debug and dnode.attrib["identifier"] not in pkk_debug_list: |
7 | 265 continue |
266 | |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
267 if pkk_cfg.mode == 0: |
19 | 268 try: |
269 pkk_output_node(0, dnode) | |
270 except Exception as e: | |
271 pkk_dump_recursive(0, dnode) | |
272 print(str(e)) | |
273 sys.exit(0) | |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
274 elif pkk_cfg.mode == 1: |
7 | 275 pkk_dump_recursive(0, dnode) |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
276 elif pkk_cfg.mode == 2: |
10 | 277 pkk_print(str(xmlET.tostring(dnode, encoding="utf8")) + "\n") |
0 | 278 else: |
10 | 279 pkk_fatal("Invalid operation mode?") |
7 | 280 |
281 print("\n") | |
0 | 282 |
283 except (BrokenPipeError, IOError) as e: | |
284 sys.stderr.close() | |
285 sys.exit(1) | |
286 | |
287 pkk_cleanup() | |
288 sys.exit(0) |