usfmtc 0.2.3__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.
Files changed (32) hide show
  1. {usfmtc-0.2.3/python/lib/usfmtc.egg-info → usfmtc-0.2.4}/PKG-INFO +2 -2
  2. {usfmtc-0.2.3 → usfmtc-0.2.4}/pyproject.toml +1 -1
  3. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/__init__.py +17 -11
  4. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/usfmparser.py +106 -43
  5. {usfmtc-0.2.3 → usfmtc-0.2.4/python/lib/usfmtc.egg-info}/PKG-INFO +2 -2
  6. {usfmtc-0.2.3 → usfmtc-0.2.4}/LICENSE.md +0 -0
  7. {usfmtc-0.2.3 → usfmtc-0.2.4}/README.md +0 -0
  8. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/README.md +0 -0
  9. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/diagrams.py +0 -0
  10. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/extension.py +0 -0
  11. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/grammar.py +0 -0
  12. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/parser.py +0 -0
  13. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/railroad.py +0 -0
  14. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/reference.py +0 -0
  15. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/rnc2rng/__init__.py +0 -0
  16. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/rnc2rng/parser.py +0 -0
  17. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/rnc2rng/rnctree.py +0 -0
  18. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/rnc2rng/serializer.py +0 -0
  19. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/sfmparser.py +0 -0
  20. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/usfmgenerate.py +0 -0
  21. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/usjproc.py +0 -0
  22. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/usx.rng +0 -0
  23. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/usxgrammar.py +0 -0
  24. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/usxmodel.py +0 -0
  25. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/usxparser.py +0 -0
  26. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc/xmlutils.py +0 -0
  27. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/SOURCES.txt +0 -0
  28. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/dependency_links.txt +0 -0
  29. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/entry_points.txt +0 -0
  30. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/requires.txt +0 -0
  31. {usfmtc-0.2.3 → usfmtc-0.2.4}/python/lib/usfmtc.egg-info/top_level.txt +0 -0
  32. {usfmtc-0.2.3 → usfmtc-0.2.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: usfmtc
3
- Version: 0.2.3
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:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "usfmtc"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  authors = [{name = "USFM Technical Committee", email = "fonts@sil.org"}]
9
9
  readme = "python/README.md"
10
10
  license = {file = "LICENSE.md"}
@@ -173,7 +173,7 @@ class USX:
173
173
  fn(file, dat)
174
174
  return True
175
175
 
176
- def outUsx(self, file=None):
176
+ def outUsx(self, file=None, **kw):
177
177
  """ Output pretty XML USX. If file is None returns string """
178
178
  if self.xml is None:
179
179
  return None
@@ -192,16 +192,17 @@ class USX:
192
192
  return self._outwrite(file, dat)
193
193
  return False
194
194
 
195
- def outUsj(self, file=None):
195
+ def outUsj(self, file=None, ensure_ascii=False, **kw):
196
196
  """ Output USJ from USX object. If file is None returns dict """
197
197
  res = usxtousj(self.xml)
198
198
  if file is None:
199
199
  return res
200
200
  else:
201
- dat = json.dumps(res, indent=2)
201
+ dat = json.dumps(res, indent=2, ensure_ascii=ensure_ascii)
202
202
  self._outwrite(file, dat)
203
203
 
204
- def saveAs(self, outfpath, outformat=None, addesids=False, grammar=None, gramfile=None, version=None, altparser=True):
204
+ def saveAs(self, outfpath, outformat=None, addesids=False, grammar=None,
205
+ gramfile=None, version=None, altparser=True, **kw):
205
206
  """ Saves the document to a file in the appropriate format, either given
206
207
  or inferred from the filename extension. """
207
208
  if outformat is None:
@@ -215,9 +216,9 @@ class USX:
215
216
  if outtype == "usx":
216
217
  if addesids:
217
218
  usxdoc.addesids()
218
- self.outUsx(outfpath)
219
+ self.outUsx(outfpath, **kw)
219
220
  elif outtype == "usj":
220
- self.outUsj(outfpath)
221
+ self.outUsj(outfpath, **kw)
221
222
  elif outtype == "usfm":
222
223
  if grammar is None and altparser:
223
224
  if gramfile is None:
@@ -230,7 +231,7 @@ class USX:
230
231
  outtype = "usfm"
231
232
  if version is None:
232
233
  version = "3.0"
233
- self.outUsfm(grammar, outfpath, outversion=version, altparser=altparser)
234
+ self.outUsfm(grammar, outfpath, outversion=version, altparser=altparser, **kw)
234
235
 
235
236
  def canonicalise(self):
236
237
  canonicalise(self.getroot())
@@ -262,11 +263,14 @@ def main(hookcli=None, hookusx=None):
262
263
  parser.add_argument("-F","--outformat",help="Output format [usfm, usx, usj, usfm3.0]")
263
264
  parser.add_argument("-I","--informat",help="Input format [usfm, usx, usj]")
264
265
  parser.add_argument("-g","--grammar",help="Grammar file to use, if needed")
265
- parser.add_argument("-e","--esids",action="store_true",help="Add esids, vids, sids, etc. to USX output")
266
+ parser.add_argument("-e","--esids",action="store_true",
267
+ help="Add esids, vids, sids, etc. to USX output")
266
268
  parser.add_argument("-v","--version",default=None,help="Set USFM version [3.1]")
267
- parser.add_argument("-x","--extfiles",action="append",default=[],help="markers.ext files to include")
269
+ parser.add_argument("-x","--extfiles",action="append",default=[],
270
+ help="markers.ext files to include")
268
271
  parser.add_argument("-V","--validate",action="store_true",help="Use validating parsers for USFM")
269
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")
270
274
  parser.add_argument("-l","--logging",help="Set logging level to usfmxtest.log")
271
275
  parser.add_argument("-q","--quiet",action="store_true",help="Don't say much")
272
276
  parser.add_argument("--nooutput",action="store_true",help="Don't output any data")
@@ -353,7 +357,8 @@ def main(hookcli=None, hookusx=None):
353
357
  if not args.quiet:
354
358
  print(f"{infile} -> {outfile}" if outfile else f"{infile}")
355
359
  try:
356
- usxdoc = readFile(infile, informat=args.informat, grammar=ingrammar, altparser=args.validate)
360
+ usxdoc = readFile(infile, informat=args.informat, grammar=ingrammar,
361
+ altparser=args.validate)
357
362
  except NoParseError as e:
358
363
  doerror(f"Failed to parse {infile}: {e}", False)
359
364
 
@@ -375,7 +380,8 @@ def main(hookcli=None, hookusx=None):
375
380
  if not args.canonical:
376
381
  usxdoc.canonicalise()
377
382
 
378
- usxdoc.saveAs(outfile, outformat=args.outformat, addesids=args.esids, grammar=outgrammar, altparser=args.validate)
383
+ usxdoc.saveAs(outfile, outformat=args.outformat, addesids=args.esids,
384
+ grammar=outgrammar, altparser=args.validate, ensure_ascii=args.ascii)
379
385
 
380
386
  if __name__ == "__main__":
381
387
  main()
@@ -7,12 +7,13 @@ from collections import UserDict, UserString
7
7
 
8
8
 
9
9
  class Pos:
10
- def __init__(self, l, c):
10
+ def __init__(self, l, c, **kw):
11
11
  self.l = l
12
12
  self.c = c
13
+ self.kw = kw
13
14
 
14
15
  class Tag(str):
15
- def __new__(cls, s, l=0, c=0):
16
+ def __new__(cls, s, l=0, c=0, **kw):
16
17
  isend = False
17
18
  isplus = False
18
19
  if s.startswith("+"):
@@ -22,6 +23,7 @@ class Tag(str):
22
23
  isend = True
23
24
  s = s[:-1]
24
25
  res = super().__new__(cls, s)
26
+ res.kw = kw
25
27
  res.isplus = isplus
26
28
  res.isend = isend
27
29
  return res
@@ -47,14 +49,16 @@ class Tag(str):
47
49
  self.pos = pos
48
50
 
49
51
  class AttribText(UserString):
50
- def __init__(self, s, l=0, c=0):
52
+ def __init__(self, s, l=0, c=0, **kw):
51
53
  super().__init__(s)
52
54
  self.pos = Pos(l, c)
55
+ self.kw = kw
53
56
  pass
54
57
 
55
58
  class OptBreak:
56
- def __init__(self, l=0, c=0):
59
+ def __init__(self, l=0, c=0, **kw):
57
60
  self.pos = Pos(l, c)
61
+ self.kw = kw
58
62
 
59
63
  def __repr__(self):
60
64
  return "OptBreak(" + str(self) + ")"
@@ -62,9 +66,10 @@ class OptBreak:
62
66
  return "//"
63
67
 
64
68
  class String(UserString):
65
- def __init__(self, s, l=0, c=0):
69
+ def __init__(self, s, l=0, c=0, **kw):
66
70
  super().__init__(s)
67
71
  self.pos = Pos(l, c)
72
+ self.kw = kw
68
73
 
69
74
  def __add__(self, s):
70
75
  return String(str(self) + s)
@@ -72,25 +77,28 @@ class String(UserString):
72
77
  pass
73
78
 
74
79
  class Attribs(UserDict):
75
- def __init__(self, l=0, c=0):
80
+ def __init__(self, l=0, c=0, **kw):
76
81
  super().__init__()
77
82
  self.pos = Pos(l, c)
83
+ self.kw = kw
78
84
 
79
85
  class Lexer:
80
86
 
81
87
  tokenre = regex.compile(r'((?:[^\\|/]|\\[\\|\n]|/[^/])+)|([\\|\n]|//)')
82
- 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^-]*)?\*?')
83
89
  attribsre = regex.compile(r'\s*([a-zA-Z_][a-zA-Z0-9_-]*)\s*=\s*"((?:\\"|[^"])*)"')
