Mercurial > hg > lxmldump
annotate lxmldump.py @ 28:3442b8700da7
Comments.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 25 May 2021 11:42:44 +0300 |
parents | d77ab8a300b1 |
children | f91ef7d7615b |
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 ### | |
23 | 8 ### Released / distributed under 3-clause BSD license |
9 ### (see file "COPYING" for more information) | |
10 ### | |
4 | 11 ### Python 3.7+ required! |
0 | 12 ### |
13 import sys | |
14 import signal | |
15 import re | |
16 from pathlib import Path | |
17 import xml.etree.ElementTree as xmlET | |
5
274b2091137c
Some more work on cleaning this up.
Matti Hamalainen <ccr@tnsp.org>
parents:
4
diff
changeset
|
18 import unicodedata |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
19 import argparse |
0 | 20 |
21 assert sys.version_info >= (3, 7) | |
22 | |
23 | |
24 ### | |
25 ### Default settings | |
26 ### | |
25
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
27 # Operation modes |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
28 PKK_MODE_NORMAL = 0 |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
29 PKK_MODE_DUMP = 1 |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
30 PKK_MODE_XML = 2 |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
31 |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
32 |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
33 # Element annotation mappings |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
34 pkk_element_annotation_map = { |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
35 "Fragment" : { |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
36 PKK_MODE_NORMAL: ["<", ">"], |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
37 }, |
7 | 38 } |
39 | |
40 | |
28 | 41 # List of words in kks1/ useful for debugging, option -p |
7 | 42 pkk_debug_list = [ |
43 "ahas", | |
44 "ahavakkaine", | |
45 "ahavakala", | |
46 "ahavakoittuo", | |
47 "ahvaliha", | |
9 | 48 "aloilleh", |
18 | 49 "hanjahtoakseh", |
19 | 50 "akkalisto", |
7 | 51 ] |
52 | |
53 | |
21 | 54 |
0 | 55 ### |
56 ### Misc. helper functions, etc | |
57 ### | |
58 def pkk_cleanup(): | |
59 return 0 | |
60 | |
61 | |
13 | 62 ## 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
|
63 def pkk_print(smsg): |
12 | 64 try: |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
65 if pkk_cfg.normalize: |
12 | 66 sys.stdout.write(unicodedata.normalize("NFC", smsg)) |
67 else: | |
68 sys.stdout.write(smsg) | |
69 | |
70 except (BrokenPipeError, IOError) as e: | |
71 sys.stderr.close() | |
72 | |
0 | 73 |
13 | 74 ## Print string with indentation |
7 | 75 def pkk_printi(indent, smsg): |
22 | 76 pkk_print((" " * pkk_cfg.indent * indent) + smsg) |
7 | 77 |
0 | 78 |
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 | |
28 | 92 ## Annotate given string with prefix and suffix based on tag |
26 | 93 def pkk_str_annotate(mtag, mstr): |
25
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
94 if pkk_cfg.annotate and mtag in pkk_element_annotation_map: |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
95 if pkk_cfg.mode in pkk_element_annotation_map[mtag]: |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
96 mmode = pkk_cfg.mode |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
97 else: |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
98 mmode = PKK_MODE_NORMAL |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
99 |
26 | 100 return pkk_element_annotation_map[mtag][mmode][0] + mstr + pkk_element_annotation_map[mtag][mmode][1] |
25
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
101 else: |
26 | 102 return mstr |
25
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
103 |
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
104 |
13 | 105 ## Clean string by removing tabs and newlines |
10 | 106 def pkk_str_clean(mstr): |
107 return re.sub(r'[\n\r\t]', '', mstr) | |
108 | |
109 | |
28 | 110 ## Format a "Ptr" node as text |
9 | 111 def pkk_ptr_to_text(pnode): |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
112 phref = pnode.attrib["{http://www.w3.org/TR/xlink}href"] |
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
113 ptext = ("".join(pnode.itertext())).strip() |
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
114 |
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
115 return f"<PTR:{phref}>{ptext}</PTR>" |
9 | 116 |
117 | |
13 | 118 ## Get text inside a given node |
16 | 119 def pkk_node_to_text(lnode): |
7 | 120 stmp = "" |
121 for pnode in lnode.iter(): | |
9 | 122 if pnode.tag == "Ptr": |
123 stmp += pkk_ptr_to_text(pnode) | |
124 else: | |
125 if isinstance(pnode.text, str): | |
25
8a6738f67106
Factor string annotation into separate function.
Matti Hamalainen <ccr@tnsp.org>
parents:
24
diff
changeset
|
126 stmp += pkk_str_annotate(pnode.tag, pkk_str_clean(pnode.text).strip()) |
7 | 127 |
9 | 128 if isinstance(pnode.tail, str): |
10 | 129 stmp += pkk_str_clean(pnode.tail) |
7 | 130 |
131 return stmp.strip() | |
132 | |
133 | |
13 | 134 ## Simple recursive dump starting at given node |
7 | 135 def pkk_dump_recursive(indent, lnode): |
136 if lnode.tag in ["Example"]: | |
16 | 137 stmp = pkk_node_to_text(lnode) |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
138 pkk_printi(indent, f"{lnode.tag} \"{stmp}\"\n") |
6 | 139 else: |
7 | 140 if isinstance(lnode.text, str): |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
141 textstr = pkk_str_clean(lnode.text).strip() |
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
142 if textstr != "": |
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
143 textstr = " \""+ textstr +"\"" |
7 | 144 else: |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
145 textstr = "" |
6 | 146 |
147 if len(lnode.attrib) > 0: | |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
148 attrstr = " "+ str(lnode.attrib) |
6 | 149 else: |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
150 attrstr = "" |
6 | 151 |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
152 pkk_printi(indent, f"{lnode.tag}{attrstr}{textstr}\n") |
6 | 153 for qnode in lnode.findall("./*"): |
7 | 154 pkk_dump_recursive(indent + 1, qnode) |
6 | 155 |
156 | |
17 | 157 ## Output item(s) under given node with given format string |
13 | 158 def pkk_output_subs_fmt(indent, dnode, dsub, dname, dfmt): |
7 | 159 for qnode in dnode.findall(dsub): |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
160 pkk_printi(indent, dfmt.format(nname=dname, ntext=pkk_node_to_text(qnode))) |
7 | 161 |
17 | 162 ## Output item(s) under given node with a prefixed name string |
13 | 163 def pkk_output_subs_prefix(indent, dnode, dsub, dname): |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
164 pkk_output_subs_fmt(indent, dnode, dsub, dname, "{nname} \"{ntext}\"\n") |
7 | 165 |
166 | |
17 | 167 ## Output a main "Headword" or "Sense" node under it |
7 | 168 def pkk_output_sense(indent, dnode): |
17 | 169 # Search form and definition |
13 | 170 pkk_output_subs_prefix(indent, dnode, "./SearchForm", "srch") |
171 pkk_output_subs_prefix(indent, dnode, "./Definition", "defn") | |
7 | 172 |
17 | 173 # Examples |
7 | 174 for wnode in dnode.findall("./ExampleBlock/ExampleCtn"): |
16 | 175 sstr = pkk_node_to_text(wnode.find("./Example")) |
8 | 176 lstr = "" |
6 | 177 |
24
8c8e8e4504bb
Remove verbosity option as it is not really used.
Matti Hamalainen <ccr@tnsp.org>
parents:
23
diff
changeset
|
178 ltmp = [] |
8c8e8e4504bb
Remove verbosity option as it is not really used.
Matti Hamalainen <ccr@tnsp.org>
parents:
23
diff
changeset
|
179 for qnode in wnode.findall("./FreeTopic[@type='levikki']/GeographicalUsage"): |
8c8e8e4504bb
Remove verbosity option as it is not really used.
Matti Hamalainen <ccr@tnsp.org>
parents:
23
diff
changeset
|
180 ltmp.append("{} [{}]".format(pkk_node_to_text(qnode), qnode.attrib["class"])) |
8 | 181 |
24
8c8e8e4504bb
Remove verbosity option as it is not really used.
Matti Hamalainen <ccr@tnsp.org>
parents:
23
diff
changeset
|
182 if len(ltmp) > 0: |
8c8e8e4504bb
Remove verbosity option as it is not really used.
Matti Hamalainen <ccr@tnsp.org>
parents:
23
diff
changeset
|
183 lstr = " ({})".format(", ".join(ltmp)) |
7 | 184 |
185 pkk_printi(indent + 1, "{} \"{}\"{}\n".format("exmp", sstr, lstr)) | |
186 | |
187 | |
17 | 188 ## Output one "DictionaryEntry" node |
7 | 189 def pkk_output_node(indent, dnode): |
6 | 190 |
7 | 191 for wnode in dnode.findall("./HeadwordCtn"): |
18 | 192 # Create list with grammatical attributes (noun, verb, etc.) |
193 tmpl = [] | |
194 for pnode in wnode.findall("./PartOfSpeechCtn/PartOfSpeech"): | |
195 tmpl.append(pnode.attrib["freeValue"]) | |
196 | |
197 for pnode in wnode.findall("./GrammaticalNote"): | |
19 | 198 tmpl.append(pkk_node_to_text(pnode)) |
18 | 199 |
200 # Remove duplicates and sort the list | |
201 tmpl = list(set(tmpl)) | |
202 tmpl.sort(reverse=False, key=lambda attr: (attr, len(attr))) | |
203 | |
204 # Print the headword and attributes if any | |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
205 pkk_output_subs_fmt(indent, wnode, "./Headword", "", "\"{ntext}\"") |
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
206 |
18 | 207 if len(tmpl) > 0: |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
208 pkk_print(" ({nlist})".format(nlist=" ; ".join(tmpl))) |
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
209 |
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
210 pkk_print("\n") |
18 | 211 |
212 # Print main "sense" | |
7 | 213 pkk_output_sense(indent + 1, wnode) |
6 | 214 |
18 | 215 # Print any other "senses" |
7 | 216 index = 1 |
217 for wnode in dnode.findall("./SenseGrp"): | |
27
d77ab8a300b1
Use Python 3.6/3.7 format strings where we can.
Matti Hamalainen <ccr@tnsp.org>
parents:
26
diff
changeset
|
218 pkk_printi(indent + 1, f"sense #{index}\n") |
7 | 219 pkk_output_sense(indent + 2, wnode) |
220 index += 1 | |
6 | 221 |
222 | |
0 | 223 ### |
224 ### Main program starts | |
225 ### | |
226 signal.signal(signal.SIGINT, pkk_signal_handler) | |
227 | |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
228 optparser = argparse.ArgumentParser( |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
229 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
|
230 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
|
231 epilog="\n\n" |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
232 ) |
0 | 233 |
22 | 234 optparser.add_argument("filenames", |
235 type=str, action="extend", nargs="*", | |
236 metavar="filename", | |
237 help="XML filename(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("-d", "--dump", |
22 | 240 dest="mode", |
21 | 241 action="store_const", const=PKK_MODE_DUMP, default=PKK_MODE_NORMAL, |
22 | 242 help="output as simple dump") |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
243 |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
244 optparser.add_argument("-x", "--xml", |
22 | 245 dest="mode", |
21 | 246 action="store_const", const=PKK_MODE_XML, |
22 | 247 help="output as XML") |
0 | 248 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
249 optparser.add_argument("-n", "--normalize", |
22 | 250 dest="normalize", |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
251 action="store_const", const=True, default=False, |
22 | 252 help="output NFC normalized Unicode") |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
253 |
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
254 optparser.add_argument("-a", "--annotate", |
22 | 255 dest="annotate", |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
256 action="store_const", const=True, default=False, |
22 | 257 help="annotate strings") |
0 | 258 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
259 optparser.add_argument("-p", "--debug", |
22 | 260 dest="debug", action="store_const", const=True, default=False, |
261 help=argparse.SUPPRESS) | |
262 | |
263 optparser.add_argument("-i", "--indent", | |
264 dest="indent", | |
265 type=int, choices=range(0, 32), default=4, | |
266 metavar="n", | |
267 help='indent output by <n> characters (default: %(default)s)') | |
0 | 268 |
269 | |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
270 ### 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
|
271 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
|
272 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
|
273 optparser.print_help() |
0 | 274 sys.exit(0) |
275 | |
276 | |
6 | 277 ### 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
|
278 for filename in pkk_cfg.filenames: |
0 | 279 # Parse XML file into element tree |
280 try: | |
281 uxml = xmlET.parse(filename) | |
282 except Exception as e: | |
283 pkk_fatal(u"SVG/XML parsing failed: {0}".format(str(e))) | |
284 | |
285 # Dump output | |
286 try: | |
287 xroot = uxml.getroot() | |
288 for dnode in xroot.findall("./DictionaryEntry"): | |
7 | 289 |
20
f274504eafd0
Use Python argparse module instead of custom self-rolled argument parser.
Matti Hamalainen <ccr@tnsp.org>
parents:
19
diff
changeset
|
290 if pkk_cfg.debug and dnode.attrib["identifier"] not in pkk_debug_list: |
7 | 291 continue |
292 | |
21 | 293 if pkk_cfg.mode == PKK_MODE_NORMAL: |
19 | 294 try: |
295 pkk_output_node(0, dnode) | |
296 except Exception as e: | |
297 pkk_dump_recursive(0, dnode) | |
298 print(str(e)) | |
299 sys.exit(0) | |
21 | 300 elif pkk_cfg.mode == PKK_MODE_DUMP: |
7 | 301 pkk_dump_recursive(0, dnode) |
21 | 302 elif pkk_cfg.mode == PKK_MODE_XML: |
10 | 303 pkk_print(str(xmlET.tostring(dnode, encoding="utf8")) + "\n") |
0 | 304 else: |
10 | 305 pkk_fatal("Invalid operation mode?") |
7 | 306 |
307 print("\n") | |
0 | 308 |
309 except (BrokenPipeError, IOError) as e: | |
310 sys.stderr.close() | |
311 sys.exit(1) | |
312 | |
313 pkk_cleanup() | |
314 sys.exit(0) |