usfmtc 0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
usfmtc/grammar.py ADDED
@@ -0,0 +1,461 @@
1
+ #!/usr/bin/python3
2
+
3
+ import re
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ usfmns = "{http://usfm.bible/parse/2023}"
9
+ relaxns = "{http://relaxng.org/ns/structure/1.0}"
10
+
11
+ class AttribElement:
12
+ def __init__(self, tag, attrib):
13
+ self.tag = tag
14
+ self.attrib = attrib
15
+
16
+
17
+ class UsfmGrammarParser:
18
+
19
+ _manies = {
20
+ '+': 'append_plus',
21
+ '?': 'append_opt',
22
+ '*': 'append_star',
23
+ }
24
+
25
+ def __init__(self, doc, backend, flattenre=True, hrefprefix=""):
26
+ self.aliases = {}
27
+ self.back = backend
28
+ self.doc = doc
29
+ self.flattenre = flattenre
30
+ self.hrefprefix = hrefprefix or ""
31
+ self.defines = {}
32
+ self.vars = {}
33
+ self.parse()
34
+ self.idcount = 1
35
+ self.groups = []
36
+ self.ids = {}
37
+ self.groupings = []
38
+ self.elementlist = []
39
+ self.nodes = []
40
+
41
+ def parseRef(self, name, flattens=set(), flattenall=True):
42
+ self.flattens = flattens
43
+ self.flattenall = flattenall
44
+ if flattenall:
45
+ self.defines = {}
46
+ self.idcount = 1
47
+ self.ids = {}
48
+ e = self.get_ref(name)
49
+ self.curr = self.back.append_seq()
50
+ if e is None:
51
+ print("Can't find {}".format(name))
52
+ return None
53
+ self.groups = [name]
54
+ self.attribnodes = [None]
55
+ res = self.proc_children(e, self.curr, False)
56
+ self.groups.pop()
57
+ return res
58
+
59
+ def get_ref(self, name):
60
+ res = self.doc.find('.//{}define[@name="{}"]'.format(relaxns, name))
61
+ if res is None:
62
+ raise IndexError(f"{name} not in grammar")
63
+ return res
64
+
65
+ def parse(self):
66
+ for e in self.doc.getroot():
67
+ if e.tag == usfmns+"alias":
68
+ self.alias(e, None)
69
+ elif e.tag == usfmns + "terminal":
70
+ self.terminal(e, None)
71
+ elif e.tag == relaxns+"start":
72
+ self.start = e
73
+
74
+ def expandvars(self, s):
75
+ if not self.flattenre:
76
+ return s
77
+ if s is None:
78
+ return None
79
+ return re.sub(r"\$\{(.*?)\}", lambda m:self.expandvars(self.vars.get(m.group(1), "'"+m.group(1)+"'")[1:-1]), s)
80
+
81
+ def getorder(self, e):
82
+ if f'{usfmns}order' not in e.attrib and \
83
+ e.tag in (f'{relaxns}optional', '{relaxns}oneOrMore', '{relaxns}zeroOrMore', '{relaxns}group') and \
84
+ len(e) == 1 and e[0].tag == f"{relaxns}group" and f'{usfmns}order' in e[0].attrib:
85
+ return int(e[0].get(f'{usfmns}order', 0))
86
+ else:
87
+ return int(e.get(f'{usfmns}order', 0))
88
+
89
+ def proc_children(self, e, res, skip=True, start=0, parent=None, index=0, **kw):
90
+ for i, c in enumerate(sorted(e, key=lambda x:self.getorder(x))):
91
+ if i < start:
92
+ continue
93
+ t = re.sub(r"^\{.*\}", "", c.tag)
94
+ if c.tag.startswith(usfmns) and t in self.aliases:
95
+ base = self.aliases[t]
96
+ newe = c.makeelement(base.tag, {k: v for k, v in base.attrib.items()})
97
+ for k, v in c.attrib.items():
98
+ newe.set(k, self.expandvars(v))
99
+ if c.text is not None and len(c.text) and hasattr(base, 'default'):
100
+ newe.set(base.default, c.text)
101
+ c = newe
102
+ t = re.sub(r"^\{.*\}", "", c.tag)
103
+ if (m := c.get(f"{usfmns}many", None)) is not None:
104
+ newres = getattr(self.back, self._manies[m])(res, **kw)
105
+ if len(res.propmap):
106
+ newres.propmap = res.propmap.copy()
107
+ res = newres
108
+ if (not skip or t in ("match", "matchpair")) and c.get(f"{usfmns}ignore", "false") not in ("true", "1"):
109
+ fn = getattr(self, t, None)
110
+ if fn is not None:
111
+ if fn(c, res, skip=skip, index=i, parent=e, **kw) is None:
112
+ break
113
+ if getattr(res, 'children', None) is not None:
114
+ for c in res.children:
115
+ res.propmap.update(getattr(c, 'propmap', {}))
116
+ return res
117
+
118
+ def push_element(self, val):
119
+ self.nodes.append(val)
120
+
121
+ def pop_element(self):
122
+ return self.nodes.pop()
123
+
124
+ # ---- Tag methods ---
125
+
126
+ def alias(self, e, res, **kw):
127
+ n = e.get('name', None)
128
+ if n is None:
129
+ return res
130
+ if n.startswith("usfm:"):
131
+ n = n[5:]
132
+ a = AttribElement(e[0].tag, e[0].attrib)
133
+ d = e.get('def', None)
134
+ if d is not None:
135
+ a.default = d
136
+ self.aliases[n] = a
137
+ return res
138
+
139
+ def terminal(self, e, res, **kw):
140
+ n = e.get('name', None)
141
+ v = e.get('value', None)
142
+ val = self.expandvars(v)
143
+ if n is None or v is None:
144
+ return res
145
+ self.vars[n] = val
146
+ self.back.add_terminal(n, val, res)
147
+
148
+ def attribute(self, e, res, **kw):
149
+ name = e.findtext(f"./{relaxns}name") or "*"
150
+ parms = {}
151
+ nover = e.get(f'{usfmns}fallback-from', None)
152
+ if nover is not None:
153
+ parms['fallback-from'] = nover
154
+ res = self.back.attrib_start(self, e, res, name, **parms)
155
+ g = int(e.get(f"{usfmns}grouping", 0))
156
+ self.groupings.append(g)
157
+ self.proc_children(e, res)
158
+ res = self.back.attrib_end(self, e, res)
159
+ self.groupings.pop()
160
+ return res
161
+
162
+ def element(self, e, res, **kw):
163
+ name = e.findtext(f"./{relaxns}name")
164
+ res = self.back.elem_start(self, e, res, name)
165
+ g = int(e.get(f"{usfmns}grouping", 0))
166
+ self.groupings.append(g)
167
+ self.elementlist.append(res)
168
+ self.proc_children(e, res, False)
169
+ res = self.back.elem_end(self, e, res)
170
+ self.elementlist.pop()
171
+ self.groupings.pop()
172
+ return res
173
+
174
+ def match(self, e, res, **kw):
175
+ attrib = e.get('{usfmns}attrib', None)
176
+ if attrib is not None:
177
+ print(f"{attrib=}")
178
+ res = self.back.attrib_start(self, e, res, attrib)
179
+ res = self.back.append_seq(res, forced=e.get(f"{usfmns}seq", "false") in ("true", "1"))
180
+ cont = True
181
+ for a in ('before', 'match', 'after'):
182
+ v = self.expandvars(e.get(a, None))
183
+ if a == "match":
184
+ dump = e.get('dump', 'false') in ("true", "1")
185
+ capture = None
186
+ if (d := e.get('matchid', None)) is not None:
187
+ lastres = res
188
+ k = (self.groups[-1], d)
189
+ if k not in self.ids:
190
+ self.ids[k] = self.idcount
191
+ self.idcount += 1
192
+ capture = self.ids[k]
193
+ res = self.back.append_group(capture, res)
194
+ logger.debug(f"{k} := {capture}")
195
+ res = self.back.append_seq(res, capture=capture, dump=dump)
196
+ dump = False
197
+ if v is not None and len(v) and v != "''":
198
+ if a == "match" and v != v.upper() and v[0] not in "'/":
199
+ v = "'{}'".format(v)
200
+ res = self.back.match(v, res, dump=dump, alt=self.expandvars(e.get(a+"out", None)))
201
+ elif (r := e.get('matchref', None)) not in (None, ""):
202
+ for j in range(len(self.groups) - 1, -1, -1):
203
+ k = (self.groups[j], r)
204
+ if k in self.ids:
205
+ logger.debug(f"{k} = {self.ids[k]} {self.groups}")
206
+ self.back.backref(self.ids[k], res, dump=dump)
207
+ break
208
+ else:
209
+ self.proc_children(kw.get('parent', e), res, False, start=kw.get('index', 0)+1)
210
+ cont = False
211
+ if d is not None:
212
+ res = lastres
213
+ elif v is not None:
214
+ res = self.back.match(v, res, dump=True, alt=self.expandvars(e.get(a+"out", None)))
215
+ # this needs more work: @ahead
216
+ if attrib is not None:
217
+ res = self.back.attrib_end(self, e, res)
218
+ return res if cont else None
219
+
220
+ def matchpair(self, e, res, **kw):
221
+ fb = self.expandvars(e.get('fallback', None))
222
+ if fb is not None:
223
+ res = self.back.append_or(res)
224
+ self.back.match(fb, res)
225
+ res = self.back.append_seq(res, forced=e.get(f"{usfmns}seq", "false") in ("true", "1"))
226
+ for a in ('before', 'first', 'between', 'second', 'after'):
227
+ v = self.expandvars(e.get(a, None))
228
+ if v is not None:
229
+ res = self.back.match(v, res, dump=a not in ('first', 'second'))
230
+ return res
231
+
232
+ def optional(self, e, res, **kw):
233
+ res = self.back.append_opt(res)
234
+ return self.proc_children(e, res, **kw)
235
+
236
+ def group(self, e, res, **kw):
237
+ res = self.back.append_seq(res, forced=e.get(f"{usfmns}seq", "false") in ("true", "1"))
238
+ return self.proc_children(e, res, **kw)
239
+
240
+ def choice(self, e, res, **kw):
241
+ res = self.back.append_or(res, groupby=self.groupings[-1] if len(self.groupings) else 0)
242
+ return self.proc_children(e, res, **kw)
243
+
244
+ def oneOrMore(self, e, res, **kw):
245
+ res = self.back.append_plus(res)
246
+ return self.proc_children(e, res, **kw)
247
+
248
+ def zeroOrMore(self, e, res, **kw):
249
+ res = self.back.append_star(res)
250
+ return self.proc_children(e, res, **kw)
251
+
252
+ def interleave(self, e, res, **kw):
253
+ res = self.back.append_interleave(res)
254
+ return self.proc_children(e, res, **kw)
255
+
256
+ def value(self, e, res, **kw):
257
+ ekw = {}
258
+ pv = e.get(f'{usfmns}propval', None)
259
+ if pv is not None:
260
+ pe = self.elementlist[-1]
261
+ pe.propmap[e.text] = pv
262
+ res.propmap[e.text] = pv
263
+ pl = e.get(f'{usfmns}href', None)
264
+ if pl is not None:
265
+ ekw['href'] = self.hrefprefix + pl.format(e.text)
266
+ return self.back.match('"'+e.text+'"', res, **ekw)
267
+
268
+ def ref(self, e, res, **kw):
269
+ if e is None and 'name' in kw:
270
+ n = kw['name']
271
+ del kw['name']
272
+ else:
273
+ alt = e.get(f'{usfmns}alt', None)
274
+ if alt is not None:
275
+ res = self.back.append_or(res)
276
+ self.ref(None, res, name=alt, **kw)
277
+ n = e.get('name', None)
278
+ if n is None:
279
+ return res
280
+ elif n in self.flattens:
281
+ r = self.get_ref(n)
282
+ self.groups.append(n)
283
+ res = self.proc_children(r, res, **kw)
284
+ self.groups.pop()
285
+ return res
286
+ elif "+"+n in self.flattens:
287
+ return res
288
+ elif n not in self.defines and self.flattenall:
289
+ r = self.get_ref(n)
290
+ newres = self.back.add_define(n, r, context=res, box=self.flattenall, **kw)
291
+ self.defines[n] = newres
292
+ self.groups.append(n)
293
+ res = self.proc_children(r, newres, **kw)
294
+ self.groups.pop()
295
+ return res
296
+ if self.flattenall and len(self.elementlist):
297
+ self.elementlist[-1].propmap.update(self.defines[n].propmap)
298
+ return self.back.ref(n, res, **kw)
299
+
300
+ def data(self, e, res, **kw):
301
+ t = e.get('type', None)
302
+ if t == 'string':
303
+ usfmp = e.find(f'./{usfmns}pattern')
304
+ if usfmp is None:
305
+ reg = e.findtext('./{}param/[@name="pattern"]'.format(relaxns))
306
+ if reg is not None:
307
+ reg = "/" + reg + "/"
308
+ else:
309
+ reg = self.expandvars(usfmp.get('name', None))
310
+ if reg is not None:
311
+ res = self.back.match(reg, res)
312
+ else:
313
+ res = self.back.match('TEXT', res)
314
+ elif t == "integer":
315
+ res = self.back.match("/[0-9]+/", res)
316
+ elif t == "boolean":
317
+ res = self.back.match('BOOL', res)
318
+ return res
319
+
320
+ def properties(self, e, res, **kw):
321
+ res.properties.update(e.attrib)
322
+ return res
323
+
324
+ def text(self, e, res, **kw):
325
+ return self.proc_children(e, res, **kw)
326
+
327
+ class XmlGrammarParser:
328
+ def __init__(self, doc, backend, flattenre=True):
329
+ self.flattenre = flattenre
330
+ self.doc = doc
331
+ self.back = backend
332
+ self.groupings = []
333
+ self.elements = {}
334
+
335
+ def expandvars(self, s):
336
+ if not self.flattenre:
337
+ return s
338
+ if s is None:
339
+ return None
340
+ return re.sub(r"\$\{(.*?)\}", lambda m:self.vars.get(m.group(1), "'"+m.group(1)+"'")[1:-1], s)
341
+
342
+ def parseRef(self, name, flattens=set(), flattenall=True):
343
+ self.flattens = flattens
344
+ self.flattenall = flattenall
345
+ e = self.get_ref(name)
346
+ self.curr = self.back.append_seq()
347
+ if e is None:
348
+ print("Can't find {}".format(name))
349
+ return None
350
+ self.elements = {}
351
+ res = self.proc_children(e, self.curr)
352
+ return res
353
+
354
+ def get_ref(self, name):
355
+ return self.doc.find(f'.//{relaxns}define[@name="{name}"]')
356
+
357
+ def parse(self):
358
+ pass
359
+
360
+ def proc_children(self, e, res):
361
+ for c in e:
362
+ t = re.sub(r"^\{.*\}", "", c.tag)
363
+ fn = getattr(self, t, None)
364
+ if fn is not None:
365
+ if fn(c, res, parent=e) is None:
366
+ break
367
+ return res
368
+
369
+ def attribute(self, e, res, **kw):
370
+ name = e.findtext(f"./{relaxns}name") or "*"
371
+ res = self.back.attrib_start(self, e, res, name)
372
+ g = int(e.get(f"{usfmns}grouping", 0))
373
+ self.groupings.append(g)
374
+ self.proc_children(e, res)
375
+ self.groupings.pop()
376
+ res = self.back.attrib_end(self, e, res)
377
+ return res
378
+
379
+ def element(self, e, res, **kw):
380
+ name = e.findtext(f"./{relaxns}name")
381
+ if name in self.elements:
382
+ res = self.elements[name]
383
+ else:
384
+ res = self.back.elem_start(self, e, res, name)
385
+ g = int(e.get(f"{usfmns}grouping", 0))
386
+ self.groupings.append(g)
387
+ self.proc_children(e, res)
388
+ self.groupings.pop()
389
+ self.elements[name] = res
390
+ res = self.back.elem_end(self, e, res)
391
+ return res
392
+
393
+ def optional(self, e, res, **kw):
394
+ res = self.back.append_opt(res)
395
+ return self.proc_children(e, res)
396
+
397
+ def group(self, e, res, **kw):
398
+ res = self.back.append_seq(res, forced=e.get(f"{usfmns}seq", "false") in ("true", "1"))
399
+ return self.proc_children(e, res)
400
+
401
+ def choice(self, e, res, **kw):
402
+ res = self.back.append_or(res, groupby=self.groupings[-1] if len(self.groupings) else 0)
403
+ return self.proc_children(e, res)
404
+
405
+ def oneOrMore(self, e, res, **kw):
406
+ res = self.back.append_plus(res)
407
+ return self.proc_children(e, res)
408
+
409
+ def zeroOrMore(self, e, res, **kw):
410
+ res = self.back.append_star(res)
411
+ return self.proc_children(e, res)
412
+
413
+ def interleave(self, e, res, **kw):
414
+ res = self.back.append_interleave(res)
415
+ return self.proc_children(e, res)
416
+
417
+ def value(self, e, res, **kw):
418
+ return self.back.match('"'+e.text+'"', res)
419
+
420
+ def ref(self, e, res, **kw):
421
+ n = e.get('name', None)
422
+ if n is None:
423
+ return res
424
+ elif n in self.flattens:
425
+ r = self.get_ref(n)
426
+ return self.proc_children(r, res)
427
+ elif "+"+n in self.flattens:
428
+ return res
429
+ else:
430
+ return self.back.ref(n, res)
431
+
432
+ def data(self, e, res, **kw):
433
+ t = e.get('type', None)
434
+ if t == 'string':
435
+ usfmp = e.find(f'./{usfmns}pattern')
436
+ if usfmp is None:
437
+ reg = e.findtext('./{}param/[@name="pattern"]'.format(relaxns))
438
+ else:
439
+ reg = self.expandvars(e.get('name', None))
440
+ if reg is not None:
441
+ res = self.back.match("/"+reg+"/", res)
442
+ else:
443
+ res = self.back.match('TEXT', res)
444
+ elif t == "integer":
445
+ res = self.back.match("/[0-9]+/", res)
446
+ elif t == "boolean":
447
+ res = self.back.match('BOOL', res)
448
+ return res
449
+
450
+ def text(self, e, res, **kw):
451
+ if e.tag.startswith(usfmns):
452
+ return res
453
+ return self.back.match("TEXT", res)
454
+
455
+ def empty(self, e, res, **kw):
456
+ return self.back.match("EMPTY", res)
457
+
458
+ def properties(self, e, res, **kw):
459
+ res.properties.update(e.attrib)
460
+ return res
461
+