84
90
  textrunre = regex.compile(r'(?:[^\\]|\\[^a-zA-Z_*+])*')
85
91
 
86
- def __init__(self, txt):
92
+ def __init__(self, txt, expanded=False):
87
93
  self.txt = txt
94
+ self.expanded = expanded
88
95
 
89
96
  def __iter__(self):
90
97
  self.nexts = []
91
98
  self.cindex = 0
92
99
  self.lindex = 0
93
100
  self.lpos = 0
101
+ self.currxpand = None
94
102
  return self
95
103
 
96
104
  def __next__(self):
@@ -119,7 +127,9 @@ class Lexer:
119
127
  curri = m.end() + 1
120
128
  continue
121
129
  else:
122
- res = Tag(t.group(0), l=self.lindex, c=curri-self.lpos)
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)
123
133
  curri += t.end()
124
134
  break
125
135
  elif n == '|':
@@ -161,6 +171,13 @@ class Lexer:
161
171
  self.cindex = m.end()
162
172
  self.nexts.append(String(m.group(1)))
163
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
+
164
181
  class Grammar:
165
182
  category_markers = {
166
183
  "attrib": "cp vp usfm ca va cat",
@@ -184,8 +201,14 @@ class Grammar:
184
201
  "title": "mt1 mt2 mt3 mt4 mt",
185
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",
186
203
  }
