usfmtc 0.3.2__tar.gz → 0.3.3__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.3.2/src/usfmtc.egg-info → usfmtc-0.3.3}/PKG-INFO +1 -1
- {usfmtc-0.3.2 → usfmtc-0.3.3}/pyproject.toml +1 -1
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/reference.py +113 -95
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/usxmodel.py +128 -20
- {usfmtc-0.3.2 → usfmtc-0.3.3/src/usfmtc.egg-info}/PKG-INFO +1 -1
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc.egg-info/SOURCES.txt +1 -0
- usfmtc-0.3.3/tests/test_parser.py +40 -0
- usfmtc-0.3.3/tests/test_refs.py +365 -0
- usfmtc-0.3.2/tests/test_refs.py +0 -199
- {usfmtc-0.3.2 → usfmtc-0.3.3}/LICENSE +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/setup.cfg +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/__init__.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/eng.vrs +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/extension.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/lxmlutils.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/usfmgenerate.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/usfmparser.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/usjproc.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/usx.rng +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/validating/rngparser.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/validating/usfmgrammar.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/validating/usfmparser.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/validating/usxparser.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc/xmlutils.py +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc.egg-info/dependency_links.txt +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc.egg-info/entry_points.txt +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc.egg-info/requires.txt +0 -0
- {usfmtc-0.3.2 → usfmtc-0.3.3}/src/usfmtc.egg-info/top_level.txt +0 -0
|
@@ -97,35 +97,6 @@ class MarkerRef:
|
|
|
97
97
|
res.append(str(self.char))
|
|
98
98
|
return "".join(res)
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
_regexes = {
|
|
102
|
-
"book": r"""(?P<transid>(?:[a-z0-9_-]*[+])*)
|
|
103
|
-
(?P<book>(?:[A-Z][A-Z0-9][A-Z0-9]|[0-9](?:[A-Z][A-Z]|[0-9][A-Z]|[A-Z][0-9])))
|
|
104
|
-
\s*{chap}""",
|
|
105
|
-
"id": r"(?:[a-z][a-z0-9_-]*[a-z0-9]?)",
|
|
106
|
-
"charref": r"(?:(?:[0-9]|end)[+]?)",
|
|
107
|
-
"wordrefanon": r"",
|
|
108
|
-
"mrkref": r"(?:\!{id}(?:\[[0-9]\])?(?:\!(?:[0-9|end)(?:[+]{charref})?)?)",
|
|
109
|
-
"wordrefonly": r"(?P<word>[0-9]|end)(?P<char>[+]{charref})?",
|
|
110
|
-
"wordref1": r"""(?:(?: \!(?P<word1>[0-9]|end)(?:[+](?P<char1>{charref}))?
|
|
111
|
-
|(?P<mrk1>{mrkref}))
|
|
112
|
-
(?P<mrkn1>{mrkref}*))""",
|
|
113
|
-
"wordref2": r"""(?:(?: \!(?P<word2>[0-9]|end)(?:[+](?P<char2>{charref}))?
|
|
114
|
-
|(?P<mrk2>{mrkref}))
|
|
115
|
-
(?P<mrkn2>{mrkref}*))""",
|
|
116
|
-
"verse1": r"(?P<verse1>[0-9]+)(?P<subv1>[a-z]?|end)",
|
|
117
|
-
"verse2": r"(?P<verse2>[0-9]+)(?P<subv2>[a-z]?|end)",
|
|
118
|
-
"chapter": r"(?:(?P<chap>[0-9]+))",
|
|
119
|
-
"chap": r"{chapter}(?:[:.]{verse1})?{wordref1}?",
|
|
120
|
-
"context": r"""(?:{chap}
|
|
121
|
-
| {verse2}{wordref2}? | {wordrefonly}(?P<mrkn3>{mrkref}*)
|
|
122
|
-
| (?P<char3>{charref})(?P<mrkn4>{mrkref}*))(?=[-;,\s]|$)"""
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
regexes = _regexes
|
|
126
|
-
for i in range(3):
|
|
127
|
-
regexes = {k: v.format(**regexes) for k, v in regexes.items()}
|
|
128
|
-
|
|
129
100
|
def intend(s: str) -> Optional[int]:
|
|
130
101
|
if not s:
|
|
131
102
|
return None
|
|
@@ -198,6 +169,35 @@ def readvrs(fname):
|
|
|
198
169
|
return res
|
|
199
170
|
|
|
200
171
|
|
|
172
|
+
_regexes = {
|
|
173
|
+
"book": r"""(?P<transid>(?:[a-z0-9_-]*[+])*)
|
|
174
|
+
(?P<book>(?:[A-Z][A-Z0-9][A-Z0-9]|[0-9](?:[A-Z][A-Z]|[0-9][A-Z]|[A-Z][0-9])))
|
|
175
|
+
\s*{chap}""",
|
|
176
|
+
"id": r"(?:[a-z][a-z0-9_-]*[a-z0-9]?)",
|
|
177
|
+
"charref": r"(?:(?:[0-9]|end)[+]?)",
|
|
178
|
+
"wordrefanon": r"",
|
|
179
|
+
"mrkref": r"(?:\!{id}(?:\[[0-9]\])?(?:\!(?:[0-9]|end)(?:[+]{charref})?)?)",
|
|
180
|
+
"wordrefonly": r"(?P<word>[0-9]|end)(?P<char>[+]{charref})?",
|
|
181
|
+
"wordref1": r"""(?:(?: \!(?P<word1>[0-9]|end)(?:[+](?P<char1>{charref}))?
|
|
182
|
+
|(?P<mrk1>{mrkref}))
|
|
183
|
+
(?P<mrkn1>{mrkref}*))""",
|
|
184
|
+
"wordref2": r"""(?:(?: \!(?P<word2>[0-9]|end)(?:[+](?P<char2>{charref}))?
|
|
185
|
+
|(?P<mrk2>{mrkref}))
|
|
186
|
+
(?P<mrkn2>{mrkref}*))""",
|
|
187
|
+
"verse1": r"(?P<verse1>[0-9]+|end)(?P<subv1>[a-z]?)",
|
|
188
|
+
"verse2": r"(?P<verse2>[0-9]+|end)(?P<subv2>[a-z]?)",
|
|
189
|
+
"chapter": r"(?:(?P<chap>[0-9]+|end))",
|
|
190
|
+
"chap": r"{chapter}(?:[:.]{verse1})?{wordref1}?",
|
|
191
|
+
"context": r"""(?:{chap}
|
|
192
|
+
| {verse2}{wordref2}? | {wordrefonly}(?P<mrkn3>{mrkref}*)
|
|
193
|
+
| (?P<char3>{charref})(?P<mrkn4>{mrkref}*))(?=[-;,\s]|$)"""
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
regexes = _regexes
|
|
197
|
+
for i in range(3):
|
|
198
|
+
regexes = {k: v.format(**regexes) for k, v in regexes.items()}
|
|
199
|
+
|
|
200
|
+
|
|
201
201
|
class Ref:
|
|
202
202
|
product: Optional[str]
|
|
203
203
|
book: Optional[str]
|
|
@@ -232,20 +232,21 @@ class Ref:
|
|
|
232
232
|
def __init__(self, string: Optional[str] = None,
|
|
233
233
|
context: Optional['Ref'] = None, start: int = 0, **kw):
|
|
234
234
|
if string is not None:
|
|
235
|
-
|
|
235
|
+
s = string.strip()
|
|
236
|
+
self.parse(s, context=(context.last if context is not None else None), start=start)
|
|
237
|
+
if self.strend < len(s):
|
|
238
|
+
raise SyntaxError(f"Extra content after reference {s[0:self.strend]} | {s[self.strend:]}")
|
|
236
239
|
return
|
|
237
240
|
|
|
238
241
|
if context is not None:
|
|
239
|
-
|
|
240
|
-
rel = None
|
|
241
|
-
if isempty:
|
|
242
|
-
rel = kw["word"]
|
|
243
|
-
kw["word"] = None
|
|
244
|
-
idx = [k for k in self._parmlist if getattr(context.last, k, None)][-1]
|
|
242
|
+
hitlimit = False
|
|
245
243
|
for a in self._parmlist:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
244
|
+
v = kw.get(a, None)
|
|
245
|
+
if v is not None:
|
|
246
|
+
hitlimit = True
|
|
247
|
+
elif not hitlimit:
|
|
248
|
+
v = getattr(context.last, a, None)
|
|
249
|
+
setattr(self, a, v)
|
|
249
250
|
else:
|
|
250
251
|
for a in self._parmlist:
|
|
251
252
|
setattr(self, a, kw.get(a, None))
|
|
@@ -254,54 +255,31 @@ class Ref:
|
|
|
254
255
|
if "-" in s:
|
|
255
256
|
return
|
|
256
257
|
p = {}
|
|
258
|
+
s = s.strip()
|
|
257
259
|
if m := self._rebook.match(s, pos=start):
|
|
258
260
|
p['product'] = m.group('transid') or None
|
|
259
261
|
p['book'] = m.group('book')
|
|
260
|
-
p['chapter'] = int(m.group('chap')) if m.group('chap') else None
|
|
261
|
-
p['verse'] = intend(m.group('verse1'))
|
|
262
|
-
p['subverse'] = m.group('subv1') or None
|
|
263
|
-
p['word'] = intend(m.group('word1'))
|
|
264
|
-
p['char'] = intend(m.group('char1'))
|
|
265
|
-
p['mrkrs'] = asmarkers(m.group("mrk1"), m.group("mrkn1"))
|
|
266
262
|
elif not (m:= self._recontext.match(s, pos=start)):
|
|
267
263
|
raise SyntaxError("Cannot parse {}".format(s))
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
p['mrkrs'] = asmarkers("", m.group('mrkn4'))
|
|
288
|
-
elif not m.group('chap') and m.group('word1'):
|
|
289
|
-
p['word'] = intend(m.group('word1'))
|
|
290
|
-
p['char'] = intend(m.group('char1'))
|
|
291
|
-
p['mrkrs'] = asmarkers(m.group('mrk1'), m.group('mrkn1'))
|
|
292
|
-
elif context is not None and context.char:
|
|
293
|
-
p['char'] = intend(m.group('chap'))
|
|
294
|
-
elif context is not None and context.word:
|
|
295
|
-
p['word'] = intend(m.group('chap'))
|
|
296
|
-
p['char'] = intend(m.group('char1'))
|
|
297
|
-
elif context is not None and context.verse:
|
|
298
|
-
p['verse'] = intend(m.group('chap'))
|
|
299
|
-
p['word'] = intend(m.group('word1'))
|
|
300
|
-
p['char'] = intend(m.group('char1'))
|
|
301
|
-
else:
|
|
302
|
-
p['chapter'] = int(m.group('chap'))
|
|
303
|
-
p['word'] = intend(m.group('word1'))
|
|
304
|
-
p['char'] = intend(m.group('char1'))
|
|
264
|
+
gs = m.groupdict()
|
|
265
|
+
p['chapter'] = intend(gs.get('chap', None))
|
|
266
|
+
p['verse'] = intend(gs.get('verse1', gs.get('verse2', None)))
|
|
267
|
+
p['subverse'] = gs.get('subv1', gs.get('subv2', None)) or None
|
|
268
|
+
p['word'] = intend(gs.get('word1', gs.get('word2', gs.get('word', None))))
|
|
269
|
+
p['char'] = intend(gs.get('char', gs.get('char1', gs.get('char2', None))))
|
|
270
|
+
mnum = "2" if 'mrk2' in gs else "1"
|
|
271
|
+
p['mrkrs'] = asmarkers(gs.get('mrk'+mnum, None), gs.get('mrkn'+mnum, None))
|
|
272
|
+
if 'book' not in p and context is not None and p['chapter'] is not None and p['verse'] is None and p['word'] is None:
|
|
273
|
+
rep = None
|
|
274
|
+
if context.char is not None:
|
|
275
|
+
rep = 'char'
|
|
276
|
+
elif context.word is not None:
|
|
277
|
+
rep = 'word'
|
|
278
|
+
elif context.verse is not None:
|
|
279
|
+
rep = 'verse'
|
|
280
|
+
if rep is not None:
|
|
281
|
+
p[rep] = p['chapter']
|
|
282
|
+
p['chapter'] = None
|
|
305
283
|
self.strend = m.end(0)
|
|
306
284
|
if p.get('mrkrs', None) == []:
|
|
307
285
|
p['mrkrs'] = None
|
|
@@ -319,7 +297,7 @@ class Ref:
|
|
|
319
297
|
def __eq__(self, o):
|
|
320
298
|
if not isinstance(o, Ref):
|
|
321
299
|
return False
|
|
322
|
-
res = all(getattr(self, a) == getattr(o, a) for a in self._parmlist)
|
|
300
|
+
res = all(getattr(self, a) is None or getattr(o, a) is None or getattr(self, a) == getattr(o, a) for a in self._parmlist)
|
|
323
301
|
return res
|
|
324
302
|
|
|
325
303
|
def __contains__(self, o):
|
|
@@ -409,7 +387,7 @@ class Ref:
|
|
|
409
387
|
if self.mrkrs is not None and len(self.mrkrs):
|
|
410
388
|
forcemkr = False
|
|
411
389
|
for i, m in enumerate(self.mrkrs):
|
|
412
|
-
if not forcemkr and context is not None and i < len(context.mrkrs):
|
|
390
|
+
if not forcemkr and context is not None and context.mrkrs is not None and i < len(context.mrkrs):
|
|
413
391
|
if context.mrkrs[i] != m:
|
|
414
392
|
res.append(m.str(context=contex.mrkrs[i], force=force or len(res)))
|
|
415
393
|
forcemkr = True
|
|
@@ -429,27 +407,57 @@ class Ref:
|
|
|
429
407
|
kw = {k: getattr(self, k) for k in self._parmlist}
|
|
430
408
|
return self.__class__(**kw)
|
|
431
409
|
|
|
432
|
-
def
|
|
410
|
+
def _setall(self, val):
|
|
433
411
|
res = self.copy()
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
412
|
+
for a in ('chapter', 'verse', 'word', 'char'):
|
|
413
|
+
if getattr(self, a, None) is None:
|
|
414
|
+
setattr(res, a, val)
|
|
415
|
+
return res
|
|
416
|
+
|
|
417
|
+
def end(self):
|
|
418
|
+
if self.last != self:
|
|
419
|
+
self.last = end(self.last)
|
|
420
|
+
return self.last
|
|
421
|
+
res = self._setall(-1)
|
|
422
|
+
if res.chapter == -1:
|
|
423
|
+
vrs = self.versification or Ref.loadversification()
|
|
424
|
+
book = books.get(self.book, None)
|
|
425
|
+
if book is not None:
|
|
426
|
+
res.chapter = len(vrs[book])
|
|
427
|
+
if res.verse == -1:
|
|
428
|
+
res.verse = self._getmaxvrs(self.book, self.chapter)
|
|
442
429
|
return res
|
|
443
430
|
|
|
431
|
+
def start(self):
|
|
432
|
+
if self.first != self:
|
|
433
|
+
self.first = start(self.first)
|
|
434
|
+
return self.first
|
|
435
|
+
return self._setall(1)
|
|
436
|
+
|
|
437
|
+
def expand(self):
|
|
438
|
+
return RefRange(self.start(), self.end())
|
|
439
|
+
|
|
444
440
|
def _getmaxvrs(self, bk, chap):
|
|
445
441
|
vrs = self.first.versification or Ref.loadversification()
|
|
446
442
|
book = books.get(bk, None)
|
|
447
443
|
if book is None or len(vrs[book]) <= chap:
|
|
448
|
-
maxvrs =
|
|
444
|
+
maxvrs = -1
|
|
449
445
|
else:
|
|
450
446
|
maxvrs = vrs[book][chap] - (vrs[book][chap-1] if chap > 1 else 0)
|
|
451
447
|
return maxvrs
|
|
452
448
|
|
|
449
|
+
def isvalid(self):
|
|
450
|
+
vrs = self.first.versification or Ref.loadversification()
|
|
451
|
+
if self.book not in books:
|
|
452
|
+
return False
|
|
453
|
+
book = vrs[books.get(self.book)]
|
|
454
|
+
if len(book) < self.chapter:
|
|
455
|
+
return False
|
|
456
|
+
if book[self.chapter] < self.verse:
|
|
457
|
+
return False
|
|
458
|
+
# checking subverse, word and char involves having access to the document
|
|
459
|
+
return True
|
|
460
|
+
|
|
453
461
|
def nextverse(self):
|
|
454
462
|
r = self.first.copy()
|
|
455
463
|
maxvrs = self._getmaxvrs(r.book, r.chapter)
|
|
@@ -458,7 +466,7 @@ class Ref:
|
|
|
458
466
|
else:
|
|
459
467
|
r.verse += 1
|
|
460
468
|
if r.verse > maxvrs:
|
|
461
|
-
r.chapter
|
|
469
|
+
r.chapter = (r.chapter + 1) if r.chapter is not None else 1
|
|
462
470
|
r.verse = 1
|
|
463
471
|
if r.book not in books:
|
|
464
472
|
r.book = "GEN"
|
|
@@ -483,6 +491,8 @@ class RefRange:
|
|
|
483
491
|
def __init__(self, first: Optional[Ref]=None, last: Optional[Ref]=None):
|
|
484
492
|
self.first = first
|
|
485
493
|
self.last = last
|
|
494
|
+
if self.last < self.first:
|
|
495
|
+
raise ValueError(f"{first=} is after {last=}")
|
|
486
496
|
|
|
487
497
|
def str(self, context: Optional[Ref] = None, force: bool = False):
|
|
488
498
|
res = [self.first.str(context, force=force)]
|
|
@@ -523,6 +533,14 @@ class RefRange:
|
|
|
523
533
|
def strend(self):
|
|
524
534
|
return self.last.strend
|
|
525
535
|
|
|
536
|
+
def expand(self):
|
|
537
|
+
self.first = self.first.start()
|
|
538
|
+
self.last = self.last.end()
|
|
539
|
+
return self
|
|
540
|
+
|
|
541
|
+
def isvalid(self):
|
|
542
|
+
return self.first.isvalid() and self.last.isvalid()
|
|
543
|
+
|
|
526
544
|
def allverses(self):
|
|
527
545
|
r = self.first
|
|
528
546
|
while r is not None and r <= self.last:
|
|
@@ -3,6 +3,7 @@ import re
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from usfmtc.xmlutils import isempty
|
|
5
5
|
from usfmtc.usfmparser import Grammar
|
|
6
|
+
from usfmtc.reference import Ref
|
|
6
7
|
import xml.etree.ElementTree as et
|
|
7
8
|
from typing import Optional
|
|
8
9
|
|
|
@@ -259,11 +260,11 @@ def strnormal(s, t, mode=0):
|
|
|
259
260
|
'''
|
|
260
261
|
if s is None:
|
|
261
262
|
return ""
|
|
262
|
-
res = re.sub("[\n
|
|
263
|
+
res = re.sub("[\n\t\r ]+", " ", s) if t in ('para', 'char') else s
|
|
263
264
|
if mode & 1 == 1:
|
|
264
|
-
res = res.lstrip()
|
|
265
|
+
res = res.lstrip(" \t\n\r")
|
|
265
266
|
if mode & 2 == 2:
|
|
266
|
-
res = res.rstrip()
|
|
267
|
+
res = res.rstrip(" \t\n\r")
|
|
267
268
|
res = re.sub(r"[ \n]*\n[ ]*", "\n", res)
|
|
268
269
|
for k, v in unescapes.items():
|
|
269
270
|
if k in res:
|
|
@@ -442,6 +443,8 @@ def iterusx(root, parindex=0, start=None, blocks=[], filt=[], unblocks=False, gr
|
|
|
442
443
|
yield from runiter(root, parindex=parindex, started=start is None)
|
|
443
444
|
|
|
444
445
|
def copy_range(root, a, b):
|
|
446
|
+
''' Returns a usx document containing paragraphs containing the content
|
|
447
|
+
between a and b '''
|
|
445
448
|
factory = root.__class__
|
|
446
449
|
if a.el not in root:
|
|
447
450
|
p = a.el.parent
|
|
@@ -482,6 +485,52 @@ def copy_range(root, a, b):
|
|
|
482
485
|
res.remove(last)
|
|
483
486
|
return res
|
|
484
487
|
|
|
488
|
+
def copy_text(root, a, b):
|
|
489
|
+
''' Returns a text string of all the main text between a and b '''
|
|
490
|
+
res = []
|
|
491
|
+
if a.el not in root:
|
|
492
|
+
p = a.el.parent
|
|
493
|
+
while p not in root:
|
|
494
|
+
p = p.parent
|
|
495
|
+
else:
|
|
496
|
+
p = a.el
|
|
497
|
+
i = list(root).index(p)
|
|
498
|
+
for eloc in iterusx(root, parindex=i, start=a.el, until=b.el):
|
|
499
|
+
if eloc.head is None:
|
|
500
|
+
if not isempty(eloc.parent.text):
|
|
501
|
+
start = 0; end = len(eloc.parent.text)
|
|
502
|
+
if eloc.parent == a.el:
|
|
503
|
+
if a.attrib == " text":
|
|
504
|
+
start = a.char
|
|
505
|
+
if eloc.parent == b.el:
|
|
506
|
+
if b.attrib == " text":
|
|
507
|
+
end = b.char
|
|
508
|
+
res.append(eloc.parent.text[start:end])
|
|
509
|
+
elif eloc.head == a.el and a.attrib == " tail":
|
|
510
|
+
res.append(eloc.head.tail[a.char:])
|
|
511
|
+
elif eloc.head == b.el and b.attrib == " text":
|
|
512
|
+
pass
|
|
513
|
+
elif not isempty(eloc.head.tail):
|
|
514
|
+
res.append(eloc.head.tail)
|
|
515
|
+
if not len(res) and a.attrib == " tail":
|
|
516
|
+
start = a.char
|
|
517
|
+
else:
|
|
518
|
+
start = 0
|
|
519
|
+
if b.attrib == " tail":
|
|
520
|
+
res.append(b.el.tail[start:b.char])
|
|
521
|
+
return "".join(res)
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def vnumin(s, v, base):
|
|
525
|
+
b = base.copy()
|
|
526
|
+
b.verse = None
|
|
527
|
+
b.subverse = None
|
|
528
|
+
b.word = None
|
|
529
|
+
b.char = None
|
|
530
|
+
b.mrkrs = None
|
|
531
|
+
r = Ref(str(b)+":"+s)
|
|
532
|
+
if v >= r.first.verse and v <= r.last.verse:
|
|
533
|
+
return True
|
|
485
534
|
|
|
486
535
|
@dataclass
|
|
487
536
|
class USXLoc:
|
|
@@ -504,6 +553,8 @@ def _findel(node, tag, attrib, limits=[]):
|
|
|
504
553
|
|
|
505
554
|
def _findtext(node, limits):
|
|
506
555
|
""" Iterator returning text strings until an element in limits is encountered """
|
|
556
|
+
if not isempty(node.text):
|
|
557
|
+
yield(node.text, node, " text")
|
|
507
558
|
for eloc in iterusx(node, until=lambda e: e.tag in limits):
|
|
508
559
|
if eloc.head is None:
|
|
509
560
|
if not isempty(eloc.parent.text):
|
|
@@ -513,7 +564,7 @@ def _findtext(node, limits):
|
|
|
513
564
|
if not isempty(node.tail): # but what if we hit the limits?
|
|
514
565
|
yield(node.tail, node, " tail")
|
|
515
566
|
|
|
516
|
-
def _findcvel(ref, root, atend=False, parindex=0):
|
|
567
|
+
def _findcvel(ref, root, grammar, atend=False, parindex=0):
|
|
517
568
|
if ref.book:
|
|
518
569
|
bkel = root.find("./book")
|
|
519
570
|
bk = bkel.get("code", "")
|
|
@@ -539,31 +590,74 @@ def _findcvel(ref, root, atend=False, parindex=0):
|
|
|
539
590
|
v = ref.verse
|
|
540
591
|
if v is not None and v > 0:
|
|
541
592
|
if atend:
|
|
542
|
-
parindex = startparindex
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
593
|
+
parindex = startparindex + 1
|
|
594
|
+
if not ref.mrkrs and not ref.word and not ref.char:
|
|
595
|
+
v += 1
|
|
596
|
+
else:
|
|
597
|
+
parindex += 1
|
|
598
|
+
while parindex < len(root):
|
|
599
|
+
n = root[parindex]
|
|
600
|
+
el = _findel(n, "verse", {"number": str(v)}, limits=("chapter",))
|
|
601
|
+
if el is not None:
|
|
602
|
+
break
|
|
603
|
+
parindex += 1
|
|
604
|
+
else:
|
|
605
|
+
raise ValueError("Reference verse {} out of range".format(ref))
|
|
606
|
+
if ref.mrkrs:
|
|
607
|
+
if not ref.word and not ref.char:
|
|
608
|
+
oparindex = parindex
|
|
609
|
+
t = ref.mrkrs[0].mrkr # look forward or backwards for a para
|
|
610
|
+
if grammar.marker_categories.get(t, "") in ("sectionpara",):
|
|
611
|
+
while parindex > 0:
|
|
612
|
+
p = root[parindex]
|
|
613
|
+
if p.get("style", "") == t:
|
|
614
|
+
if ref.mrkrs[0].index is None or ref.mrkrs[0].index - 1 == count:
|
|
615
|
+
foundme = True
|
|
616
|
+
break
|
|
617
|
+
parindex -= 1
|
|
618
|
+
endme = False
|
|
619
|
+
for e in root[parindex]:
|
|
620
|
+
if e.tag == "verse" and not vnumin(e.get("number", ""), v, ref.last if atend else ref.first):
|
|
621
|
+
endme = True
|
|
622
|
+
break
|
|
623
|
+
if endme:
|
|
624
|
+
parindex = oparindex
|
|
625
|
+
raise SyntaxError("Cannot find preceding section paragraph {t} in {ref}")
|
|
626
|
+
else:
|
|
627
|
+
el = root[parindex]
|
|
548
628
|
else:
|
|
549
|
-
|
|
629
|
+
for eloc in iterusx(root, parindex=parindex, start=lambda e:e.tag == "verse" and vnumin(e.get("number", ""), v, ref.last if atend else ref.first), until=lambda e:e.tag == "verse" and not vnumin(e.get("number", ""), v, ref.last if atend else ref.first)):
|
|
630
|
+
if eloc.head is not None:
|
|
631
|
+
continue
|
|
632
|
+
if eloc.parent.get("style", "") == t:
|
|
633
|
+
el = eloc.parent
|
|
634
|
+
break
|
|
550
635
|
return el
|
|
551
636
|
|
|
552
|
-
def _findtextref(ref, el, atend=False):
|
|
637
|
+
def _findtextref(ref, el, atend=False, mrkri=0):
|
|
638
|
+
if mrkri > 0:
|
|
639
|
+
r = ref.mrkrs[mrkri-1]
|
|
640
|
+
else:
|
|
641
|
+
r = ref
|
|
553
642
|
a = ""
|
|
554
643
|
w = -1
|
|
555
644
|
c = -1
|
|
556
645
|
clen = -1
|
|
557
646
|
t = ""
|
|
558
|
-
if
|
|
647
|
+
if r.word is None or r.word == 0:
|
|
559
648
|
t = el.get('number', "")
|
|
560
649
|
a = "number"
|
|
561
650
|
c = 0
|
|
562
651
|
clen = len(t)
|
|
563
652
|
else:
|
|
564
|
-
word =
|
|
653
|
+
word = r.word - 1
|
|
565
654
|
for t, el, a in _findtext(el, limits=("chapter", "verse")):
|
|
566
655
|
b = re.split("([\u0020\u00A0\u1680\u2000-\u200B\u202F\u205F\u3000]+)", t)
|
|
656
|
+
if not len(b[-1]):
|
|
657
|
+
b.pop()
|
|
658
|
+
if not len(b[0]):
|
|
659
|
+
b.pop(0)
|
|
660
|
+
b.pop(0)
|
|
567
661
|
l = (len(b) + 1) // 2
|
|
568
662
|
if not len(b[0]):
|
|
569
663
|
l -= 1
|
|
@@ -572,24 +666,38 @@ def _findtextref(ref, el, atend=False):
|
|
|
572
666
|
continue
|
|
573
667
|
c = sum(len(s) for s in b[:word*2])
|
|
574
668
|
clen = len(b[word*2])
|
|
669
|
+
if ref.mrkrs and len(ref.mrkrs) > mrkri and c + clen == len(t.rstrip()):
|
|
670
|
+
en = el.getnext()
|
|
671
|
+
while en is not None:
|
|
672
|
+
if en.get("style", "") == ref.mrkrs[mrkri].mrkr:
|
|
673
|
+
break
|
|
674
|
+
else:
|
|
675
|
+
raise SyntaxError(f"Can't find {ref.mrkrs[mrkri+1].mrkr}")
|
|
676
|
+
return _findtext(ref, en, atend=atend, mrkri=mrkri+1)
|
|
575
677
|
break
|
|
576
678
|
else:
|
|
577
679
|
raise ValueError("Reference word {} out of range".format(ref))
|
|
578
|
-
if
|
|
579
|
-
if
|
|
680
|
+
if r.char is not None:
|
|
681
|
+
if r.char > clen:
|
|
580
682
|
raise ValueError("Reference char {} out of range for word length {}".format(ref, clen))
|
|
581
|
-
c +=
|
|
683
|
+
c += r.char
|
|
582
684
|
elif atend:
|
|
583
685
|
c += clen
|
|
584
686
|
return USXLoc(el, a, c)
|
|
585
687
|
|
|
586
|
-
def findref(ref, root, atend=False, parindex=0):
|
|
688
|
+
def findref(ref, root, atend=False, parindex=0, grammar=None):
|
|
587
689
|
""" From a reference, return a USXLoc (element, attribute and char index) """
|
|
588
|
-
|
|
690
|
+
if grammar is None:
|
|
691
|
+
grammar = Grammar()
|
|
692
|
+
el = _findcvel(ref, root, grammar, atend=atend, parindex=parindex)
|
|
589
693
|
if ref.word is not None or ref.char is not None:
|
|
590
694
|
res = _findtextref(ref, el, atend=atend)
|
|
695
|
+
elif ref.mrkrs:
|
|
696
|
+
res = _findtextref(ref, el, atend=atend, mrkri=1)
|
|
697
|
+
elif atend and len(el):
|
|
698
|
+
res = USXLoc(el[-1], " tail", -1)
|
|
591
699
|
else:
|
|
592
|
-
res = USXLoc(el, "", -1)
|
|
700
|
+
res = USXLoc(el, " text", -1 if atend else 0)
|
|
593
701
|
# What about markers?
|
|
594
702
|
return res
|
|
595
703
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pytest import fail
|
|
3
|
+
import usfmtc
|
|
4
|
+
import xml.etree.ElementTree as et
|
|
5
|
+
import re, io
|
|
6
|
+
|
|
7
|
+
def asusfm(root):
|
|
8
|
+
grammar = usfmtc.Grammar()
|
|
9
|
+
with io.StringIO() as fh:
|
|
10
|
+
usfmtc.usx2usfm(fh, root, grammar)
|
|
11
|
+
res = fh.getvalue()
|
|
12
|
+
return res
|
|
13
|
+
|
|
14
|
+
def _dousfm(s):
|
|
15
|
+
doc = usfmtc.readFile(s, informat="usfm")
|
|
16
|
+
doc.canonicalise()
|
|
17
|
+
r = doc.getroot()
|
|
18
|
+
et.dump(r)
|
|
19
|
+
f = asusfm(r)
|
|
20
|
+
print(f)
|
|
21
|
+
return (doc, f)
|
|
22
|
+
|
|
23
|
+
def test_hardspaces():
|
|
24
|
+
usfm = r"""\id TST testing hardspaces
|
|
25
|
+
\c 1 \p \v 1 This{0}has a hard{0}space\f + \fr 1:1{0}\ft And here\f*""".format("\u00A0")
|
|
26
|
+
doc, f = _dousfm(usfm)
|
|
27
|
+
r = doc.getroot()
|
|
28
|
+
e = r.find('.//char[@style="fr"]')
|
|
29
|
+
t = e.text
|
|
30
|
+
if not t.endswith("\u00A0"):
|
|
31
|
+
fail("No hard space after fr in usx")
|
|
32
|
+
if not re.search("\u00A0\\\\ft", f):
|
|
33
|
+
fail("No hard space after fr in usfm")
|
|
34
|
+
|
|
35
|
+
def test_glossary():
|
|
36
|
+
usfm = r"""\id TST glossary in text
|
|
37
|
+
\c 1 \p \v 1 We have \w glossary\w* words to deal with"""
|
|
38
|
+
doc, f = _dousfm(usfm)
|
|
39
|
+
if not re.search(" \\w", f):
|
|
40
|
+
fail("No space before glossary word")
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pytest import fail
|
|
3
|
+
from usfmtc import readFile, usx2usfm
|
|
4
|
+
from usfmtc.usxmodel import findref, copy_range, copy_text
|
|
5
|
+
from usfmtc.reference import Ref, RefList, RefRange
|
|
6
|
+
import os, io
|
|
7
|
+
import xml.etree.ElementTree as et
|
|
8
|
+
|
|
9
|
+
# To see the print() output of passing tests, run pytest -s
|
|
10
|
+
|
|
11
|
+
def check_ref(ref, product=None, book=None, chapter=None, verse=None, subverse=None,
|
|
12
|
+
word=None, char=None, mrkrs=None):
|
|
13
|
+
r = Ref(product=product, book=book, chapter=chapter, verse=verse, subverse=subverse,
|
|
14
|
+
word=word, char=char, mrkrs=mrkrs)
|
|
15
|
+
return r == ref
|
|
16
|
+
|
|
17
|
+
def asusfm(root):
|
|
18
|
+
with io.StringIO() as fh:
|
|
19
|
+
usx2usfm(fh, root)
|
|
20
|
+
res = fh.getvalue()
|
|
21
|
+
return res
|
|
22
|
+
|
|
23
|
+
jon_usfm = readFile(os.path.join(os.path.dirname(__file__), "32JONBSB.usfm"))
|
|
24
|
+
|
|
25
|
+
def _get_textref(s):
|
|
26
|
+
r = Ref(s)
|
|
27
|
+
root = jon_usfm.getroot()
|
|
28
|
+
start = findref(r.first, root)
|
|
29
|
+
end = findref(r.last, root, atend=True)
|
|
30
|
+
res = copy_text(root, start, end)
|
|
31
|
+
return res
|
|
32
|
+
|
|
33
|
+
def _r(bk, chap, verse, subv=None):
|
|
34
|
+
return Ref(book=bk, chapter=chap, verse=verse, subverse=subv)
|
|
35
|
+
|
|
36
|
+
def _t(s, *r):
|
|
37
|
+
res = RefList(s)
|
|
38
|
+
if len(res) != len(r):
|
|
39
|
+
fail("Failed '{}' resulted in {} references, {}, rather than {}".format(s, len(res), res, len(r)))
|
|
40
|
+
for z in zip(res, r):
|
|
41
|
+
if z[0] != z[1]:
|
|
42
|
+
fail("Reference list failed '{}', {} != {}".format(s, z[0], z[1]))
|
|
43
|
+
if str(res) != s:
|
|
44
|
+
fail("{} != canonical string of {}".format(s, str(res)))
|
|
45
|
+
print("{} = {}".format(s, str(res)))
|
|
46
|
+
|
|
47
|
+
def _listtest(s, a):
|
|
48
|
+
res = RefList(s)
|
|
49
|
+
res.simplify()
|
|
50
|
+
base = RefList(a)
|
|
51
|
+
if res != base:
|
|
52
|
+
fail("{} simplified to {} instead of {}".format(s, res, base))
|
|
53
|
+
print("{} -> {}".format(s, res))
|
|
54
|
+
|
|
55
|
+
def _o(a, b, res):
|
|
56
|
+
x = RefList(a)[0]
|
|
57
|
+
y = RefList(b)[0]
|
|
58
|
+
if (isinstance(res,tuple)):
|
|
59
|
+
restup=res
|
|
60
|
+
restupd=f"x<y {restup[0]}, y<x {restup[1]}, x in y {restup[2]}"
|
|
61
|
+
else:
|
|
62
|
+
restup=(res,res,res)
|
|
63
|
+
restupd=res
|
|
64
|
+
a = (y<x)
|
|
65
|
+
b = (x<y)
|
|
66
|
+
#print(f"y<x :{a} x<y:{b}")
|
|
67
|
+
if restup[0] != b:
|
|
68
|
+
raise fail(f"{x} should be < {y} [expect: {restupd}]")
|
|
69
|
+
if restup[1] != a:
|
|
70
|
+
raise fail(f"{y} should not be < {x} [expect: {restupd}]")
|
|
71
|
+
if (x in y) != restup[2]:
|
|
72
|
+
raise fail(f"{x} in {y} should be {res}")
|
|
73
|
+
print("{} <-> {} ({})".format(x, y,restupd))
|
|
74
|
+
|
|
75
|
+
def _do_findreftest(first, last):
|
|
76
|
+
root = jon_usfm.getroot()
|
|
77
|
+
start = findref(first, root)
|
|
78
|
+
end = findref(last, root, atend=True)
|
|
79
|
+
res = copy_range(root, start, end)
|
|
80
|
+
|
|
81
|
+
print(f"{first=} {start}; {last=} {end}")
|
|
82
|
+
et.dump(res)
|
|
83
|
+
print(asusfm(res))
|
|
84
|
+
if len(res) != 7 or len(res[2]) != 1 or "vomited" not in res[-1][-1].tail:
|
|
85
|
+
fail(f"{len(res)=}, {res[-1][-1].tail=}")
|
|
86
|
+
|
|
87
|
+
def test_basic():
|
|
88
|
+
r = Ref("JHN 3:16")
|
|
89
|
+
rr = Ref("12", context=r)
|
|
90
|
+
|
|
91
|
+
if not check_ref(r, None, "JHN", 3, 16):
|
|
92
|
+
fail(f"{r} is not JHN 3:16")
|
|
93
|
+
if not check_ref(rr, None, "JHN", 3, 12):
|
|
94
|
+
fail(f"{rr} is not JHN 3:12")
|
|
95
|
+
|
|
96
|
+
def test_range():
|
|
97
|
+
r = RefList("JHN 3:16-18")
|
|
98
|
+
|
|
99
|
+
if not check_ref(r[0].first, None, "JHN", 3, 16):
|
|
100
|
+
fail(f"{r[0].first} is not JHN 3:16")
|
|
101
|
+
if not check_ref(r[0].last, None, "JHN", 3, 18):
|
|
102
|
+
fail(f"{r[0].last} is not JHN 3:18")
|
|
103
|
+
|
|
104
|
+
def test_findreftext():
|
|
105
|
+
r = Ref("JON 3:6!3")
|
|
106
|
+
loc = findref(r, jon_usfm.getroot())
|
|
107
|
+
|
|
108
|
+
if loc.el.tag != "verse" and loc.attrib != " tail" and loc.char != 18:
|
|
109
|
+
fail(f"{r} resulted in {loc}")
|
|
110
|
+
end = Ref("JON 3:6!4")
|
|
111
|
+
eloc = findref(end, jon_usfm.getroot(), atend=True)
|
|
112
|
+
if eloc.el.tag != "verse" and eloc.attrib != " tail" and eloc.char !=24:
|
|
113
|
+
fail(f"{end} resulted in {eloc}")
|
|
114
|
+
res = eloc.el.tail[loc.char:eloc.char]
|
|
115
|
+
if res != "reached the":
|
|
116
|
+
fail(f"{res} != 'reached the'")
|
|
117
|
+
|
|
118
|
+
def test_findref1():
|
|
119
|
+
first = Ref("JON 2:8")
|
|
120
|
+
last = Ref("JON 2")
|
|
121
|
+
_do_findreftest(first, last)
|
|
122
|
+
|
|
123
|
+
def test_findref2():
|
|
124
|
+
ref = Ref("JON 2:8-end")
|
|
125
|
+
_do_findreftest(ref.first, ref.last)
|
|
126
|
+
|
|
127
|
+
def test_getrefs():
|
|
128
|
+
refs = RefList("JON 1:3; 2:4-5; 3:9-end")
|
|
129
|
+
res = jon_usfm.getrefs(*refs)
|
|
130
|
+
root = res.getroot()
|
|
131
|
+
print(res.outUsfm())
|
|
132
|
+
if len(root) != 14 or root[0].get("vid", "") != "JON 1:3":
|
|
133
|
+
fail(f"{len(root)=}")
|
|
134
|
+
|
|
135
|
+
def test_textref1():
|
|
136
|
+
r = Ref("JON 2:8!2-4")
|
|
137
|
+
root = jon_usfm.getroot()
|
|
138
|
+
start = findref(r.first, root)
|
|
139
|
+
end = findref(r.last, root, atend=True)
|
|
140
|
+
print(start, end)
|
|
141
|
+
res = copy_text(jon_usfm.getroot(), start, end)
|
|
142
|
+
print(res)
|
|
143
|
+
if res != "who cling to":
|
|
144
|
+
fail(f"{res}")
|
|
145
|
+
|
|
146
|
+
def test_gen1():
|
|
147
|
+
_t("GEN 1:1", _r("GEN", 1, 1))
|
|
148
|
+
|
|
149
|
+
def test_jhn3():
|
|
150
|
+
_t("JHN 3", _r("JHN", 3, None))
|
|
151
|
+
|
|
152
|
+
def test_3jn():
|
|
153
|
+
_t("3JN 3", _r("3JN", 1, 3))
|
|
154
|
+
|
|
155
|
+
def test_1co():
|
|
156
|
+
_t("1CO 6:5a", _r("1CO", 6, 5, "a"))
|
|
157
|
+
|
|
158
|
+
def test_mat5():
|
|
159
|
+
_t("MAT 5:1-7", RefRange(_r("MAT", 5, 1), _r("MAT", 5, 7)))
|
|
160
|
+
|
|
161
|
+
def test_mat7():
|
|
162
|
+
_t("MAT 7:1,2; 8:6b-9:4", _r("MAT", 7, 1), _r("MAT", 7, 2), RefRange(_r("MAT", 8, 6, "b"), _r("MAT", 9, 4)))
|
|
163
|
+
|
|
164
|
+
def test_luk3():
|
|
165
|
+
_t("LUK 3:35-end", RefRange(_r("LUK", 3, 35), _r("LUK", 3, -1)))
|
|
166
|
+
|
|
167
|
+
def test_rom1():
|
|
168
|
+
_listtest("ROM 1; MAT 3:4-11; ROM 1:3-2:7", "MAT 3:4-11; ROM 1-2:7")
|
|
169
|
+
|
|
170
|
+
def test_gen1_1():
|
|
171
|
+
_t("GEN 1:1-3; 3:2-11; LUK 4:5", RefRange(_r("GEN", 1, 1), _r("GEN", 1, 3)), RefRange(_r("GEN", 3, 2), _r("GEN", 3, 11)), _r("LUK", 4, 5))
|
|
172
|
+
|
|
173
|
+
def test_jud():
|
|
174
|
+
_t("JUD 1,2,4", _r("JUD", 1, 1), _r("JUD", 1, 2), _r("JUD", 1, 4))
|
|
175
|
+
|
|
176
|
+
def test_gen1_1a():
|
|
177
|
+
_o("GEN 1:1", "EXO 2:3", (True,False,False))
|
|
178
|
+
|
|
179
|
+
def test_exo2():
|
|
180
|
+
_o("EXO 2:4", "EXO 2", (False,True,True))
|
|
181
|
+
|
|
182
|
+
def test_exo2a():
|
|
183
|
+
_o("EXO 2:4-5", "EXO 2", (False,True,True))
|
|
184
|
+
|
|
185
|
+
def test_exo2b():
|
|
186
|
+
_o("EXO 2:4-5", "EXO 3", (True,False,False))
|
|
187
|
+
|
|
188
|
+
def text_exo2c():
|
|
189
|
+
_o("EXO 2:1-2", "EXO 2:1-5", (False,False,True))
|
|
190
|
+
|
|
191
|
+
def test_exo2d():
|
|
192
|
+
_o("EXO 2:1-2", "EXO 2:2-7", (True,False,True))
|
|
193
|
+
|
|
194
|
+
def test_exo2e():
|
|
195
|
+
_o("EXO 2:1-2", "EXO 2:3-6", (True,False,False))
|
|
196
|
+
|
|
197
|
+
def test_gen2():
|
|
198
|
+
_o("GEN 2:1-2", "EXO 2:3-6", (True,False,False))
|
|
199
|
+
|
|
200
|
+
def test_deu2():
|
|
201
|
+
_o("DEU 2:1-2", "EXO 2:3-6", (False,True,False))
|
|
202
|
+
|
|
203
|
+
def test_exo2f():
|
|
204
|
+
_o("EXO 2:2-3", "EXO 2:1-5", (False,False,True))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ChatGPT tests:
|
|
208
|
+
def test_invalid_ref():
|
|
209
|
+
try:
|
|
210
|
+
r = Ref("INVALID 1:1")
|
|
211
|
+
fail("Invalid reference did not raise an error")
|
|
212
|
+
except SyntaxError:
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
def test_cross_book_range():
|
|
216
|
+
r = Ref("JON 3:10 - MIC 1:2")
|
|
217
|
+
if not check_ref(r.first, None, "JON", 3, 10):
|
|
218
|
+
fail(f"{r.first} is not JON 3:10")
|
|
219
|
+
if not check_ref(r.last, None, "MIC", 1, 2):
|
|
220
|
+
fail(f"{r.last} is not MIC 1:2")
|
|
221
|
+
|
|
222
|
+
def test_partial_chapter():
|
|
223
|
+
r = Ref("JON 2")
|
|
224
|
+
if not check_ref(r.first, None, "JON", 2, None):
|
|
225
|
+
fail(f"{r.first} is not JON 2")
|
|
226
|
+
|
|
227
|
+
def test_textref_boundary():
|
|
228
|
+
res = _get_textref("JON 3:10!1-2")
|
|
229
|
+
print(res)
|
|
230
|
+
if res != "When God":
|
|
231
|
+
fail(f"Unexpected result: {res}")
|
|
232
|
+
|
|
233
|
+
def test_multiple_ranges():
|
|
234
|
+
refs = RefList("JON 1:1-3; 2:4; 3:8-end")
|
|
235
|
+
res = jon_usfm.getrefs(*refs)
|
|
236
|
+
root = res.getroot()
|
|
237
|
+
et.dump(root)
|
|
238
|
+
|
|
239
|
+
if len(root) != 11 or root[0].get("vid", "") != "JON 1:1":
|
|
240
|
+
fail(f"Incorrect range extraction: {len(root)=}")
|
|
241
|
+
|
|
242
|
+
def test_chapter_only_range():
|
|
243
|
+
r = Ref("JON 3").expand()
|
|
244
|
+
if not check_ref(r.first, None, "JON", 3, 1):
|
|
245
|
+
fail(f"{r.first} is not JON 3:1")
|
|
246
|
+
|
|
247
|
+
def test_single_verse():
|
|
248
|
+
r = Ref("JON 1:1")
|
|
249
|
+
if not check_ref(r.first, None, "JON", 1, 1):
|
|
250
|
+
fail(f"{r.first} is not JON 1:1")
|
|
251
|
+
|
|
252
|
+
def test_multi_book_list():
|
|
253
|
+
refs = RefList("JON 1:1; MIC 2:3; NAH 1:7")
|
|
254
|
+
if len(refs) != 3:
|
|
255
|
+
fail("Expected three references")
|
|
256
|
+
|
|
257
|
+
def test_edge_case_verse():
|
|
258
|
+
r = Ref("JON 4:11")
|
|
259
|
+
if not check_ref(r.first, None, "JON", 4, 11):
|
|
260
|
+
fail(f"{r.first} is not JON 4:11")
|
|
261
|
+
|
|
262
|
+
def test_reverse_range():
|
|
263
|
+
try:
|
|
264
|
+
r = RefList("JON 3:5 - JON 2:1")
|
|
265
|
+
fail("Reverse range did not raise an error")
|
|
266
|
+
except ValueError:
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
def test_reference_with_suffix():
|
|
270
|
+
r = Ref("JON 3:10a")
|
|
271
|
+
if not check_ref(r.first, None, "JON", 3, 10):
|
|
272
|
+
fail(f"{r.first} is not JON 3:10a")
|
|
273
|
+
if not check_ref(r.first, None, "JON", 3, 10, "a"):
|
|
274
|
+
fail(f"{r.first} is not JON 3:10")
|
|
275
|
+
|
|
276
|
+
def test_overlapping_ranges():
|
|
277
|
+
refs = RefList("JON 1:3-5; 1:4-6")
|
|
278
|
+
if len(refs) != 2:
|
|
279
|
+
fail("Expected two overlapping references")
|
|
280
|
+
|
|
281
|
+
def test_whitespace_in_ref():
|
|
282
|
+
try:
|
|
283
|
+
r = Ref(" JON 2 : 3 ")
|
|
284
|
+
fail(f"{r.first} has whitespace in it and should fail")
|
|
285
|
+
except SyntaxError:
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
def test_missing_chapter():
|
|
289
|
+
try:
|
|
290
|
+
r = Ref("JON :5")
|
|
291
|
+
fail("Missing chapter did not raise an error")
|
|
292
|
+
except SyntaxError:
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
def test_long_reference():
|
|
296
|
+
r = RefList("JON 1:1-4:11")
|
|
297
|
+
if not (check_ref(r[0].first, None, "JON", 1, 1) and check_ref(r[0].last, None, "JON", 4, 11)):
|
|
298
|
+
fail("Long reference not parsed correctly")
|
|
299
|
+
|
|
300
|
+
def test_empty_reference():
|
|
301
|
+
try:
|
|
302
|
+
r = Ref("")
|
|
303
|
+
fail("Empty reference did not raise an error")
|
|
304
|
+
except SyntaxError:
|
|
305
|
+
pass
|
|
306
|
+
|
|
307
|
+
def test_invalid_book_code():
|
|
308
|
+
r = Ref("XYZ 1:1")
|
|
309
|
+
if r.isvalid():
|
|
310
|
+
fail("Invalid book code did not raise an error")
|
|
311
|
+
|
|
312
|
+
def test_multiple_chapters():
|
|
313
|
+
refs = RefList("JON 2:1-3:5")
|
|
314
|
+
if not (check_ref(refs[0].first, None, "JON", 2, 1) and check_ref(refs[0].last, None, "JON", 3, 5)):
|
|
315
|
+
fail("Multiple chapters not handled correctly")
|
|
316
|
+
|
|
317
|
+
def test_partial_verse():
|
|
318
|
+
r = Ref("JON 3:10!1-4")
|
|
319
|
+
root = jon_usfm.getroot()
|
|
320
|
+
start = findref(r.first, root)
|
|
321
|
+
end = findref(r.last, root, atend=True)
|
|
322
|
+
res = copy_text(root, start, end)
|
|
323
|
+
|
|
324
|
+
if "God" not in res:
|
|
325
|
+
fail("Partial verse not extracted correctly")
|
|
326
|
+
|
|
327
|
+
def test_ref_with_extra_text():
|
|
328
|
+
try:
|
|
329
|
+
r = Ref("JON 3:10abc")
|
|
330
|
+
fail("Reference with extra text did not raise an error")
|
|
331
|
+
except SyntaxError:
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
def test_range_with_negative_chapter():
|
|
335
|
+
try:
|
|
336
|
+
r = Ref("JON -1:5")
|
|
337
|
+
fail("Negative chapter did not raise an error")
|
|
338
|
+
except SyntaxError:
|
|
339
|
+
pass
|
|
340
|
+
|
|
341
|
+
def test_incomplete_reference():
|
|
342
|
+
try:
|
|
343
|
+
r = Ref("JON")
|
|
344
|
+
fail("Incomplete reference did not raise an error")
|
|
345
|
+
except SyntaxError:
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
def test_large_chapter_number():
|
|
349
|
+
r = Ref("JON 999:1")
|
|
350
|
+
if r.isvalid():
|
|
351
|
+
fail("Large chapter number did not raise an error")
|
|
352
|
+
|
|
353
|
+
### Markers
|
|
354
|
+
|
|
355
|
+
def test_parsemarker():
|
|
356
|
+
res = _get_textref("JON 1:4!s1!2")
|
|
357
|
+
if res != "Great":
|
|
358
|
+
fail(f"{r}: [{start}, {end}] {res}")
|
|
359
|
+
|
|
360
|
+
def test_footnote():
|
|
361
|
+
res = _get_textref("JON 1:13!f!5")
|
|
362
|
+
if res != "dug":
|
|
363
|
+
fail(f"{r}: [{start}, {end}] {res}")
|
|
364
|
+
|
|
365
|
+
|
usfmtc-0.3.2/tests/test_refs.py
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from pytest import fail
|
|
3
|
-
from usfmtc import readFile, usx2usfm
|
|
4
|
-
from usfmtc.usxmodel import findref, copy_range
|
|
5
|
-
from usfmtc.reference import Ref, RefList, RefRange
|
|
6
|
-
import os, io
|
|
7
|
-
import xml.etree.ElementTree as et
|
|
8
|
-
|
|
9
|
-
# To see the print() output of passing tests, run pytest -s
|
|
10
|
-
|
|
11
|
-
def check_ref(ref, product=None, book=None, chapter=None, verse=None, subverse=None,
|
|
12
|
-
word=None, char=None, mrkrs=None):
|
|
13
|
-
if product != ref.product:
|
|
14
|
-
return False
|
|
15
|
-
if book != ref.book:
|
|
16
|
-
return False
|
|
17
|
-
if chapter != ref.chapter:
|
|
18
|
-
return False
|
|
19
|
-
if verse != ref.verse:
|
|
20
|
-
return False
|
|
21
|
-
if subverse != ref.subverse:
|
|
22
|
-
return False
|
|
23
|
-
if word != ref.word:
|
|
24
|
-
return False
|
|
25
|
-
if char != ref.char:
|
|
26
|
-
return False
|
|
27
|
-
return True
|
|
28
|
-
|
|
29
|
-
def asusfm(root):
|
|
30
|
-
with io.StringIO() as fh:
|
|
31
|
-
usx2usfm(fh, root)
|
|
32
|
-
res = fh.getvalue()
|
|
33
|
-
return res
|
|
34
|
-
|
|
35
|
-
jon_usfm = readFile(os.path.join(os.path.dirname(__file__), "32JONBSB.usfm"))
|
|
36
|
-
|
|
37
|
-
def test_basic():
|
|
38
|
-
r = Ref("JHN 3:16")
|
|
39
|
-
rr = Ref("12", context=r)
|
|
40
|
-
|
|
41
|
-
if not check_ref(r, None, "JHN", 3, 16):
|
|
42
|
-
fail(f"{r} is not JHN 3:16")
|
|
43
|
-
if not check_ref(rr, None, "JHN", 3, 12):
|
|
44
|
-
fail(f"{r} is not JHN 3:12")
|
|
45
|
-
|
|
46
|
-
def test_range():
|
|
47
|
-
r = RefList("JHN 3:16-18")
|
|
48
|
-
|
|
49
|
-
if not check_ref(r[0].first, None, "JHN", 3, 16):
|
|
50
|
-
fail(f"{r[0].first} is not JHN 3:16")
|
|
51
|
-
if not check_ref(r[0].last, None, "JHN", 3, 18):
|
|
52
|
-
fail(f"{r[0].last} is not JHN 3:18")
|
|
53
|
-
|
|
54
|
-
def test_findreftext():
|
|
55
|
-
r = Ref("JON 3:6!3")
|
|
56
|
-
loc = findref(r, jon_usfm.getroot())
|
|
57
|
-
|
|
58
|
-
if loc.el.tag != "verse" and loc.attrib != " tail" and loc.char != 18:
|
|
59
|
-
fail(f"{r} resulted in {loc}")
|
|
60
|
-
end = Ref("JON 3:6!4")
|
|
61
|
-
eloc = findref(end, jon_usfm.getroot(), atend=True)
|
|
62
|
-
if eloc.el.tag != "verse" and eloc.attrib != " tail" and eloc.char !=24:
|
|
63
|
-
fail(f"{end} resulted in {eloc}")
|
|
64
|
-
res = eloc.el.tail[loc.char:eloc.char]
|
|
65
|
-
if res != "reached the":
|
|
66
|
-
fail(f"{res} != 'reached the'")
|
|
67
|
-
|
|
68
|
-
def test_findref1():
|
|
69
|
-
first = Ref("JON 2:8")
|
|
70
|
-
last = Ref("JON 2")
|
|
71
|
-
_do_findreftest(first, last)
|
|
72
|
-
|
|
73
|
-
def test_findref2():
|
|
74
|
-
ref = Ref("JON 2:8-end")
|
|
75
|
-
_do_findreftest(ref.first, ref.last)
|
|
76
|
-
|
|
77
|
-
def test_getrefs():
|
|
78
|
-
refs = RefList("JON 1:3; 2:4-5; 3:9-end")
|
|
79
|
-
res = jon_usfm.getrefs(*refs)
|
|
80
|
-
root = res.getroot()
|
|
81
|
-
print(res.outUsfm())
|
|
82
|
-
if len(root) != 14 or root[0].get("vid", "") != "JON 1:3":
|
|
83
|
-
fail(f"{len(root)=}")
|
|
84
|
-
|
|
85
|
-
def _do_findreftest(first, last):
|
|
86
|
-
root = jon_usfm.getroot()
|
|
87
|
-
start = findref(first, root)
|
|
88
|
-
end = findref(last, root, atend=True)
|
|
89
|
-
res = copy_range(root, start, end)
|
|
90
|
-
|
|
91
|
-
print(f"{first=} {start}; {last=} {end}")
|
|
92
|
-
et.dump(res)
|
|
93
|
-
print(asusfm(res))
|
|
94
|
-
if len(res) != 7 or len(res[2]) != 1 or "vomited" not in res[-1][-1].tail:
|
|
95
|
-
fail(f"{len(res)=}, {res[-1][-1].tail=}")
|
|
96
|
-
|
|
97
|
-
def r(bk, chap, verse, subv=None):
|
|
98
|
-
return Ref(book=bk, chapter=chap, verse=verse, subverse=subv)
|
|
99
|
-
|
|
100
|
-
def t(s, *r):
|
|
101
|
-
res = RefList(s)
|
|
102
|
-
if len(res) != len(r):
|
|
103
|
-
fail("Failed '{}' resulted in {} references, {}, rather than {}".format(s, len(res), res, len(r)))
|
|
104
|
-
for z in zip(res, r):
|
|
105
|
-
if z[0] != z[1]:
|
|
106
|
-
fail("Reference list failed '{}', {} != {}".format(s, z[0], z[1]))
|
|
107
|
-
if str(res) != s:
|
|
108
|
-
fail("{} != canonical string of {}".format(s, str(res)))
|
|
109
|
-
print("{} = {}".format(s, str(res)))
|
|
110
|
-
|
|
111
|
-
def listtest(s, a):
|
|
112
|
-
res = RefList(s)
|
|
113
|
-
res.simplify()
|
|
114
|
-
base = RefList(a)
|
|
115
|
-
if res != base:
|
|
116
|
-
fail("{} simplified to {} instead of {}".format(s, res, base))
|
|
117
|
-
print("{} -> {}".format(s, res))
|
|
118
|
-
|
|
119
|
-
def o(a, b, res):
|
|
120
|
-
x = RefList(a)[0]
|
|
121
|
-
y = RefList(b)[0]
|
|
122
|
-
if (isinstance(res,tuple)):
|
|
123
|
-
restup=res
|
|
124
|
-
restupd=f"x<y {restup[0]}, y<x {restup[1]}, x in y {restup[2]}"
|
|
125
|
-
else:
|
|
126
|
-
restup=(res,res,res)
|
|
127
|
-
restupd=res
|
|
128
|
-
a = (y<x)
|
|
129
|
-
b = (x<y)
|
|
130
|
-
#print(f"y<x :{a} x<y:{b}")
|
|
131
|
-
if restup[0] != b:
|
|
132
|
-
raise fail(f"{x} should be < {y} [expect: {restupd}]")
|
|
133
|
-
if restup[1] != a:
|
|
134
|
-
raise fail(f"{y} should not be < {x} [expect: {restupd}]")
|
|
135
|
-
if (x in y) != restup[2]:
|
|
136
|
-
raise fail(f"{x} in {y} should be {res}")
|
|
137
|
-
print("{} <-> {} ({})".format(x, y,restupd))
|
|
138
|
-
|
|
139
|
-
def test_gen1():
|
|
140
|
-
t("GEN 1:1", r("GEN", 1, 1))
|
|
141
|
-
|
|
142
|
-
def test_jhn3():
|
|
143
|
-
t("JHN 3", r("JHN", 3, None))
|
|
144
|
-
|
|
145
|
-
def test_3jn():
|
|
146
|
-
t("3JN 3", r("3JN", 1, 3))
|
|
147
|
-
|
|
148
|
-
def test_1co():
|
|
149
|
-
t("1CO 6:5a", r("1CO", 6, 5, "a"))
|
|
150
|
-
|
|
151
|
-
def test_mat5():
|
|
152
|
-
t("MAT 5:1-7", RefRange(r("MAT", 5, 1), r("MAT", 5, 7)))
|
|
153
|
-
|
|
154
|
-
def test_mat7():
|
|
155
|
-
t("MAT 7:1,2; 8:6b-9:4", r("MAT", 7, 1), r("MAT", 7, 2), RefRange(r("MAT", 8, 6, "b"), r("MAT", 9, 4)))
|
|
156
|
-
|
|
157
|
-
def test_luk3():
|
|
158
|
-
t("LUK 3:35-end", RefRange(r("LUK", 3, 35), r("LUK", 3, -1)))
|
|
159
|
-
|
|
160
|
-
def test_rom1():
|
|
161
|
-
listtest("ROM 1; MAT 3:4-11; ROM 1:3-2:7", "MAT 3:4-11; ROM 1-2:7")
|
|
162
|
-
|
|
163
|
-
def test_gen1_1():
|
|
164
|
-
t("GEN 1:1-3; 3:2-11; LUK 4:5", RefRange(r("GEN", 1, 1), r("GEN", 1, 3)), RefRange(r("GEN", 3, 2), r("GEN", 3, 11)), r("LUK", 4, 5))
|
|
165
|
-
|
|
166
|
-
def test_jud():
|
|
167
|
-
t("JUD 1,2,4", r("JUD", 1, 1), r("JUD", 1, 2), r("JUD", 1, 4))
|
|
168
|
-
|
|
169
|
-
def test_gen1_1a():
|
|
170
|
-
o("GEN 1:1", "EXO 2:3", (True,False,False))
|
|
171
|
-
|
|
172
|
-
def test_exo2():
|
|
173
|
-
o("EXO 2:4", "EXO 2", (False,True,True))
|
|
174
|
-
|
|
175
|
-
def test_exo2a():
|
|
176
|
-
o("EXO 2:4-5", "EXO 2", (False,True,True))
|
|
177
|
-
|
|
178
|
-
def test_exo2b():
|
|
179
|
-
o("EXO 2:4-5", "EXO 3", (True,False,False))
|
|
180
|
-
|
|
181
|
-
def text_exo2c():
|
|
182
|
-
o("EXO 2:1-2", "EXO 2:1-5", (False,False,True))
|
|
183
|
-
|
|
184
|
-
def test_exo2d():
|
|
185
|
-
o("EXO 2:1-2", "EXO 2:2-7", (True,False,True))
|
|
186
|
-
|
|
187
|
-
def test_exo2e():
|
|
188
|
-
o("EXO 2:1-2", "EXO 2:3-6", (True,False,False))
|
|
189
|
-
|
|
190
|
-
def test_gen2():
|
|
191
|
-
o("GEN 2:1-2", "EXO 2:3-6", (True,False,False))
|
|
192
|
-
|
|
193
|
-
def test_deu2():
|
|
194
|
-
o("DEU 2:1-2", "EXO 2:3-6", (False,True,False))
|
|
195
|
-
|
|
196
|
-
def test_exo2f():
|
|
197
|
-
o("EXO 2:2-3", "EXO 2:1-5", (False,False,True))
|
|
198
|
-
|
|
199
|
-
|
|
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
|