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/__init__.py +379 -0
- usfmtc/diagrams.py +247 -0
- usfmtc/extension.py +79 -0
- usfmtc/grammar.py +461 -0
- usfmtc/parser.py +300 -0
- usfmtc/railroad.py +1320 -0
- usfmtc/reference.py +302 -0
- usfmtc/rnc2rng/__init__.py +13 -0
- usfmtc/rnc2rng/parser.py +741 -0
- usfmtc/rnc2rng/rnctree.py +17 -0
- usfmtc/rnc2rng/serializer.py +255 -0
- usfmtc/sfmparser.py +345 -0
- usfmtc/usfmgenerate.py +156 -0
- usfmtc/usfmparser.py +590 -0
- usfmtc/usjproc.py +88 -0
- usfmtc/usx.rng +2182 -0
- usfmtc/usxgrammar.py +36 -0
- usfmtc/usxmodel.py +520 -0
- usfmtc/usxparser.py +568 -0
- usfmtc/xmlutils.py +139 -0
- usfmtc-0.2.dist-info/LICENSE.md +20 -0
- usfmtc-0.2.dist-info/METADATA +61 -0
- usfmtc-0.2.dist-info/RECORD +26 -0
- usfmtc-0.2.dist-info/WHEEL +5 -0
- usfmtc-0.2.dist-info/entry_points.txt +2 -0
- usfmtc-0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# Compatibility API for rnc2rng 1.0
|
|
3
|
+
from . import parser, serializer
|
|
4
|
+
|
|
5
|
+
class Tree(object):
|
|
6
|
+
|
|
7
|
+
def __init__(self, root):
|
|
8
|
+
self.root = root
|
|
9
|
+
|
|
10
|
+
def toxml(self):
|
|
11
|
+
return serializer.XMLSerializer().toxml(self.root)
|
|
12
|
+
|
|
13
|
+
def token_list(src):
|
|
14
|
+
return parser.lex(src)
|
|
15
|
+
|
|
16
|
+
def make_nodetree(tokens):
|
|
17
|
+
return Tree(parser.parser.parse(tokens, parser.State()))
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# Convert an RELAX NG compact syntax schema to a Node tree
|
|
2
|
+
# This file released to the Public Domain by David Mertz
|
|
3
|
+
from . import parser
|
|
4
|
+
from usfmtc.rnc2rng.parser import (
|
|
5
|
+
ANNO_ATTR, ANNOTATION, ANY, ASSIGN, ATTR, CHOICE, DATATAG, DATATYPES,
|
|
6
|
+
DEFAULT_NS, DEFINE, DIV, DOCUMENTATION, ELEM, EMPTY, EXCEPT, GRAMMAR,
|
|
7
|
+
GROUP, INTERLEAVE, LIST, LITERAL, MAYBE, MIXED, NAME, NOT_ALLOWED, NS,
|
|
8
|
+
PARAM, PARENT, REF, ROOT, SEQ, SOME, TEXT,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
import html
|
|
12
|
+
|
|
13
|
+
QUANTS = {SOME: 'oneOrMore', MAYBE: 'optional', ANY: 'zeroOrMore'}
|
|
14
|
+
TYPELIB_NS = 'http://www.w3.org/2001/XMLSchema-datatypes'
|
|
15
|
+
NAMESPACES = {
|
|
16
|
+
'a': 'http://relaxng.org/ns/compatibility/annotations/1.0',
|
|
17
|
+
'xml': 'http://www.w3.org/XML/1998/namespace',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class XMLSerializer(object):
|
|
21
|
+
|
|
22
|
+
def __init__(self, indent=None):
|
|
23
|
+
self.indent = indent or ' '
|
|
24
|
+
self.reset()
|
|
25
|
+
|
|
26
|
+
def reset(self):
|
|
27
|
+
self.buf = []
|
|
28
|
+
self.needs = {}
|
|
29
|
+
self.types = None
|
|
30
|
+
self.ns = {}
|
|
31
|
+
self.default = ''
|
|
32
|
+
self.level = 0
|
|
33
|
+
|
|
34
|
+
def write(self, s):
|
|
35
|
+
self.buf.append(self.indent * self.level + s)
|
|
36
|
+
|
|
37
|
+
def namespace(self, ns):
|
|
38
|
+
assert ns in self.ns or ns in NAMESPACES, ns
|
|
39
|
+
if ns not in self.ns:
|
|
40
|
+
self.ns[ns] = NAMESPACES[ns]
|
|
41
|
+
return self.ns[ns]
|
|
42
|
+
|
|
43
|
+
def toxml(self, node):
|
|
44
|
+
|
|
45
|
+
self.reset()
|
|
46
|
+
types = None
|
|
47
|
+
for n in node.value:
|
|
48
|
+
if n.type == DATATYPES:
|
|
49
|
+
types = n.value[0]
|
|
50
|
+
self.types = types
|
|
51
|
+
elif n.type == DEFAULT_NS:
|
|
52
|
+
self.default = n.value[0]
|
|
53
|
+
if n.name is not None:
|
|
54
|
+
self.ns[n.name] = n.value[0]
|
|
55
|
+
elif n.type == NS:
|
|
56
|
+
self.ns[n.name] = n.value[0]
|
|
57
|
+
|
|
58
|
+
prelude = ['<?xml version="1.0" encoding="UTF-8"?>']
|
|
59
|
+
prelude.append('<grammar xmlns="http://relaxng.org/ns/structure/1.0"')
|
|
60
|
+
if self.default:
|
|
61
|
+
prelude.append(' ns="%s"' % self.default)
|
|
62
|
+
|
|
63
|
+
self.visit(node.value)
|
|
64
|
+
for ns, url in sorted(self.ns.items()):
|
|
65
|
+
prelude.append(' xmlns:%s="%s"' % (ns, url))
|
|
66
|
+
if types is not None or self.needs.get('types'):
|
|
67
|
+
url = types if types is not None else TYPELIB_NS
|
|
68
|
+
prelude.append(' datatypeLibrary="%s"' % url)
|
|
69
|
+
|
|
70
|
+
prelude[-1] = prelude[-1] + '>'
|
|
71
|
+
self.write('</grammar>')
|
|
72
|
+
return '\n'.join(prelude + self.buf)
|
|
73
|
+
|
|
74
|
+
def anno_attrs(self, nodes):
|
|
75
|
+
select = lambda n: isinstance(n, parser.Node) and n.type == ANNO_ATTR
|
|
76
|
+
pairs = [(n.name, html.escape(n.value[0])) for n in nodes if select(n)]
|
|
77
|
+
if not pairs:
|
|
78
|
+
return ''
|
|
79
|
+
return ' ' + ' '.join('%s="%s"' % attr for attr in pairs)
|
|
80
|
+
|
|
81
|
+
def visit(self, nodes, ctx=None, indent=True):
|
|
82
|
+
'''Visiting a list of nodes, writes out the XML content to the internal
|
|
83
|
+
line-based buffer. By default, adds one level of indentation to the
|
|
84
|
+
output compared to the caller's level; passing False as the second
|
|
85
|
+
argument will prevent this from happening.'''
|
|
86
|
+
if indent:
|
|
87
|
+
self.level += 1
|
|
88
|
+
for x in nodes:
|
|
89
|
+
|
|
90
|
+
if not isinstance(x, parser.Node):
|
|
91
|
+
raise TypeError("Not a Node: " + repr(x))
|
|
92
|
+
elif x.type in set([ANNO_ATTR, DATATYPES, DEFAULT_NS, NS]):
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
attribs = self.anno_attrs(x.value)
|
|
96
|
+
if x.type == DEFINE:
|
|
97
|
+
|
|
98
|
+
op, attrib = x.value[0].name, ''
|
|
99
|
+
if op in set(['|=', '&=']):
|
|
100
|
+
modes = {'|': 'choice', '&': 'interleave'}
|
|
101
|
+
attrib = ' combine="%s"' % modes[op[0]]
|
|
102
|
+
|
|
103
|
+
if x.name == 'start':
|
|
104
|
+
self.write('<start%s%s>' % (attrib, attribs))
|
|
105
|
+
else:
|
|
106
|
+
bits = x.name, attrib, attribs
|
|
107
|
+
self.write('<define name="%s"%s%s>' % bits)
|
|
108
|
+
|
|
109
|
+
self.visit(x.value)
|
|
110
|
+
if x.name == 'start':
|
|
111
|
+
self.write('</start>')
|
|
112
|
+
else:
|
|
113
|
+
self.write('</define>')
|
|
114
|
+
|
|
115
|
+
elif x.type == ASSIGN:
|
|
116
|
+
self.visit(x.value, indent=False)
|
|
117
|
+
elif x.type == GRAMMAR:
|
|
118
|
+
self.write('<grammar>')
|
|
119
|
+
self.visit(x.value)
|
|
120
|
+
self.write('</grammar>')
|
|
121
|
+
elif x.type in set([MAYBE, SOME, ANY]):
|
|
122
|
+
self.write('<%s>' % QUANTS[x.type])
|
|
123
|
+
self.visit(x.value)
|
|
124
|
+
self.write('</%s>' % QUANTS[x.type])
|
|
125
|
+
elif x.type in set([INTERLEAVE, CHOICE, MIXED, LIST, DIV]):
|
|
126
|
+
self.write('<%s>' % x.type.lower())
|
|
127
|
+
self.visit(x.value)
|
|
128
|
+
self.write('</%s>' % x.type.lower())
|
|
129
|
+
elif x.type == EXCEPT:
|
|
130
|
+
self.write('<except>')
|
|
131
|
+
self.visit(x.value, ctx=ctx)
|
|
132
|
+
self.write('</except>')
|
|
133
|
+
elif x.type == NAME:
|
|
134
|
+
if not x.value and '*' in x.name:
|
|
135
|
+
if x.name == '*':
|
|
136
|
+
self.write('<anyName/>')
|
|
137
|
+
else:
|
|
138
|
+
uri = self.ns[x.name.split(':', 1)[0]]
|
|
139
|
+
self.write('<nsName ns="%s"/>' % uri)
|
|
140
|
+
elif x.value:
|
|
141
|
+
if x.name == '*':
|
|
142
|
+
self.write('<anyName>')
|
|
143
|
+
else:
|
|
144
|
+
uri = self.ns[x.name.split(':', 1)[0]]
|
|
145
|
+
self.write('<nsName ns="%s">' % uri)
|
|
146
|
+
self.visit(x.value, ctx=ctx)
|
|
147
|
+
if x.name == '*':
|
|
148
|
+
self.write('</anyName>')
|
|
149
|
+
else:
|
|
150
|
+
self.write('</nsName>')
|
|
151
|
+
else:
|
|
152
|
+
ns = '' if ctx == 'ATTR' else self.default
|
|
153
|
+
name = x.name
|
|
154
|
+
if ':' in name:
|
|
155
|
+
parts = x.name.split(':', 1)
|
|
156
|
+
ns = self.namespace(parts[0])
|
|
157
|
+
name = parts[1]
|
|
158
|
+
self.write('<name ns="%s">%s</name>' % (ns, name))
|
|
159
|
+
elif x.type in set([REF, PARENT]):
|
|
160
|
+
bits = x.type.lower(), x.name, attribs
|
|
161
|
+
self.write('<%s name="%s"%s/>' % bits)
|
|
162
|
+
elif x.type == LITERAL:
|
|
163
|
+
bits = attribs, html.escape(x.name)
|
|
164
|
+
self.write('<value%s>%s</value>' % bits)
|
|
165
|
+
self.visit(x.value, indent=False)
|
|
166
|
+
elif x.type == ANNOTATION:
|
|
167
|
+
|
|
168
|
+
literals, rest = [], []
|
|
169
|
+
for n in x.value:
|
|
170
|
+
if n.type == LITERAL:
|
|
171
|
+
literals.append(n.name)
|
|
172
|
+
elif n.type != ANNO_ATTR:
|
|
173
|
+
rest.append(n)
|
|
174
|
+
|
|
175
|
+
end = '/' if not (literals or rest) else ''
|
|
176
|
+
tail = ''
|
|
177
|
+
if literals and not rest:
|
|
178
|
+
tail = html.escape(''.join(literals)) + '</%s>' % x.name
|
|
179
|
+
|
|
180
|
+
bits = x.name, attribs, end, tail
|
|
181
|
+
self.write('<%s%s%s>%s' % bits)
|
|
182
|
+
if not rest:
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
for n in x.value:
|
|
186
|
+
if n.type == ANNO_ATTR:
|
|
187
|
+
continue
|
|
188
|
+
elif n.type == LITERAL:
|
|
189
|
+
self.level += 1
|
|
190
|
+
self.write(html.escape(n.name))
|
|
191
|
+
self.level -= 1
|
|
192
|
+
else:
|
|
193
|
+
self.visit([n])
|
|
194
|
+
|
|
195
|
+
self.write('</%s>' % x.name)
|
|
196
|
+
|
|
197
|
+
elif x.type == DOCUMENTATION:
|
|
198
|
+
self.namespace('a')
|
|
199
|
+
fmt = '<a:documentation>%s</a:documentation>'
|
|
200
|
+
self.write(fmt % html.escape('\n'.join(x.value)))
|
|
201
|
+
elif x.type == GROUP:
|
|
202
|
+
if len(x.value) == 1 and x.value[0].type != SEQ:
|
|
203
|
+
self.visit(x.value, indent=False)
|
|
204
|
+
else:
|
|
205
|
+
self.write('<%s%s>' % (x.type.lower(), attribs))
|
|
206
|
+
self.visit(x.value)
|
|
207
|
+
self.write('</%s>' % x.type.lower())
|
|
208
|
+
elif x.type == NOT_ALLOWED:
|
|
209
|
+
self.write('<notAllowed/>')
|
|
210
|
+
elif x.type in set([TEXT, EMPTY]):
|
|
211
|
+
if not len(x.value):
|
|
212
|
+
self.write('<%s%s/>' % (x.type.lower(), attribs))
|
|
213
|
+
else:
|
|
214
|
+
self.write('<%s%s>' % (x.type.lower(), attribs))
|
|
215
|
+
self.visit(x.value)
|
|
216
|
+
self.write('</%s>' % x.type.lower())
|
|
217
|
+
elif x.type == SEQ:
|
|
218
|
+
self.visit(x.value, indent=False)
|
|
219
|
+
elif x.type == DATATAG:
|
|
220
|
+
self.needs['types'] = True
|
|
221
|
+
if not x.value: # no parameters
|
|
222
|
+
self.write('<data type="%s"/>' % x.name)
|
|
223
|
+
else:
|
|
224
|
+
name = x.name
|
|
225
|
+
if name not in ('string', 'token'):
|
|
226
|
+
name = x.name.split(':', 1)[1]
|
|
227
|
+
self.write('<data type="%s">' % name)
|
|
228
|
+
self.visit(x.value)
|
|
229
|
+
self.write('</data>')
|
|
230
|
+
elif x.type == PARAM:
|
|
231
|
+
bits = x.name, html.escape(x.value[0])
|
|
232
|
+
self.write('<param name="%s">%s</param>' % bits)
|
|
233
|
+
elif x.type == ELEM:
|
|
234
|
+
self.write('<element%s>' % attribs)
|
|
235
|
+
self.visit(x.value)
|
|
236
|
+
self.write('</element>')
|
|
237
|
+
elif x.type == ATTR:
|
|
238
|
+
self.write('<attribute%s>' % attribs)
|
|
239
|
+
self.visit(x.value, ctx=x.type)
|
|
240
|
+
self.write('</attribute>')
|
|
241
|
+
elif x.type == ROOT:
|
|
242
|
+
# Verify the included document has the same metadata
|
|
243
|
+
for n in x.value:
|
|
244
|
+
if n.type == DATATYPES:
|
|
245
|
+
assert self.types == n.value[0]
|
|
246
|
+
elif n.type == DEFAULT_NS:
|
|
247
|
+
assert self.default == n.value[0]
|
|
248
|
+
elif n.type == NS:
|
|
249
|
+
assert n.name in self.ns
|
|
250
|
+
assert self.ns[n.name] == n.value[0]
|
|
251
|
+
self.visit(x.value, indent=False)
|
|
252
|
+
else:
|
|
253
|
+
assert False, x
|
|
254
|
+
if indent:
|
|
255
|
+
self.level -= 1
|
usfmtc/sfmparser.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
import re, logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
import xml.etree.ElementTree as et
|
|
6
|
+
import regex
|
|
7
|
+
import usfmtc.parser as usfmp
|
|
8
|
+
from usfmtc.xmlutils import ParentElement
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("sfmparser")
|
|
11
|
+
|
|
12
|
+
class GlobalState(usfmp.GlobalState):
|
|
13
|
+
''' text based global state '''
|
|
14
|
+
def __init__(self, txt, timeout=1e7):
|
|
15
|
+
super().__init__(timeout=timeout)
|
|
16
|
+
self.str = txt
|
|
17
|
+
self.captures = []
|
|
18
|
+
self.refs = {}
|
|
19
|
+
|
|
20
|
+
def __getitem__(self, key):
|
|
21
|
+
return self.str[key]
|
|
22
|
+
|
|
23
|
+
def __call__(self):
|
|
24
|
+
return self.str
|
|
25
|
+
|
|
26
|
+
def _ensure(self, index):
|
|
27
|
+
if index >= len(self.captures):
|
|
28
|
+
#self.captures.extend([""] * (index - len(self.captures) + 1))
|
|
29
|
+
self.captures += [[[]] for i in range(index - len(self.captures) + 1)]
|
|
30
|
+
|
|
31
|
+
def capture(self, index, txt):
|
|
32
|
+
self._ensure(index)
|
|
33
|
+
if isinstance(txt, (list, tuple)):
|
|
34
|
+
txt = "".join(txt)
|
|
35
|
+
logger.debug(f"Capture[{index}][{len(self.captures[index])-1}] = '{txt}'")
|
|
36
|
+
self.captures[index][-1] = txt
|
|
37
|
+
|
|
38
|
+
def init(self, index, txt):
|
|
39
|
+
self._ensure(index)
|
|
40
|
+
logger.debug(f"Init capture[{index}][{len(self.captures[index])}]")
|
|
41
|
+
self.captures[index].append("")
|
|
42
|
+
|
|
43
|
+
def release(self, index):
|
|
44
|
+
self.captures[index].pop()
|
|
45
|
+
logger.debug(f"Pop capture[{index}][{len(self.captures[index])}]")
|
|
46
|
+
|
|
47
|
+
def getcapture(self, index):
|
|
48
|
+
logger.debug(f"Get capture[{index}][{len(self.captures[index])}] = '{self.captures[index][-1]}'")
|
|
49
|
+
return self.captures[index][-1]
|
|
50
|
+
|
|
51
|
+
def pop(self):
|
|
52
|
+
return self.captures.pop()
|
|
53
|
+
|
|
54
|
+
def getref(self, name):
|
|
55
|
+
return self.refs.get(name)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Group(usfmp.Group):
|
|
59
|
+
|
|
60
|
+
def _returnRes(self, res, s):
|
|
61
|
+
res = super()._returnRes(res, s)
|
|
62
|
+
if res is None:
|
|
63
|
+
return None
|
|
64
|
+
while res is not None and len(res) == 1:
|
|
65
|
+
if isinstance(res, (str, Attribute, Element)):
|
|
66
|
+
break
|
|
67
|
+
res = res[0]
|
|
68
|
+
final = self.result(res, self) if self.result is not None else res
|
|
69
|
+
if self.capture and final is not None:
|
|
70
|
+
s.gs.capture(self.capture, final)
|
|
71
|
+
return final
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class String(usfmp.Parser):
|
|
75
|
+
def __init__(self, reg, dump=False, **kw):
|
|
76
|
+
self.keep = not dump
|
|
77
|
+
def text(s):
|
|
78
|
+
m = regex.match(self.re, s.gs(), pos=s.pos)
|
|
79
|
+
if not m:
|
|
80
|
+
raise usfmp.NoParseError(f'String ({self.re}) not found', s)
|
|
81
|
+
return (m.group(1) if self.keep else None, s.extend(m.end()-s.pos))
|
|
82
|
+
super().__init__(text, **kw)
|
|
83
|
+
reg = re.sub(r"\\u([0-9a-fA-F]{4})", lambda m:chr(int(m.group(1), 16)), reg)
|
|
84
|
+
self.re = reg if dump else "(" + reg + ")"
|
|
85
|
+
self.name = "/" + reg + "/"
|
|
86
|
+
|
|
87
|
+
def __repr__(self):
|
|
88
|
+
return self.asstr()
|
|
89
|
+
|
|
90
|
+
def asstr(self, context=None):
|
|
91
|
+
return '/{}/{}'.format(self.re, "!" if self.keep else "")
|
|
92
|
+
|
|
93
|
+
def merge(self, other, mode=None):
|
|
94
|
+
if not isinstance(other, String):
|
|
95
|
+
return super().merge(other, mode=mode)
|
|
96
|
+
if mode and "|" in mode:
|
|
97
|
+
ore = other.re[1:-1] if other.re.startswith("(") and other.re.endswith(")") else other.re
|
|
98
|
+
if self.re.startswith("(") and self.re.endswith(")"):
|
|
99
|
+
self.re = self.re[:-1] + "|" + ore + ")"
|
|
100
|
+
else:
|
|
101
|
+
self.re = "(" + self.re + "|" + ore + ")"
|
|
102
|
+
elif self.keep and other.keep:
|
|
103
|
+
return super().merge(other, mode=mode)
|
|
104
|
+
else:
|
|
105
|
+
self.re += other.re
|
|
106
|
+
self.name = "/" + self.re + "/"
|
|
107
|
+
self.keep |= other.keep
|
|
108
|
+
return (self,)
|
|
109
|
+
|
|
110
|
+
def empty(self):
|
|
111
|
+
return len(self.name) == 0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Reference(usfmp.Parser):
|
|
115
|
+
def __init__(self, index, **kw):
|
|
116
|
+
self.backref = index
|
|
117
|
+
def test(s):
|
|
118
|
+
v = s.gs.getcapture(self.backref)
|
|
119
|
+
if s.gs().startswith(v, s.pos):
|
|
120
|
+
return(v if not kw.get('dump', False) else None, s.extend(len(v)))
|
|
121
|
+
else:
|
|
122
|
+
raise usfmp.NoParseError(f'String from backref (v) not found', s)
|
|
123
|
+
super().__init__(test, **kw)
|
|
124
|
+
|
|
125
|
+
def __repr__(self):
|
|
126
|
+
return "\u21D0{}".format(self.backref)
|
|
127
|
+
|
|
128
|
+
def asstr(self, context=None):
|
|
129
|
+
return "\u21D0{}".format(self.backref)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class Attribute:
|
|
134
|
+
name: str
|
|
135
|
+
value: str
|
|
136
|
+
override: str
|
|
137
|
+
|
|
138
|
+
def __init__(self, name, value, override, **kw):
|
|
139
|
+
self.override = override
|
|
140
|
+
if isinstance(value, list):
|
|
141
|
+
if len(value) > 1:
|
|
142
|
+
self.name, self.value = value
|
|
143
|
+
return
|
|
144
|
+
else:
|
|
145
|
+
value = value[0]
|
|
146
|
+
self.name = name
|
|
147
|
+
self.value = value
|
|
148
|
+
|
|
149
|
+
def __len__(self):
|
|
150
|
+
return 1
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class Element(list):
|
|
154
|
+
def __init__(self, *a, name=None, propmap=None, **kw):
|
|
155
|
+
self.name = name
|
|
156
|
+
self.attributes = {}
|
|
157
|
+
self.propmap = propmap
|
|
158
|
+
self.kw = kw
|
|
159
|
+
for e in a:
|
|
160
|
+
self._append(e)
|
|
161
|
+
if usfmp.Parser.debug:
|
|
162
|
+
logger.debug(f"Create Element({self})")
|
|
163
|
+
|
|
164
|
+
def _append(self, e):
|
|
165
|
+
if isinstance(e, Attribute):
|
|
166
|
+
if self.propmap is not None and e.override is not None and e.name == "_default":
|
|
167
|
+
self.attributes[self.propmap.get(self.attributes[e.override], e.name)] = e.value
|
|
168
|
+
else:
|
|
169
|
+
self.attributes[e.name] = e.value
|
|
170
|
+
elif isinstance(e, list) and not isinstance(e, Element):
|
|
171
|
+
for c in e:
|
|
172
|
+
self._append(c)
|
|
173
|
+
else:
|
|
174
|
+
self.append(e)
|
|
175
|
+
|
|
176
|
+
def asEt(self, parent=None, elfactory=None):
|
|
177
|
+
if elfactory is None:
|
|
178
|
+
elfactory = ParentElement
|
|
179
|
+
res = elfactory(self.name, attrib=self.attributes, parent=parent)
|
|
180
|
+
if parent is not None:
|
|
181
|
+
parent.append(res)
|
|
182
|
+
def asetchild(c, res):
|
|
183
|
+
if isinstance(c, str):
|
|
184
|
+
if len(res):
|
|
185
|
+
res[-1].tail = c if res[-1].tail is None else res[-1].tail + c
|
|
186
|
+
else:
|
|
187
|
+
res.text = c if res.text is None else res.text + c
|
|
188
|
+
elif isinstance(c, Element):
|
|
189
|
+
c.asEt(res, elfactory=elfactory)
|
|
190
|
+
elif isinstance(c, list):
|
|
191
|
+
for e in c:
|
|
192
|
+
asetchild(e, res)
|
|
193
|
+
j = self[:]
|
|
194
|
+
self.clear()
|
|
195
|
+
for c in j:
|
|
196
|
+
self._append(c)
|
|
197
|
+
for c in self:
|
|
198
|
+
asetchild(c, res)
|
|
199
|
+
return res
|
|
200
|
+
|
|
201
|
+
def __str__(self):
|
|
202
|
+
return "{}<{}>{}".format(self.name, self.attributes, [str(x) for x in self])
|
|
203
|
+
|
|
204
|
+
def __repr__(self):
|
|
205
|
+
return str(self)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def parseusfm(infilename, parser, timeout=1e7, isdata=True):
|
|
209
|
+
if isdata:
|
|
210
|
+
dat = infilename
|
|
211
|
+
elif hasattr(infilename, 'read'):
|
|
212
|
+
dat = infilename.read().decode("utf-8")
|
|
213
|
+
else:
|
|
214
|
+
with open(infilename, encoding="utf-8") as inf:
|
|
215
|
+
dat = inf.read()
|
|
216
|
+
gs = GlobalState(dat, timeout=timeout)
|
|
217
|
+
return parser.parse(usfmp.State(gs))
|
|
218
|
+
|
|
219
|
+
escapes = {
|
|
220
|
+
'\\' : '\\',
|
|
221
|
+
'n': '\n'
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
def expandescape(s):
|
|
225
|
+
return re.sub(r'\\(.)', lambda m: escapes.get(m.group(1), "\\"+m.group(1)), s)
|
|
226
|
+
|
|
227
|
+
class UsfmParserBackend:
|
|
228
|
+
_terminals = {
|
|
229
|
+
'text': lambda **kw : Text(**kw),
|
|
230
|
+
'ws': lambda **kw: String(r"\s", **kw),
|
|
231
|
+
'word': lambda **kw: Text(**kw)
|
|
232
|
+
}
|
|
233
|
+
_grouptypes = {
|
|
234
|
+
'sequence': "&",
|
|
235
|
+
'choice': "|",
|
|
236
|
+
'optional': "?",
|
|
237
|
+
'zeroOrMore': "*",
|
|
238
|
+
'oneOrMore': "+"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
def __init__(self):
|
|
242
|
+
self.references = {}
|
|
243
|
+
self.elements = []
|
|
244
|
+
self.nodes = []
|
|
245
|
+
self.defines = {}
|
|
246
|
+
self.defstack = []
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
def _trimTo(self, curr):
|
|
250
|
+
i = self.nodes.index(curr)
|
|
251
|
+
self.nodes[:] = self.nodes[:i]
|
|
252
|
+
|
|
253
|
+
def get_nodename(self):
|
|
254
|
+
res = {}
|
|
255
|
+
if len(self.nodes):
|
|
256
|
+
res['to'] = self.nodes[-1].name
|
|
257
|
+
return res
|
|
258
|
+
|
|
259
|
+
# --- Parser backend API
|
|
260
|
+
|
|
261
|
+
def attrib_start(self, parser, e, context, name, **kw):
|
|
262
|
+
if name == "*":
|
|
263
|
+
name = "_default"
|
|
264
|
+
group = Group(name=name, parent=context, mode="&",
|
|
265
|
+
result=(lambda r,s: Attribute(name, r, kw.get('fallback-from', None))),
|
|
266
|
+
**self.get_nodename())
|
|
267
|
+
self.nodes.append(group)
|
|
268
|
+
return group
|
|
269
|
+
|
|
270
|
+
def attrib_end(self, parser, e, context, **kw):
|
|
271
|
+
self._trimTo(context)
|
|
272
|
+
return context
|
|
273
|
+
|
|
274
|
+
def elem_start(self, parser, e, context, name, **kw):
|
|
275
|
+
group = Group(name=name, parent=context, mode="&",
|
|
276
|
+
result=(lambda r,s: Element(r, name=name, propmap=s.propmap.copy())),
|
|
277
|
+
**self.get_nodename())
|
|
278
|
+
self.nodes.append(group)
|
|
279
|
+
return group
|
|
280
|
+
|
|
281
|
+
def elem_end(self, parser, e, context, **kw):
|
|
282
|
+
self._trimTo(context)
|
|
283
|
+
return context
|
|
284
|
+
|
|
285
|
+
def append_seq(self, context=None, to=None, **kw):
|
|
286
|
+
return Group(parent=context, mode="&", **self.get_nodename(), **kw)
|
|
287
|
+
|
|
288
|
+
def append_opt(self, context=None, to=None, **kw):
|
|
289
|
+
return Group(parent=context, mode="?", **self.get_nodename(), **kw)
|
|
290
|
+
|
|
291
|
+
def append_or(self, context=None, to=None, **kw):
|
|
292
|
+
return Group(parent=context, mode="|", **self.get_nodename(), **kw)
|
|
293
|
+
|
|
294
|
+
def append_plus(self, context=None, to=None, **kw):
|
|
295
|
+
bet = context.properties.get("between", None)
|
|
296
|
+
# handle "between"
|
|
297
|
+
return Group(parent=context, mode="+", **self.get_nodename(), **kw)
|
|
298
|
+
|
|
299
|
+
def append_star(self, context=None, to=None, **kw):
|
|
300
|
+
return Group(parent=context, mode="*", **self.get_nodename(), **kw)
|
|
301
|
+
|
|
302
|
+
def append_group(self, index, context=None, to=None, **kw):
|
|
303
|
+
group = Group(parent=context, mode="&", **self.get_nodename(), **kw)
|
|
304
|
+
return group
|
|
305
|
+
|
|
306
|
+
def append_interleave(self, context=None, to=None, **kw):
|
|
307
|
+
return Group(parent=context, mode="|+", **self.get_nodename(), **kw)
|
|
308
|
+
|
|
309
|
+
def match(self, name, context, dump=False, to=None, **kw):
|
|
310
|
+
if not len(name):
|
|
311
|
+
return context
|
|
312
|
+
if name[0] in "\"'":
|
|
313
|
+
res = String(regex.escape(expandescape(name[1:-1])), parent=context, dump=dump, **self.get_nodename(), **kw)
|
|
314
|
+
elif name.startswith("/"):
|
|
315
|
+
res = String(name[1:-1], parent=context, dump=dump, **self.get_nodename(), **kw)
|
|
316
|
+
else:
|
|
317
|
+
res = self._terminals.get(name.lower(), lambda **kw: usfmp.Skip(**kw))(parent=context, dump=dump, **self.get_nodename(), **kw)
|
|
318
|
+
context.append(res)
|
|
319
|
+
return context
|
|
320
|
+
|
|
321
|
+
def terminal(self, name, context, to=None, **kw):
|
|
322
|
+
return self.match(name, context, dump=True, **self.get_nodename(), **kw)
|
|
323
|
+
|
|
324
|
+
def backref(self, index, context=None, to=None, **kw):
|
|
325
|
+
res = Reference(index, parent=context, **self.get_nodename(), **kw)
|
|
326
|
+
context.append(res)
|
|
327
|
+
return context
|
|
328
|
+
|
|
329
|
+
def add_terminal(self, name, value, context, **kw):
|
|
330
|
+
if value[0] in "\"'":
|
|
331
|
+
res = lambda **kw: String(regex.escape(value[1:-1]), **kw)
|
|
332
|
+
elif value[0].startswith("/"):
|
|
333
|
+
res = lambda **kw: String(value[1:-1], **kw)
|
|
334
|
+
self._terminals[name.lower()] = res
|
|
335
|
+
|
|
336
|
+
def add_define(self, name, e, context=None, **kw):
|
|
337
|
+
res = self.append_seq(context, inref=name)
|
|
338
|
+
self.defines[name] = res
|
|
339
|
+
return res
|
|
340
|
+
|
|
341
|
+
def ref(self, name, context, **kw):
|
|
342
|
+
res = self.defines[name]
|
|
343
|
+
context.append(res)
|
|
344
|
+
return res
|
|
345
|
+
|