204
+ category_tags = {
205
+ "char": ("char", "crossreferencechar", "footnotechar", "introchar", "listchar"),
206
+ "para": ("header", "introduction", "list", "otherpara", "sectionpara", "title", "versepara"),
207
+ "ms": ("milestone", )
208
+ }
187
209
 
188
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}
189
212
 
190
213
  attribmap = { 'jmp' : 'href', 'k' : 'key', 'qt-s': 'who', 'qt1-s': 'who', 'qt2-s': 'who',
191
214
  'qt3-s': 'who', 'qt4-s': 'who', 'qt5-s': 'who', 'rb': 'gloss', 't-s': 'sid', 'ts-s': 'sid',
@@ -217,13 +240,15 @@ def isfirstText(e):
217
240
  return True
218
241
 
219
242
  class Node:
220
- def __init__(self, parser, usxtag, tag, ispara=False, notag=False, pos=None):
243
+ def __init__(self, parser, usxtag, tag, ispara=False, notag=False, pos=None, **kw):
221
244
  self.parser = parser
222
245
  self.tag = tag
223
246
  self.ispara = ispara
224
247
  parent = parser.stack[-1] if len(parser.stack) else None
225
- self.element = parser.factory(usxtag, {} if notag or tag is None else {"style": tag},
226
- parent=getattr(parent, 'element', None), pos=pos)
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)
227
252
  if parent:
228
253
  parent.addNodeElement(self.element)
229
254
  self.attribnodes = []
@@ -303,7 +328,7 @@ class USXNode(Node):
303
328
  self.element.set('version', str(t).strip())
304
329
 
305
330
  class AttribNode(Node):
306
- def __init__(self, parser, parent, tag, pos=None):
331
+ def __init__(self, parser, parent, tag, pos=None, **kw):
307
332
  self.parser = parser
308
333
  self.parent = parent
309
334
  self.parent.addAttribNode(self)
@@ -320,8 +345,8 @@ class AttribNode(Node):
320
345
  self.parent.element.set(attrib, str(t).strip())
321
346
 
322
347
  class NumberNode(Node):
