usfmtc 0.2.2__tar.gz → 0.2.4__tar.gz
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-0.2.2/python/lib/usfmtc.egg-info → usfmtc-0.2.4}/PKG-INFO +2 -2
- {usfmtc-0.2.2 → usfmtc-0.2.4}/pyproject.toml +1 -1
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/__init__.py +30 -22
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/extension.py +2 -2
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/usfmparser.py +144 -71
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/xmlutils.py +13 -3
- {usfmtc-0.2.2 → usfmtc-0.2.4/python/lib/usfmtc.egg-info}/PKG-INFO +2 -2
- {usfmtc-0.2.2 → usfmtc-0.2.4}/LICENSE.md +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/README.md +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/README.md +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/diagrams.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/grammar.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/parser.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/railroad.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/reference.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/rnc2rng/__init__.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/rnc2rng/parser.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/rnc2rng/rnctree.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/rnc2rng/serializer.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/sfmparser.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/usfmgenerate.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/usjproc.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/usx.rng +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/usxgrammar.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/usxmodel.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc/usxparser.py +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/SOURCES.txt +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/dependency_links.txt +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/entry_points.txt +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/requires.txt +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/top_level.txt +0 -0
- {usfmtc-0.2.2 → usfmtc-0.2.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: usfmtc
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: USFM reference parser
|
|
5
5
|
Author-email: USFM Technical Committee <fonts@sil.org>
|
|
6
6
|
License: Copyright © 2022-2023 by the members of the USFM/USX technical committee, listed below:
|
|
@@ -58,15 +58,15 @@ def _usfmGrammar(rdoc, backend=None, start=None):
|
|
|
58
58
|
|
|
59
59
|
def usfmGrammar(gsrc, extensions=[], altparser=True, backend=None, start=None):
|
|
60
60
|
""" Create UsfmGrammarParser from gsrc as used by USX.fromUsfm """
|
|
61
|
-
if len(extensions) or not altparser:
|
|
62
|
-
rdoc = _grammarDoc(gsrc, extensions)
|
|
63
61
|
if altparser:
|
|
62
|
+
rdoc = _grammarDoc(gsrc, extensions)
|
|
63
|
+
return _usfmGrammar(rdoc, backend, start)
|
|
64
|
+
else:
|
|
64
65
|
res = Grammar()
|
|
65
66
|
if len(extensions):
|
|
66
67
|
for e in extensions:
|
|
67
68
|
res.readmrkrs(e)
|
|
68
69
|
return res
|
|
69
|
-
return _usfmGrammar(rdoc, backend, start)
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
_filetypes = {".xml": "usx", ".usx": "usx", ".usfm": "usfm", ".sfm": "usfm3.0", ".json": "usj"}
|
|
@@ -129,10 +129,12 @@ class USX:
|
|
|
129
129
|
@classmethod
|
|
130
130
|
def fromUsfm(cls, src, grammar, altparser=True, elfactory=None, timeout=1e7):
|
|
131
131
|
""" Parses USFM using UsfmGrammarParser grammar and creates USX object.
|
|
132
|
-
Raise usfmtc.parser.NoParseError on error.
|
|
132
|
+
Raise usfmtc.parser.NoParseError on error.
|
|
133
|
+
elfactory must take parent and pos named parameters not as attributes
|
|
134
|
+
"""
|
|
133
135
|
data = _readsrc(src)
|
|
134
136
|
|
|
135
|
-
if altparser:
|
|
137
|
+
if not altparser:
|
|
136
138
|
p = USFMParser(data, factory=elfactory or ParentElement, grammar=grammar)
|
|
137
139
|
xml = p.parse()
|
|
138
140
|
else:
|
|
@@ -171,7 +173,7 @@ class USX:
|
|
|
171
173
|
fn(file, dat)
|
|
172
174
|
return True
|
|
173
175
|
|
|
174
|
-
def outUsx(self, file=None):
|
|
176
|
+
def outUsx(self, file=None, **kw):
|
|
175
177
|
""" Output pretty XML USX. If file is None returns string """
|
|
176
178
|
if self.xml is None:
|
|
177
179
|
return None
|
|
@@ -181,7 +183,7 @@ class USX:
|
|
|
181
183
|
def outUsfm(self, grammar, file=None, altparser=True, **kw):
|
|
182
184
|
""" Output USFM from USX object. grammar is et doc. If file is None returns string """
|
|
183
185
|
el = messup(self.xml)
|
|
184
|
-
if altparser:
|
|
186
|
+
if not altparser:
|
|
185
187
|
return self._outwrite(file, el, fn=usx2usfm)
|
|
186
188
|
parser = USXConverter(grammar.getroot(), **kw)
|
|
187
189
|
res = parser.parse(el)
|
|
@@ -190,16 +192,17 @@ class USX:
|
|
|
190
192
|
return self._outwrite(file, dat)
|
|
191
193
|
return False
|
|
192
194
|
|
|
193
|
-
def outUsj(self, file=None):
|
|
195
|
+
def outUsj(self, file=None, ensure_ascii=False, **kw):
|
|
194
196
|
""" Output USJ from USX object. If file is None returns dict """
|
|
195
197
|
res = usxtousj(self.xml)
|
|
196
198
|
if file is None:
|
|
197
199
|
return res
|
|
198
200
|
else:
|
|
199
|
-
dat = json.dumps(res, indent=2)
|
|
201
|
+
dat = json.dumps(res, indent=2, ensure_ascii=ensure_ascii)
|
|
200
202
|
self._outwrite(file, dat)
|
|
201
203
|
|
|
202
|
-
def saveAs(self, outfpath, outformat=None, addesids=False, grammar=None,
|
|
204
|
+
def saveAs(self, outfpath, outformat=None, addesids=False, grammar=None,
|
|
205
|
+
gramfile=None, version=None, altparser=True, **kw):
|
|
203
206
|
""" Saves the document to a file in the appropriate format, either given
|
|
204
207
|
or inferred from the filename extension. """
|
|
205
208
|
if outformat is None:
|
|
@@ -213,11 +216,11 @@ class USX:
|
|
|
213
216
|
if outtype == "usx":
|
|
214
217
|
if addesids:
|
|
215
218
|
usxdoc.addesids()
|
|
216
|
-
self.outUsx(outfpath)
|
|
219
|
+
self.outUsx(outfpath, **kw)
|
|
217
220
|
elif outtype == "usj":
|
|
218
|
-
self.outUsj(outfpath)
|
|
221
|
+
self.outUsj(outfpath, **kw)
|
|
219
222
|
elif outtype == "usfm":
|
|
220
|
-
if grammar is None and
|
|
223
|
+
if grammar is None and altparser:
|
|
221
224
|
if gramfile is None:
|
|
222
225
|
for a in ([], ['..', '..', '..', 'grammar']):
|
|
223
226
|
gramfile = os.path.join(os.path.dirname(__file__), *a, "usx.rng")
|
|
@@ -228,7 +231,7 @@ class USX:
|
|
|
228
231
|
outtype = "usfm"
|
|
229
232
|
if version is None:
|
|
230
233
|
version = "3.0"
|
|
231
|
-
self.outUsfm(grammar, outfpath, outversion=version, altparser=altparser)
|
|
234
|
+
self.outUsfm(grammar, outfpath, outversion=version, altparser=altparser, **kw)
|
|
232
235
|
|
|
233
236
|
def canonicalise(self):
|
|
234
237
|
canonicalise(self.getroot())
|
|
@@ -260,11 +263,14 @@ def main(hookcli=None, hookusx=None):
|
|
|
260
263
|
parser.add_argument("-F","--outformat",help="Output format [usfm, usx, usj, usfm3.0]")
|
|
261
264
|
parser.add_argument("-I","--informat",help="Input format [usfm, usx, usj]")
|
|
262
265
|
parser.add_argument("-g","--grammar",help="Grammar file to use, if needed")
|
|
263
|
-
parser.add_argument("-e","--esids",action="store_true",
|
|
266
|
+
parser.add_argument("-e","--esids",action="store_true",
|
|
267
|
+
help="Add esids, vids, sids, etc. to USX output")
|
|
264
268
|
parser.add_argument("-v","--version",default=None,help="Set USFM version [3.1]")
|
|
265
|
-
parser.add_argument("-x","--extfiles",action="append",default=[],
|
|
266
|
-
|
|
269
|
+
parser.add_argument("-x","--extfiles",action="append",default=[],
|
|
270
|
+
help="markers.ext files to include")
|
|
271
|
+
parser.add_argument("-V","--validate",action="store_true",help="Use validating parsers for USFM")
|
|
267
272
|
parser.add_argument("-C","--canonical",action="store_true",help="Do not canonicalise")
|
|
273
|
+
parser.add_argument("-A","--ascii",action="store_true",help="Output as ASCII only in json")
|
|
268
274
|
parser.add_argument("-l","--logging",help="Set logging level to usfmxtest.log")
|
|
269
275
|
parser.add_argument("-q","--quiet",action="store_true",help="Don't say much")
|
|
270
276
|
parser.add_argument("--nooutput",action="store_true",help="Don't output any data")
|
|
@@ -326,16 +332,16 @@ def main(hookcli=None, hookusx=None):
|
|
|
326
332
|
if args.informat.startswith("usfm"):
|
|
327
333
|
args.extfiles.append(os.path.join(os.path.dirname(infiles[0]), "markers.ext"))
|
|
328
334
|
exts = [x for x in args.extfiles if os.path.exists(x)]
|
|
329
|
-
ingrammar = usfmGrammar(args.grammar, altparser=args.
|
|
335
|
+
ingrammar = usfmGrammar(args.grammar, altparser=args.validate, extensions=exts)
|
|
330
336
|
if args.outformat and args.outformat.startswith("usfm"):
|
|
331
337
|
outgrammar = _grammarDoc(args.grammar)
|
|
332
338
|
|
|
333
339
|
for infile in infiles:
|
|
334
340
|
outfile = None
|
|
335
341
|
if args.outfile is None:
|
|
336
|
-
outfile = _makeoutfile(infile, args.
|
|
342
|
+
outfile = _makeoutfile(infile, args.outformat)
|
|
337
343
|
elif os.path.isdir(args.outfile):
|
|
338
|
-
outf = _makeoutfile(infile, args.
|
|
344
|
+
outf = _makeoutfile(infile, args.outformat)
|
|
339
345
|
if outf is None:
|
|
340
346
|
doerror(f"invalid output format {args.format} in {args.outfile}")
|
|
341
347
|
outfile = os.path.join(args.outfile, os.path.basename(outf))
|
|
@@ -351,7 +357,8 @@ def main(hookcli=None, hookusx=None):
|
|
|
351
357
|
if not args.quiet:
|
|
352
358
|
print(f"{infile} -> {outfile}" if outfile else f"{infile}")
|
|
353
359
|
try:
|
|
354
|
-
usxdoc = readFile(infile, informat=args.informat, grammar=ingrammar,
|
|
360
|
+
usxdoc = readFile(infile, informat=args.informat, grammar=ingrammar,
|
|
361
|
+
altparser=args.validate)
|
|
355
362
|
except NoParseError as e:
|
|
356
363
|
doerror(f"Failed to parse {infile}: {e}", False)
|
|
357
364
|
|
|
@@ -373,7 +380,8 @@ def main(hookcli=None, hookusx=None):
|
|
|
373
380
|
if not args.canonical:
|
|
374
381
|
usxdoc.canonicalise()
|
|
375
382
|
|
|
376
|
-
usxdoc.saveAs(outfile, outformat=args.outformat, addesids=args.esids,
|
|
383
|
+
usxdoc.saveAs(outfile, outformat=args.outformat, addesids=args.esids,
|
|
384
|
+
grammar=outgrammar, altparser=args.validate, ensure_ascii=args.ascii)
|
|
377
385
|
|
|
378
386
|
if __name__ == "__main__":
|
|
379
387
|
main()
|
|
@@ -58,11 +58,11 @@ class Extensions(SFMFile):
|
|
|
58
58
|
|
|
59
59
|
def applyto(self, rdoc, factory=et):
|
|
60
60
|
res = False # nothing added
|
|
61
|
-
allcats = set([x.get('category',
|
|
61
|
+
allcats = set([x.get('category', "").split()[0] for x in self.markers.values()])
|
|
62
62
|
for a in allcats:
|
|
63
63
|
if a not in categories:
|
|
64
64
|
continue
|
|
65
|
-
ms = [k for k, v in self.markers.items() if v.get('category',
|
|
65
|
+
ms = [k for k, v in self.markers.items() if a in v.get('category', "").split()]
|
|
66
66
|
enum = categories[a]+".style.enum"
|
|
67
67
|
e = rdoc.find('./{0}define[@name="{1}"]/{0}choice'.format(relaxns, enum))
|
|
68
68
|
allmks = set([v.text for v in e.findall(f'./{relaxns}value')])
|
|
@@ -5,8 +5,15 @@ import xml.etree.ElementTree as et
|
|
|
5
5
|
from usfmtc.extension import SFMFile
|
|
6
6
|
from collections import UserDict, UserString
|
|
7
7
|
|
|
8
|
+
|
|
9
|
+
class Pos:
|
|
10
|
+
def __init__(self, l, c, **kw):
|
|
11
|
+
self.l = l
|
|
12
|
+
self.c = c
|
|
13
|
+
self.kw = kw
|
|
14
|
+
|
|
8
15
|
class Tag(str):
|
|
9
|
-
def __new__(cls, s):
|
|
16
|
+
def __new__(cls, s, l=0, c=0, **kw):
|
|
10
17
|
isend = False
|
|
11
18
|
isplus = False
|
|
12
19
|
if s.startswith("+"):
|
|
@@ -16,6 +23,7 @@ class Tag(str):
|
|
|
16
23
|
isend = True
|
|
17
24
|
s = s[:-1]
|
|
18
25
|
res = super().__new__(cls, s)
|
|
26
|
+
res.kw = kw
|
|
19
27
|
res.isplus = isplus
|
|
20
28
|
res.isend = isend
|
|
21
29
|
return res
|
|
@@ -23,7 +31,7 @@ class Tag(str):
|
|
|
23
31
|
def __init__(self, s, l=0, c=0):
|
|
24
32
|
self.isplus = getattr(self, 'isplus', False)
|
|
25
33
|
self.isend = getattr(self, 'isend', False)
|
|
26
|
-
self.pos = (l, c)
|
|
34
|
+
self.pos = Pos(l, c)
|
|
27
35
|
|
|
28
36
|
def __repr__(self):
|
|
29
37
|
return "Tag("+str(self)+")"
|
|
@@ -41,14 +49,16 @@ class Tag(str):
|
|
|
41
49
|
self.pos = pos
|
|
42
50
|
|
|
43
51
|
class AttribText(UserString):
|
|
44
|
-
def __init__(self, s, l=0, c=0):
|
|
52
|
+
def __init__(self, s, l=0, c=0, **kw):
|
|
45
53
|
super().__init__(s)
|
|
46
|
-
self.pos = (l, c)
|
|
54
|
+
self.pos = Pos(l, c)
|
|
55
|
+
self.kw = kw
|
|
47
56
|
pass
|
|
48
57
|
|
|
49
58
|
class OptBreak:
|
|
50
|
-
def __init__(self, l=0, c=0):
|
|
51
|
-
self.pos = (l, c)
|
|
59
|
+
def __init__(self, l=0, c=0, **kw):
|
|
60
|
+
self.pos = Pos(l, c)
|
|
61
|
+
self.kw = kw
|
|
52
62
|
|
|
53
63
|
def __repr__(self):
|
|
54
64
|
return "OptBreak(" + str(self) + ")"
|
|
@@ -56,9 +66,10 @@ class OptBreak:
|
|
|
56
66
|
return "//"
|
|
57
67
|
|
|
58
68
|
class String(UserString):
|
|
59
|
-
def __init__(self, s, l=0, c=0):
|
|
69
|
+
def __init__(self, s, l=0, c=0, **kw):
|
|
60
70
|
super().__init__(s)
|
|
61
|
-
self.pos = (l, c)
|
|
71
|
+
self.pos = Pos(l, c)
|
|
72
|
+
self.kw = kw
|
|
62
73
|
|
|
63
74
|
def __add__(self, s):
|
|
64
75
|
return String(str(self) + s)
|
|
@@ -66,25 +77,28 @@ class String(UserString):
|
|
|
66
77
|
pass
|
|
67
78
|
|
|
68
79
|
class Attribs(UserDict):
|
|
69
|
-
def __init__(self, l=0, c=0):
|
|
80
|
+
def __init__(self, l=0, c=0, **kw):
|
|
70
81
|
super().__init__()
|
|
71
|
-
self.pos = (l, c)
|
|
82
|
+
self.pos = Pos(l, c)
|
|
83
|
+
self.kw = kw
|
|
72
84
|
|
|
73
85
|
class Lexer:
|
|
74
86
|
|
|
75
87
|
tokenre = regex.compile(r'((?:[^\\|/]|\\[\\|\n]|/[^/])+)|([\\|\n]|//)')
|
|
76
|
-
tagre = regex.compile(r'(?:\+?[a-zA-Z_][a-zA-Z_0-9
|
|
88
|
+
tagre = regex.compile(r'(?:\+?[a-zA-Z_][a-zA-Z_0-9^-]*)?\*?')
|
|
77
89
|
attribsre = regex.compile(r'\s*([a-zA-Z_][a-zA-Z0-9_-]*)\s*=\s*"((?:\\"|[^"])*)"')
|
|
78
90
|
textrunre = regex.compile(r'(?:[^\\]|\\[^a-zA-Z_*+])*')
|
|
79
91
|
|
|
80
|
-
def __init__(self, txt):
|
|
92
|
+
def __init__(self, txt, expanded=False):
|
|
81
93
|
self.txt = txt
|
|
94
|
+
self.expanded = expanded
|
|
82
95
|
|
|
83
96
|
def __iter__(self):
|
|
84
97
|
self.nexts = []
|
|
85
98
|
self.cindex = 0
|
|
86
99
|
self.lindex = 0
|
|
87
100
|
self.lpos = 0
|
|
101
|
+
self.currxpand = None
|
|
88
102
|
return self
|
|
89
103
|
|
|
90
104
|
def __next__(self):
|
|
@@ -113,7 +127,9 @@ class Lexer:
|
|
|
113
127
|
curri = m.end() + 1
|
|
114
128
|
continue
|
|
115
129
|
else:
|
|
116
|
-
|
|
130
|
+
tagname = self.processtag(t.group(0))
|
|
131
|
+
extras = {"xp": self.currxpand} if self.expanded else {}
|
|
132
|
+
res = Tag(tagname, l=self.lindex, c=curri-self.lpos, **extras)
|
|
117
133
|
curri += t.end()
|
|
118
134
|
break
|
|
119
135
|
elif n == '|':
|
|
@@ -155,6 +171,13 @@ class Lexer:
|
|
|
155
171
|
self.cindex = m.end()
|
|
156
172
|
self.nexts.append(String(m.group(1)))
|
|
157
173
|
|
|
174
|
+
def processtag(self, t):
|
|
175
|
+
i = t.find("^")
|
|
176
|
+
if self.expanded and i >= 0:
|
|
177
|
+
self.currxpand = t[i+1:]
|
|
178
|
+
return t[:i]
|
|
179
|
+
return t
|
|
180
|
+
|
|
158
181
|
class Grammar:
|
|
159
182
|
category_markers = {
|
|
160
183
|
"attrib": "cp vp usfm ca va cat",
|
|
@@ -178,8 +201,14 @@ class Grammar:
|
|
|
178
201
|
"title": "mt1 mt2 mt3 mt4 mt",
|
|
179
202
|
"versepara": "cls nb pc pi1 pi2 pi3 pi po pr pmo pmc pmr pm ph1 ph2 ph3 ph p q1 q2 q3 q4 qc qr qm1 qm2 qm3 qm qd q b d mi1 mi2 mi3 mi4 mi m",
|
|
180
203
|
}
|
|
204
|
+
category_tags = {
|
|
205
|
+
"char": ("char", "crossreferencechar", "footnotechar", "introchar", "listchar"),
|
|
206
|
+
"para": ("header", "introduction", "list", "otherpara", "sectionpara", "title", "versepara"),
|
|
207
|
+
"ms": ("milestone", )
|
|
208
|
+
}
|
|
181
209
|
|
|
182
|
-
marker_categories = {t:k for k, v in category_markers.items() for t in v.split(
|
|
210
|
+
marker_categories = {t:k for k, v in category_markers.items() for t in v.split()}
|
|
211
|
+
marker_tags = {t:k for k, v in category_tags.items() for t in v}
|
|
183
212
|
|
|
184
213
|
attribmap = { 'jmp' : 'href', 'k' : 'key', 'qt-s': 'who', 'qt1-s': 'who', 'qt2-s': 'who',
|
|
185
214
|
'qt3-s': 'who', 'qt4-s': 'who', 'qt5-s': 'who', 'rb': 'gloss', 't-s': 'sid', 'ts-s': 'sid',
|
|
@@ -211,13 +240,15 @@ def isfirstText(e):
|
|
|
211
240
|
return True
|
|
212
241
|
|
|
213
242
|
class Node:
|
|
214
|
-
def __init__(self, parser, usxtag, tag, ispara=False, notag=False):
|
|
243
|
+
def __init__(self, parser, usxtag, tag, ispara=False, notag=False, pos=None, **kw):
|
|
215
244
|
self.parser = parser
|
|
216
245
|
self.tag = tag
|
|
217
246
|
self.ispara = ispara
|
|
218
247
|
parent = parser.stack[-1] if len(parser.stack) else None
|
|
219
|
-
|
|
220
|
-
|
|
248
|
+
attribs = kw
|
|
249
|
+
if not notag and tag is not None:
|
|
250
|
+
attribs['style'] = tag
|
|
251
|
+
self.element = parser.factory(usxtag, attribs, parent=getattr(parent, 'element', None), pos=pos)
|
|
221
252
|
if parent:
|
|
222
253
|
parent.addNodeElement(self.element)
|
|
223
254
|
self.attribnodes = []
|
|
@@ -227,7 +258,7 @@ class Node:
|
|
|
227
258
|
|
|
228
259
|
def appendElement(self, child):
|
|
229
260
|
if isinstance(child, OptBreak):
|
|
230
|
-
c = self.parser.factory("optbreak", {}, parent=self.element)
|
|
261
|
+
c = self.parser.factory("optbreak", {}, parent=self.element, pos=child.pos)
|
|
231
262
|
self.element.append(c)
|
|
232
263
|
self.clearAttribNodes()
|
|
233
264
|
|
|
@@ -297,11 +328,12 @@ class USXNode(Node):
|
|
|
297
328
|
self.element.set('version', str(t).strip())
|
|
298
329
|
|
|
299
330
|
class AttribNode(Node):
|
|
300
|
-
def __init__(self, parser, parent, tag):
|
|
331
|
+
def __init__(self, parser, parent, tag, pos=None, **kw):
|
|
301
332
|
self.parser = parser
|
|
302
333
|
self.parent = parent
|
|
303
334
|
self.parent.addAttribNode(self)
|
|
304
335
|
self.tag = tag
|
|
336
|
+
self.pos = pos
|
|
305
337
|
self.attribnodes = []
|
|
306
338
|
|
|
307
339
|
def addNodeElement(self, e):
|
|
@@ -313,8 +345,8 @@ class AttribNode(Node):
|
|
|
313
345
|
self.parent.element.set(attrib, str(t).strip())
|
|
314
346
|
|
|
315
347
|
class NumberNode(Node):
|
|
316
|
-
def __init__(self, parser, usxtag, tag, ispara=False):
|
|
317
|
-
super().__init__(parser, usxtag, tag, ispara=ispara)
|
|
348
|
+
def __init__(self, parser, usxtag, tag, ispara=False, pos=None, **kw):
|
|
349
|
+
super().__init__(parser, usxtag, tag, ispara=ispara, pos=pos, **kw)
|
|
318
350
|
self.hasarg = False
|
|
319
351
|
|
|
320
352
|
def appendText(self, t):
|
|
@@ -327,7 +359,7 @@ class NumberNode(Node):
|
|
|
327
359
|
if len(b) > 1 and b[1].strip():
|
|
328
360
|
self.parser.removeTag(self.tag)
|
|
329
361
|
if self.ispara:
|
|
330
|
-
self.parser.addNode(Node(self.parser, 'para', 'p'))
|
|
362
|
+
self.parser.addNode(Node(self.parser, 'para', 'p', pos=self.element.pos))
|
|
331
363
|
self.parser.stack[-1].appendText(b[1])
|
|
332
364
|
|
|
333
365
|
def addNodeElement(self, e):
|
|
@@ -335,8 +367,8 @@ class NumberNode(Node):
|
|
|
335
367
|
self.parser.stack[-1].addNodeElement(e)
|
|
336
368
|
|
|
337
369
|
class NoteNode(Node):
|
|
338
|
-
def __init__(self, parser, usxtag, tag):
|
|
339
|
-
super().__init__(parser, usxtag, tag)
|
|
370
|
+
def __init__(self, parser, usxtag, tag, pos=None, **kw):
|
|
371
|
+
super().__init__(parser, usxtag, tag, pos=pos, **kw)
|
|
340
372
|
self.hascaller = False
|
|
341
373
|
|
|
342
374
|
def appendText(self, t):
|
|
@@ -358,38 +390,51 @@ class PeriphNode(Node):
|
|
|
358
390
|
if t:
|
|
359
391
|
self.element.set('alt', str(t).strip())
|
|
360
392
|
|
|
393
|
+
class UnknownNode(Node):
|
|
394
|
+
pass
|
|
395
|
+
|
|
361
396
|
|
|
362
397
|
paratypes = ('header', 'introduction', 'list', 'otherpara', 'sectionpara', 'versepara', 'title', 'chapter', 'ident')
|
|
363
398
|
paratags = ('rem', ' table')
|
|
364
|
-
def setupParser(cls):
|
|
365
|
-
# tag closed types
|
|
366
|
-
for a in (('introchar', 'char'), ('listchar', 'char'), ('char', 'char'), ('_fig', 'figure')):
|
|
367
|
-
def maketype(c, t):
|
|
368
|
-
def dotype(self, tag):
|
|
369
|
-
if tag.isend:
|
|
370
|
-
return self.removeTag(str(tag))
|
|
371
|
-
return self.addNode(Node(self, t, tag.basestr()))
|
|
372
|
-
return dotype
|
|
373
|
-
setattr(cls, a[0], maketype(*a))
|
|
374
|
-
# implicit closed paras
|
|
375
|
-
for a in paratypes:
|
|
376
|
-
def dotype(self, tag):
|
|
377
|
-
self.removeType(paratypes, paratags)
|
|
378
|
-
return self.addNode(Node(self, 'para', str(tag), ispara=True))
|
|
379
|
-
if not hasattr(cls, a):
|
|
380
|
-
setattr(cls, a, dotype)
|
|
381
|
-
|
|
382
399
|
|
|
383
400
|
class USFMParser:
|
|
384
401
|
|
|
385
|
-
def __init__(self, txt, factory=None, grammar=None):
|
|
402
|
+
def __init__(self, txt, factory=None, grammar=None, expanded=False, strict=False):
|
|
386
403
|
if factory is None:
|
|
387
|
-
|
|
404
|
+
def makeel(tag, attrib, **extras):
|
|
405
|
+
attrib.update({" "+k:v for k, v in extras.items()})
|
|
406
|
+
return et.Element(tag, attrib)
|
|
407
|
+
factory = makeel
|
|
388
408
|
if grammar is None:
|
|
389
409
|
grammar = Grammar()
|
|
390
410
|
self.factory = factory
|
|
391
411
|
self.grammar = grammar
|
|
392
|
-
self.
|
|
412
|
+
self._setup(expanded=expanded)
|
|
413
|
+
self.lexer = Lexer(txt, expanded=expanded)
|
|
414
|
+
|
|
415
|
+
def _setup(s, expanded=False):
|
|
416
|
+
clsself = s.__class__
|
|
417
|
+
# tag closed types
|
|
418
|
+
for a in (('introchar', 'char'), ('listchar', 'char'), ('char', 'char'), ('_fig', 'figure')):
|
|
419
|
+
def maketype(c, t):
|
|
420
|
+
def dotype(self, tag):
|
|
421
|
+
if tag.isend:
|
|
422
|
+
return self.removeTag(str(tag))
|
|
423
|
+
return self.addNode(Node(self, t, tag.basestr()))
|
|
424
|
+
return dotype
|
|
425
|
+
setattr(clsself, a[-1], maketype(*a))
|
|
426
|
+
# implicit closed paras
|
|
427
|
+
for a in paratypes:
|
|
428
|
+
if expanded:
|
|
429
|
+
def dotype(self, tag):
|
|
430
|
+
self.removeType(paratypes, paratags)
|
|
431
|
+
return self.addNode(Node(self, 'para', str(tag), ispara=True, xpand=tag.xp))
|
|
432
|
+
else:
|
|
433
|
+
def dotype(self, tag):
|
|
434
|
+
self.removeType(paratypes, paratags)
|
|
435
|
+
return self.addNode(Node(self, 'para', str(tag), ispara=True))
|
|
436
|
+
if not hasattr(clsself, a):
|
|
437
|
+
setattr(clsself, a, dotype)
|
|
393
438
|
|
|
394
439
|
def parse(self):
|
|
395
440
|
self.result = []
|
|
@@ -439,6 +484,8 @@ class USFMParser:
|
|
|
439
484
|
while len(self.stack):
|
|
440
485
|
curr = self.stack.pop()
|
|
441
486
|
if curr.tag == tag:
|
|
487
|
+
if curr.element.tag == "unk":
|
|
488
|
+
curr.element.tag = "char"
|
|
442
489
|
break
|
|
443
490
|
else:
|
|
444
491
|
self.stack = oldstack
|
|
@@ -450,12 +497,31 @@ class USFMParser:
|
|
|
450
497
|
oldstack = self.stack[:]
|
|
451
498
|
while len(self.stack):
|
|
452
499
|
curr = self.stack.pop()
|
|
500
|
+
e = curr.element
|
|
501
|
+
if e.tag == "usx":
|
|
502
|
+
self.stack.append(curr)
|
|
503
|
+
break
|
|
453
504
|
curr.close()
|
|
454
|
-
|
|
505
|
+
cat = self.grammar.marker_categories.get(e.tag, None)
|
|
506
|
+
if e.tag == "unk":
|
|
507
|
+
e.tag = self.grammar.marker_tags.get(t[0], "para")
|
|
508
|
+
if e.tag == 'para' and e.parent is not None:
|
|
509
|
+
parent = e.parent
|
|
510
|
+
while parent is not None and parent.tag not in ("usx", "para"):
|
|
511
|
+
parent = parent.parent
|
|
512
|
+
if parent.tag != "usx":
|
|
513
|
+
i, p = parent._getindex()
|
|
514
|
+
if p is not None:
|
|
515
|
+
e.parent.remove(e)
|
|
516
|
+
p.insert(i+1, e)
|
|
517
|
+
e.parent = p
|
|
518
|
+
self.removeType(t, tags=tags)
|
|
519
|
+
break
|
|
520
|
+
if curr.tag in tags or cat in t:
|
|
455
521
|
break
|
|
456
522
|
else:
|
|
457
523
|
self.stack = oldstack
|
|
458
|
-
return self.stack[-1]
|
|
524
|
+
return self.stack[-1] if len(self.stack) else None
|
|
459
525
|
|
|
460
526
|
#### Event methods
|
|
461
527
|
|
|
@@ -467,31 +533,31 @@ class USFMParser:
|
|
|
467
533
|
if tag.isend:
|
|
468
534
|
return self.removeTag(str(tag))
|
|
469
535
|
elif self.stack[-1].tag == "c":
|
|
470
|
-
parent = AttribNode(self, self.stack[-1], str(tag))
|
|
536
|
+
parent = AttribNode(self, self.stack[-1], str(tag), pos=tag.pos)
|
|
471
537
|
else:
|
|
472
|
-
parent = Node(self, 'para', str(tag), ispara=True)
|
|
538
|
+
parent = Node(self, 'para', str(tag), ispara=True, pos=tag.pos)
|
|
473
539
|
self.stack.append(parent)
|
|
474
540
|
return parent
|
|
475
541
|
|
|
476
542
|
def _esb(self, tag):
|
|
477
543
|
self.removeType(paratypes, paratags)
|
|
478
|
-
return self.addNode(Node(self, "sidebar", str(tag)))
|
|
544
|
+
return self.addNode(Node(self, "sidebar", str(tag), pos=tag.pos))
|
|
479
545
|
|
|
480
546
|
def _esbe(self, tag):
|
|
481
547
|
return self.removeTag("esb")
|
|
482
548
|
|
|
483
549
|
def _periph(self, tag):
|
|
484
550
|
self.removeTag(str(tag))
|
|
485
|
-
return self.addNode(PeriphNode(self, "periph", str(tag), notag=True))
|
|
551
|
+
return self.addNode(PeriphNode(self, "periph", str(tag), notag=True, pos=tag.pos))
|
|
486
552
|
|
|
487
553
|
def _ref(self, tag):
|
|
488
554
|
if tag.isend:
|
|
489
555
|
return self.removeTag(str(tag))
|
|
490
|
-
return self.addNode(Node(self, 'ref', tag.basestr(), notag=True))
|
|
556
|
+
return self.addNode(Node(self, 'ref', tag.basestr(), notag=True, pos=tag.pos))
|
|
491
557
|
|
|
492
558
|
def _rem(self, tag):
|
|
493
559
|
self.removeType(paratypes, paratags)
|
|
494
|
-
res = self.addNode(Node(self, 'para', str(tag), ispara=True))
|
|
560
|
+
res = self.addNode(Node(self, 'para', str(tag), ispara=True, pos=tag.pos))
|
|
495
561
|
self.lexer.readLine()
|
|
496
562
|
return res
|
|
497
563
|
|
|
@@ -499,23 +565,23 @@ class USFMParser:
|
|
|
499
565
|
self.removeTag('tr')
|
|
500
566
|
if not len(self.stack) or self.stack[-1].element.tag != "table":
|
|
501
567
|
self.removeType(paratypes,)
|
|
502
|
-
self.addNode(Node(self, 'table', ' table', notag=True))
|
|
503
|
-
return self.addNode(Node(self, 'row', 'tr'))
|
|
568
|
+
self.addNode(Node(self, 'table', ' table', notag=True, pos=tag.pos))
|
|
569
|
+
return self.addNode(Node(self, 'row', 'tr', pos=tag.pos))
|
|
504
570
|
|
|
505
571
|
def _v(self, tag):
|
|
506
572
|
self.removeTag('v')
|
|
507
573
|
if len(self.stack) and self.stack[-1].tag == "c":
|
|
508
574
|
self.removeTag('c')
|
|
509
|
-
self.addNode(Node(self, 'para', 'p'))
|
|
510
|
-
return self.addNode(NumberNode(self, "verse", str(tag)))
|
|
575
|
+
self.addNode(Node(self, 'para', 'p', pos=tag.pos))
|
|
576
|
+
return self.addNode(NumberNode(self, "verse", str(tag), pos=tag.pos))
|
|
511
577
|
|
|
512
578
|
def _vp(self, tag):
|
|
513
579
|
if tag.isend:
|
|
514
580
|
return self.removeTag(str(tag))
|
|
515
581
|
elif self.stack[-1].tag == "v":
|
|
516
|
-
parent = AttribNode(self, self.stack[-1], str(tag))
|
|
582
|
+
parent = AttribNode(self, self.stack[-1], str(tag), pos=tag.pos)
|
|
517
583
|
else:
|
|
518
|
-
parent = Node(self, 'para', tag.basestr())
|
|
584
|
+
parent = Node(self, 'para', tag.basestr(), pos=tag.pos)
|
|
519
585
|
self.stack.append(parent)
|
|
520
586
|
return parent
|
|
521
587
|
|
|
@@ -523,43 +589,43 @@ class USFMParser:
|
|
|
523
589
|
if self.grammar.marker_categories.get(self.stack[-1].tag, "") == "crossreferencechar":
|
|
524
590
|
res = self.removeType('crossreferencechar')
|
|
525
591
|
if not tag.isend:
|
|
526
|
-
res = self.addNode(Node(self, 'char', tag.basestr()))
|
|
592
|
+
res = self.addNode(Node(self, 'char', tag.basestr(), pos=tag.pos))
|
|
527
593
|
return res
|
|
528
594
|
|
|
529
595
|
def attrib(self, tag):
|
|
530
596
|
if tag.isend:
|
|
531
597
|
return self.removeTag(str(tag))
|
|
532
598
|
else:
|
|
533
|
-
parent = AttribNode(self, self.stack[-1], str(tag))
|
|
599
|
+
parent = AttribNode(self, self.stack[-1], str(tag), pos=tag.pos)
|
|
534
600
|
self.stack.append(parent)
|
|
535
601
|
return parent
|
|
536
602
|
|
|
537
603
|
def cell(self, tag):
|
|
538
604
|
self.removeType('cell')
|
|
539
605
|
if not tag.isend:
|
|
540
|
-
return self.addNode(Node(self, 'cell', str(tag)))
|
|
606
|
+
return self.addNode(Node(self, 'cell', str(tag), pos=tag.pos))
|
|
541
607
|
return self.parent
|
|
542
608
|
|
|
543
609
|
def crossreference(self, tag):
|
|
544
610
|
if tag.isend:
|
|
545
611
|
return self.removeTag(str(tag))
|
|
546
|
-
return self.addNode(NoteNode(self, 'note', str(tag)))
|
|
612
|
+
return self.addNode(NoteNode(self, 'note', str(tag), pos=tag.pos))
|
|
547
613
|
|
|
548
614
|
def crossreferencechar(self, tag):
|
|
549
615
|
res = self.removeType('crossreferencechar')
|
|
550
616
|
if not tag.isend:
|
|
551
|
-
res = self.addNode(Node(self, 'char', tag.basestr()))
|
|
617
|
+
res = self.addNode(Node(self, 'char', tag.basestr(), pos=tag.pos))
|
|
552
618
|
return res
|
|
553
619
|
|
|
554
620
|
def footnote(self, tag):
|
|
555
621
|
if tag.isend:
|
|
556
622
|
return self.removeTag(str(tag))
|
|
557
|
-
return self.addNode(NoteNode(self, 'note', str(tag)))
|
|
623
|
+
return self.addNode(NoteNode(self, 'note', str(tag), pos=tag.pos))
|
|
558
624
|
|
|
559
625
|
def footnotechar(self, tag):
|
|
560
626
|
res = self.removeType('footnotechar')
|
|
561
627
|
if not tag.isend:
|
|
562
|
-
res = self.addNode(Node(self, 'char', tag.basestr()))
|
|
628
|
+
res = self.addNode(Node(self, 'char', tag.basestr(), pos=tag.pos))
|
|
563
629
|
return res
|
|
564
630
|
|
|
565
631
|
def ident(self, tag):
|
|
@@ -570,15 +636,22 @@ class USFMParser:
|
|
|
570
636
|
def milestone(self, tag):
|
|
571
637
|
if tag.isend:
|
|
572
638
|
return self.removeType('milestone')
|
|
573
|
-
return self.addNode(Node(self, 'ms', tag.basestr()))
|
|
639
|
+
return self.addNode(Node(self, 'ms', tag.basestr(), pos=tag.pos))
|
|
574
640
|
|
|
575
641
|
def unknown(self, tag):
|
|
576
|
-
|
|
577
|
-
|
|
642
|
+
if strict:
|
|
643
|
+
if len(self.stack):
|
|
644
|
+
raise SyntaxError(f"Unknown tag {tag} in {self.stack[-1]}")
|
|
645
|
+
else:
|
|
646
|
+
raise SyntaxError(f"Unknown tag {tag}")
|
|
647
|
+
return None
|
|
648
|
+
if not tag.isend:
|
|
649
|
+
res = self.addNode(UnknownNode(self, 'unk', str(tag), pos=tag.pos))
|
|
650
|
+
else:
|
|
651
|
+
res = self.removeTag(str(tag))
|
|
652
|
+
res.tag = "char"
|
|
578
653
|
return res
|
|
579
654
|
|
|
580
|
-
setupParser(USFMParser)
|
|
581
|
-
|
|
582
655
|
def main():
|
|
583
656
|
import sys
|
|
584
657
|
p = USFMParser(sys.argv[1])
|
|
@@ -3,12 +3,13 @@ import re
|
|
|
3
3
|
import xml.etree.ElementTree as et
|
|
4
4
|
|
|
5
5
|
class ParentElement(et.Element):
|
|
6
|
-
def __init__(self, tag, attrib=None, parent=None):
|
|
6
|
+
def __init__(self, tag, attrib=None, parent=None, pos=None):
|
|
7
7
|
et.Element.__init__(self, tag, attrib)
|
|
8
8
|
self.parent = parent
|
|
9
|
+
self.pos = pos
|
|
9
10
|
|
|
10
|
-
def makeelement(self, tag, attrib):
|
|
11
|
-
return self.__class__(tag, attrib, parent=self)
|
|
11
|
+
def makeelement(self, tag, attrib, pos=None):
|
|
12
|
+
return self.__class__(tag, attrib, parent=self, pos=pos or self.pos)
|
|
12
13
|
|
|
13
14
|
def __str__(self):
|
|
14
15
|
return "{}[{}]".format(self.tag, " ".join('@{}="{}"'.format(k, v) for k, v in self.attrib.items()))
|
|
@@ -18,19 +19,24 @@ class ParentElement(et.Element):
|
|
|
18
19
|
return "{}/{}".format(p, str(self))
|
|
19
20
|
|
|
20
21
|
def _getindex(self):
|
|
22
|
+
''' Finds my index in my parent, returning both '''
|
|
21
23
|
if self.parent is None:
|
|
22
24
|
return -1, None
|
|
23
25
|
return list(self.parent).index(self), self.parent
|
|
24
26
|
|
|
25
27
|
def getprevious(self):
|
|
28
|
+
''' Returns the previous element with the same parent, if any '''
|
|
26
29
|
i, parent = self._getindex()
|
|
27
30
|
return parent[i-1] if parent is not None and i > 0 else None
|
|
28
31
|
|
|
29
32
|
def getnext(self):
|
|
33
|
+
''' Returns the next element with the same parent, if any '''
|
|
30
34
|
i, parent = self._getindex()
|
|
31
35
|
return parent[i+1] if parent is not None and i < len(parent) - 1 else None
|
|
32
36
|
|
|
33
37
|
def getnext_sibling(self):
|
|
38
|
+
''' Returns the next element, while going up and down the hierarchy.
|
|
39
|
+
Notice that any text at the start of an element stops any hierarchy descent '''
|
|
34
40
|
i, parent = self._getindex()
|
|
35
41
|
if i < len(parent) - 1:
|
|
36
42
|
return parent[i+1]
|
|
@@ -41,6 +47,8 @@ class ParentElement(et.Element):
|
|
|
41
47
|
return parent
|
|
42
48
|
|
|
43
49
|
def getprevious_sibling(self):
|
|
50
|
+
''' Returns the previous element, while going up and down the hierarchy
|
|
51
|
+
Notice that any text after an element stops any hierarchy descent '''
|
|
44
52
|
i, parent = self._getindex()
|
|
45
53
|
if i > 0:
|
|
46
54
|
return parent[i-1]
|
|
@@ -51,11 +59,13 @@ class ParentElement(et.Element):
|
|
|
51
59
|
return parent
|
|
52
60
|
|
|
53
61
|
def addprevious(self, el):
|
|
62
|
+
''' Inserts el before the current element in the same parent '''
|
|
54
63
|
i, parent = self._getindex()
|
|
55
64
|
if parent is not None:
|
|
56
65
|
parent.insert(i, el)
|
|
57
66
|
|
|
58
67
|
def getparent(self):
|
|
68
|
+
''' Returns the parent '''
|
|
59
69
|
return self.parent
|
|
60
70
|
|
|
61
71
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: usfmtc
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: USFM reference parser
|
|
5
5
|
Author-email: USFM Technical Committee <fonts@sil.org>
|
|
6
6
|
License: Copyright © 2022-2023 by the members of the USFM/USX technical committee, listed below:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|