323
- def __init__(self, parser, usxtag, tag, ispara=False, pos=None):
324
- super().__init__(parser, usxtag, tag, ispara=ispara, pos=pos)
348
+ def __init__(self, parser, usxtag, tag, ispara=False, pos=None, **kw):
349
+ super().__init__(parser, usxtag, tag, ispara=ispara, pos=pos, **kw)
325
350
  self.hasarg = False
326
351
 
327
352
  def appendText(self, t):
@@ -342,8 +367,8 @@ class NumberNode(Node):
342
367
  self.parser.stack[-1].addNodeElement(e)
343
368
 
344
369
  class NoteNode(Node):
345
- def __init__(self, parser, usxtag, tag, pos=None):
346
- super().__init__(parser, usxtag, tag, pos=pos)
370
+ def __init__(self, parser, usxtag, tag, pos=None, **kw):
371
+ super().__init__(parser, usxtag, tag, pos=pos, **kw)
347
372
  self.hascaller = False
348
373
 
349
374
  def appendText(self, t):
@@ -365,31 +390,16 @@ class PeriphNode(Node):
365
390
  if t:
366
391
  self.element.set('alt', str(t).strip())
367
392
 
393
+ class UnknownNode(Node):
394
+ pass
395
+
368
396
 
369
397
  paratypes = ('header', 'introduction', 'list', 'otherpara', 'sectionpara', 'versepara', 'title', 'chapter', 'ident')
370
398
  paratags = ('rem', ' table')
371
- def setupParser(cls):
372
- # tag closed types
373
- for a in (('introchar', 'char'), ('listchar', 'char'), ('char', 'char'), ('_fig', 'figure')):
374
- def maketype(c, t):
375
- def dotype(self, tag):
376
- if tag.isend:
377
- return self.removeTag(str(tag))
378
- return self.addNode(Node(self, t, tag.basestr()))
379
- return dotype
380
- setattr(cls, a[0], maketype(*a))
381
- # implicit closed paras
382
- for a in paratypes:
383
- def dotype(self, tag):
384
- self.removeType(paratypes, paratags)
385
- return self.addNode(Node(self, 'para', str(tag), ispara=True))
386
- if not hasattr(cls, a):
387
- setattr(cls, a, dotype)
388
-
389
399
 
390
400
  class USFMParser:
391
401
 
392
- def __init__(self, txt, factory=None, grammar=None):
402
+ def __init__(self, txt, factory=None, grammar=None, expanded=False, strict=False):
393
403
  if factory is None:
394
404
  def makeel(tag, attrib, **extras):
395
405
  attrib.update({" "+k:v for k, v in extras.items()})
@@ -399,7 +409,32 @@ class USFMParser:
399
409
  grammar = Grammar()
400
410
  self.factory = factory
401
411
  self.grammar = grammar
402
- self.lexer = Lexer(txt)
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)
403
438
 
404
439
  def parse(self):
405
440
  self.result = []
@@ -449,6 +484,8 @@ class USFMParser:
449
484
  while len(self.stack):
450
485
  curr = self.stack.pop()
451
486
  if curr.tag == tag:
487
+ if curr.element.tag == "unk":
488
+ curr.element.tag = "char"
452
489
  break
453
490
  else:
454
491
  self.stack = oldstack
@@ -460,12 +497,31 @@ class USFMParser:
460
497
  oldstack = self.stack[:]
461
498
  while len(self.stack):
462
499
  curr = self.stack.pop()
500
+ e = curr.element
501
+ if e.tag == "usx":
502
+ self.stack.append(curr)
503
+ break
463
504
  curr.close()
464
- if curr is not None and (curr.tag in tags or self.grammar.marker_categories.get(curr.tag, None) in t):
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:
465
521
  break
466
522
  else:
467
523
  self.stack = oldstack
468
- return self.stack[-1]
524
+ return self.stack[-1] if len(self.stack) else None
469
525
 
470
526
  #### Event methods
471
527
 
@@ -583,12 +639,19 @@ class USFMParser:
583
639
  return self.addNode(Node(self, 'ms', tag.basestr(), pos=tag.pos))
584
640
 
585
641
  def unknown(self, tag):
586
- self.addNode(Node(self, 'ms', str(tag), pos=tag.pos))
587
- res = self.removeTag(tag)
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"
588
653
  return res
589
654
 
590
- setupParser(USFMParser)
591
-
592
655
  def main():
593
656
  import sys
594
657
  p = USFMParser(sys.argv[1])
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: usfmtc
3
- Version: 0.2.3
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