usfmtc 0.2__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/LICENSE.md +20 -0
  2. usfmtc-0.2/PKG-INFO +61 -0
  3. usfmtc-0.2/README.md +3 -0
  4. usfmtc-0.2/pyproject.toml +50 -0
  5. usfmtc-0.2/python/README.md +22 -0
  6. usfmtc-0.2/python/lib/usfmtc/__init__.py +379 -0
  7. usfmtc-0.2/python/lib/usfmtc/diagrams.py +247 -0
  8. usfmtc-0.2/python/lib/usfmtc/extension.py +79 -0
  9. usfmtc-0.2/python/lib/usfmtc/grammar.py +461 -0
  10. usfmtc-0.2/python/lib/usfmtc/parser.py +300 -0
  11. usfmtc-0.2/python/lib/usfmtc/railroad.py +1320 -0
  12. usfmtc-0.2/python/lib/usfmtc/reference.py +302 -0
  13. usfmtc-0.2/python/lib/usfmtc/rnc2rng/__init__.py +13 -0
  14. usfmtc-0.2/python/lib/usfmtc/rnc2rng/parser.py +741 -0
  15. usfmtc-0.2/python/lib/usfmtc/rnc2rng/rnctree.py +17 -0
  16. usfmtc-0.2/python/lib/usfmtc/rnc2rng/serializer.py +255 -0
  17. usfmtc-0.2/python/lib/usfmtc/sfmparser.py +345 -0
  18. usfmtc-0.2/python/lib/usfmtc/usfmgenerate.py +156 -0
  19. usfmtc-0.2/python/lib/usfmtc/usfmparser.py +590 -0
  20. usfmtc-0.2/python/lib/usfmtc/usjproc.py +88 -0
  21. usfmtc-0.2/python/lib/usfmtc/usx.rng +2182 -0
  22. usfmtc-0.2/python/lib/usfmtc/usxgrammar.py +36 -0
  23. usfmtc-0.2/python/lib/usfmtc/usxmodel.py +520 -0
  24. usfmtc-0.2/python/lib/usfmtc/usxparser.py +568 -0
  25. usfmtc-0.2/python/lib/usfmtc/xmlutils.py +139 -0
  26. usfmtc-0.2/python/lib/usfmtc.egg-info/PKG-INFO +61 -0
  27. usfmtc-0.2/python/lib/usfmtc.egg-info/SOURCES.txt +30 -0
  28. usfmtc-0.2/python/lib/usfmtc.egg-info/dependency_links.txt +1 -0
  29. usfmtc-0.2/python/lib/usfmtc.egg-info/entry_points.txt +2 -0
  30. usfmtc-0.2/python/lib/usfmtc.egg-info/requires.txt +4 -0
  31. usfmtc-0.2/python/lib/usfmtc.egg-info/top_level.txt +1 -0
  32. usfmtc-0.2/setup.cfg +4 -0
usfmtc-0.2/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright © 2022-2023 by the members of the USFM/USX technical committee, listed below:
2
+
3
+ - Martin Hosken, SIL International
4
+ - Jeff Klassen, United Bible Societies
5
+ - Joel Mathew, Bridge Connectivity Solutions
6
+ - John Wickberg, SIL International, Paratext
7
+ - Jonathan Robie, Clear Bible
8
+ - Kent Spielmann, Biblica
9
+
10
+ All specifications, documentation, schemas, and data are licensed under Creative Commons Attribution 4.0:
11
+
12
+ https://creativecommons.org/licenses/by/4.0/
13
+
14
+ All code in this project is licensed under the MIT license:
15
+
16
+ https://opensource.org/license/mit/
17
+
18
+ This code can be found in the following directories:
19
+
20
+ - python
usfmtc-0.2/PKG-INFO ADDED
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.1
2
+ Name: usfmtc
3
+ Version: 0.2
4
+ Summary: USFM reference parser
5
+ Author-email: USFM Technical Committee <fonts@sil.org>
6
+ License: Copyright © 2022-2023 by the members of the USFM/USX technical committee, listed below:
7
+
8
+ - Martin Hosken, SIL International
9
+ - Jeff Klassen, United Bible Societies
10
+ - Joel Mathew, Bridge Connectivity Solutions
11
+ - John Wickberg, SIL International, Paratext
12
+ - Jonathan Robie, Clear Bible
13
+ - Kent Spielmann, Biblica
14
+
15
+ All specifications, documentation, schemas, and data are licensed under Creative Commons Attribution 4.0:
16
+
17
+ https://creativecommons.org/licenses/by/4.0/
18
+
19
+ All code in this project is licensed under the MIT license:
20
+
21
+ https://opensource.org/license/mit/
22
+
23
+ This code can be found in the following directories:
24
+
25
+ - python
26
+
27
+ Project-URL: Home-Page, https://github.com/usfm-bible/tcdocs
28
+ Classifier: Environment :: Console
29
+ Classifier: Programming Language :: Python :: 3.8
30
+ Classifier: Intended Audience :: Developers
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Topic :: Text Processing :: Markup
33
+ Requires-Python: >=3.8
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE.md
36
+ Requires-Dist: lxml
37
+ Requires-Dist: rply
38
+ Provides-Extra: git
39
+
40
+ # usfmtc
41
+
42
+ Unified Standard Format Markup is the primary file format for the source of
43
+ bible translations. It is available in 3 serializations: USFM which is a simple
44
+ text markup using TeX style backslash codes to mark the various elements; USX
45
+ which is an XML serialization and USJ which is a JSON serialization.
46
+
47
+ The usfmtc package contains parsers and generators for USFM, USX and USJ. There
48
+ are two parsers and generators for USFM: validating and non validating.
49
+
50
+ The package comes with a script: usfmconv that can read any of the 3 file
51
+ formats and output to any of the 3 file formats.
52
+
53
+ The primary entry point for python is the usfmtc module:
54
+
55
+ ```
56
+ from usfmtc import readFile
57
+
58
+ usfmdoc = readFile(infile)
59
+ usfmdoc.saveAs(outfile)
60
+ ```
61
+
usfmtc-0.2/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # tcdocs
2
+
3
+ This is the document repository for the USFM/X Technical Committee.
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=62.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "usfmtc"
7
+ version = "0.2"
8
+ authors = [{name = "USFM Technical Committee", email = "fonts@sil.org"}]
9
+ readme = "python/README.md"
10
+ license = {file = "LICENSE.md"}
11
+ description = "USFM reference parser"
12
+ classifiers = [
13
+ "Environment :: Console",
14
+ "Programming Language :: Python :: 3.8",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Topic :: Text Processing :: Markup"
18
+ ]
19
+ requires-python = ">=3.8"
20
+ #dynamic = ["version"]
21
+
22
+ dependencies = [
23
+ "lxml",
24
+ "rply"
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ git = [
29
+ ]
30
+
31
+ [project.urls]
32
+ Home-Page = "https://github.com/usfm-bible/tcdocs"
33
+
34
+ [tool.setuptools.packages.find]
35
+ where = ["python/lib"]
36
+
37
+ [tool.setuptools.package-data]
38
+ usfmtc = ["*.rng"]
39
+
40
+ [tool.setuptools.dynamic]
41
+ # version = {attr = "usfmtc.version"}
42
+
43
+ #[tool.setuptools]
44
+ #packages = ["usfmtc"]
45
+
46
+ [tool.bdist_wheel]
47
+ universal = true
48
+
49
+ [project.scripts]
50
+ usfmconv = "usfmtc:main"
@@ -0,0 +1,22 @@
1
+ # usfmtc
2
+
3
+ Unified Standard Format Markup is the primary file format for the source of
4
+ bible translations. It is available in 3 serializations: USFM which is a simple
5
+ text markup using TeX style backslash codes to mark the various elements; USX
6
+ which is an XML serialization and USJ which is a JSON serialization.
7
+
8
+ The usfmtc package contains parsers and generators for USFM, USX and USJ. There
9
+ are two parsers and generators for USFM: validating and non validating.
10
+
11
+ The package comes with a script: usfmconv that can read any of the 3 file
12
+ formats and output to any of the 3 file formats.
13
+
14
+ The primary entry point for python is the usfmtc module:
15
+
16
+ ```
17
+ from usfmtc import readFile
18
+
19
+ usfmdoc = readFile(infile)
20
+ usfmdoc.saveAs(outfile)
21
+ ```
22
+
@@ -0,0 +1,379 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # nuitka configuration
4
+ # nuitka-project: --onefile
5
+ # nuitka-project: --include-data-files={MAIN_DIRECTORY}/usx.rng=usx.rng
6
+ # nuitka-project-if: {OS} in ("Windows",):
7
+ # nuitka-project: --output-filename=usfmconv.exe
8
+ # nuitka-project-else:
9
+ # nuitka-project: --output-filename=usfmconv.bin
10
+
11
+ import os
12
+ from usfmtc.sfmparser import parseusfm, UsfmParserBackend
13
+ from usfmtc.parser import NoParseError
14
+ from usfmtc.extension import Extensions
15
+ from usfmtc.xmlutils import ParentElement, prettyxml, writexml
16
+ from usfmtc.usxparser import USXConverter
17
+ from usfmtc.grammar import UsfmGrammarParser
18
+ from usfmtc.diagrams import UsfmRailRoad
19
+ from usfmtc.usxmodel import addesids, cleanup, messup, canonicalise
20
+ from usfmtc.usjproc import usxtousj, usjtousx
21
+ from usfmtc.usfmparser import USFMParser, Grammar
22
+ from usfmtc.usfmgenerate import usx2usfm
23
+ import xml.etree.ElementTree as et
24
+
25
+ def _readsrc(src):
26
+ if hasattr(src, "read"):
27
+ return src.read()
28
+ elif not isinstance(src, str): # we're a parsed xml doc
29
+ return src
30
+ elif len(src) < 128 and os.path.exists(src):
31
+ with open(src, encoding="utf-8") as inf:
32
+ data = inf.read()
33
+ return data
34
+ elif "\n" in src or len(src) > 127:
35
+ return src
36
+ else:
37
+ raise FileNotFoundError(src)
38
+
39
+ def _grammarDoc(gsrc, extensions=[], factory=et):
40
+ data = _readsrc(gsrc)
41
+ if isinstance(data, (str, bytes)):
42
+ rdoc = factory.ElementTree(factory.fromstring(data))
43
+ else:
44
+ rdoc = data
45
+ for ef in extensions:
46
+ e = Extensions(ef)
47
+ dirty = e.applyto(rdoc, factory=factory)
48
+ return rdoc
49
+
50
+ def _usfmGrammar(rdoc, backend=None, start=None):
51
+ if backend is None:
52
+ backend = UsfmParserBackend()
53
+ sfmproc = UsfmGrammarParser(rdoc, backend)
54
+ if start is None:
55
+ start = "Scripture"
56
+ parser = sfmproc.parseRef(start)
57
+ return parser
58
+
59
+ def usfmGrammar(gsrc, extensions=[], altparser=True, backend=None, start=None):
60
+ """ Create UsfmGrammarParser from gsrc as used by USX.fromUsfm """
61
+ if len(extensions) or not altparser:
62
+ rdoc = _grammarDoc(gsrc, extensions)
63
+ if altparser:
64
+ res = Grammar()
65
+ if len(extensions):
66
+ for e in extensions:
67
+ res.readmrkrs(e)
68
+ return res
69
+ return _usfmGrammar(rdoc, backend, start)
70
+
71
+
72
+ _filetypes = {".xml": "usx", ".usx": "usx", ".usfm": "usfm", ".sfm": "usfm3.0", ".json": "usj"}
73
+
74
+ def readFile(infpath, informat=None, gramfile=None, grammar=None, extfiles=[], altparser=True):
75
+ """ Reads a USFM file of a given type or inferred from the filename
76
+ extension. extfiles allows for extra markers.ext files to extend the grammar"""
77
+ if informat is None:
78
+ inroot, ext = os.path.splitext(infpath)
79
+ intype = _filetypes.get(ext.lower(), informat)
80
+ else:
81
+ intype = informat
82
+ if intype is None:
83
+ return None
84
+
85
+ if intype == "usx":
86
+ usxdoc = USX.fromUsx(infpath)
87
+ elif intype == "usj":
88
+ usxdoc = USX.fromUsj(infpath)
89
+ elif intype.startswith("usfm"):
90
+ if grammar is None:
91
+ if gramfile is None:
92
+ for a in ([], ['..', '..', '..', 'grammar']):
93
+ gramfile = os.path.join(os.path.dirname(__file__), *a, "usx.rng")
94
+ if os.path.exists(gramfile):
95
+ break
96
+ fname = getattr(infpath, 'name', infpath)
97
+ extfiles.append(os.path.join(os.path.dirname(fname), "markers.ext"))
98
+ exts = [x for x in extfiles if os.path.exists(x)]
99
+ grammar = usfmGrammar(gramfile, extensions=exts, altparser=altparser)
100
+ usxdoc = USX.fromUsfm(infpath, grammar, altparser=altparser)
101
+ return usxdoc
102
+
103
+
104
+ class USX:
105
+ @classmethod
106
+ def fromUsx(cls, src, elfactory=None):
107
+ """ Loads USX and creates USX object to hold it """
108
+ if elfactory is None:
109
+ elfactory = ParentElement
110
+ tb = et.TreeBuilder(element_factory=elfactory)
111
+ parser = et.XMLParser(target=tb)
112
+ if os.path.exists(src):
113
+ inf = open(src, encoding="utf_8_sig")
114
+ else:
115
+ inf = src
116
+ if hasattr(inf, "read"):
117
+ anet = et.ElementTree()
118
+ anet.parse(inf, parser=parser)
119
+ res = anet.getroot()
120
+ if src != inf:
121
+ inf.close()
122
+ else:
123
+ try:
124
+ res = et.fromstring(src, parser=parser)
125
+ except et.ParseError:
126
+ return None
127
+ return cls(res)
128
+
129
+ @classmethod
130
+ def fromUsfm(cls, src, grammar, altparser=True, elfactory=None, timeout=1e7):
131
+ """ Parses USFM using UsfmGrammarParser grammar and creates USX object.
132
+ Raise usfmtc.parser.NoParseError on error. """
133
+ data = _readsrc(src)
134
+
135
+ if altparser:
136
+ p = USFMParser(data, factory=elfactory or ParentElement, grammar=grammar)
137
+ xml = p.parse()
138
+ else:
139
+ # This can raise usfmtc.parser.NoParseError
140
+ result = parseusfm(data, grammar, timeout=timeout, isdata=True)
141
+ xml = result.asEt(elfactory=elfactory)
142
+
143
+ cleanup(xml) # normalize space, de-escape chars, cell aligns, etc.
144
+ res = cls(xml)
145
+ return res
146
+
147
+ @classmethod
148
+ def fromUsj(cls, src, elfactory=None):
149
+ data = _readsrc(src)
150
+ djson = json.loads(data)
151
+ xml = usjtousx(djson, elfactory=elfactory)
152
+ return cls(xml)
153
+
154
+ def __init__(self, xml):
155
+ self.xml = xml # an Element, not an ElementTree
156
+
157
+ def _outwrite(self, file, dat, fn=None):
158
+ if fn is None:
159
+ fn = lambda f, d: f.write(d)
160
+ if file is None:
161
+ fh = io.StringIO()
162
+ fn(fh, dat)
163
+ res = fh.getvalue()
164
+ fh.close()
165
+ return res
166
+ if not hasattr(file, "read"):
167
+ fh = open(file, "w", encoding="utf-8")
168
+ fn(fh, dat)
169
+ fh.close()
170
+ else:
171
+ fn(file, dat)
172
+ return True
173
+
174
+ def outUsx(self, file=None):
175
+ """ Output pretty XML USX. If file is None returns string """
176
+ if self.xml is None:
177
+ return None
178
+ prettyxml(self.xml)
179
+ self._outwrite(file, self.xml, fn=writexml)
180
+
181
+ def outUsfm(self, grammar, file=None, altparser=True, **kw):
182
+ """ Output USFM from USX object. grammar is et doc. If file is None returns string """
183
+ el = messup(self.xml)
184
+ if altparser:
185
+ return self._outwrite(file, el, fn=usx2usfm)
186
+ parser = USXConverter(grammar.getroot(), **kw)
187
+ res = parser.parse(el)
188
+ if res:
189
+ dat = "".join(res.results)
190
+ return self._outwrite(file, dat)
191
+ return False
192
+
193
+ def outUsj(self, file=None):
194
+ """ Output USJ from USX object. If file is None returns dict """
195
+ res, _ = usxtousj(self.xml.getroot())
196
+ if file is None:
197
+ return res
198
+ else:
199
+ dat = json.dumps(res, indent=2)
200
+ self._outwrite(file, dat)
201
+
202
+ def saveAs(self, outfpath, outformat=None, addesids=False, grammar=None, gramfile=None, version=None, altparser=True):
203
+ """ Saves the document to a file in the appropriate format, either given
204
+ or inferred from the filename extension. """
205
+ if outformat is None:
206
+ outroot, ext = os.path.splitext(outfpath)
207
+ outtype = _filetypes.get(ext.lower(), outformat)
208
+ else:
209
+ outtype = outformat
210
+ if outtype is None:
211
+ return
212
+
213
+ if outtype == "usx":
214
+ if addesids:
215
+ usxdoc.addesids()
216
+ self.outUsx(outfpath)
217
+ elif outtype == "usj":
218
+ self.outUsj(outfpath)
219
+ elif outtype == "usfm":
220
+ if grammar is None and not altparser:
221
+ if gramfile is None:
222
+ for a in ([], ['..', '..', '..', 'grammar']):
223
+ gramfile = os.path.join(os.path.dirname(__file__), *a, "usx.rng")
224
+ if os.path.exists(gramfile):
225
+ break
226
+ grammar = _grammarDoc(gramfile)
227
+ if outtype == "usfm3.0":
228
+ outtype = "usfm"
229
+ if version is None:
230
+ version = "3.0"
231
+ self.outUsfm(grammar, outfpath, outversion=version, altparser=altparser)
232
+
233
+ def canonicalise(self):
234
+ canonicalise(self.getroot())
235
+
236
+ def getroot(self):
237
+ """ Returns root XML element """
238
+ return self.xml
239
+
240
+ def addesids(self):
241
+ """ Add esids to USX object (eid, sids, vids) """
242
+ addesids(self.xml)
243
+
244
+ @property
245
+ def version(self):
246
+ return self.getroot().get('version', None)
247
+
248
+ @version.setter
249
+ def version(self, version):
250
+ self.getroot().set('version', str(version))
251
+
252
+ def main(hookcli=None, hookusx=None):
253
+
254
+ import argparse, logging, sys
255
+ from glob import glob
256
+
257
+ parser = argparse.ArgumentParser()
258
+ parser.add_argument("infile",nargs="+",help="Input file")
259
+ parser.add_argument("-o", "--outfile",help="Output file, with inferred format")
260
+ parser.add_argument("-F","--outformat",help="Output format [usfm, usx, usj, usfm3.0]")
261
+ parser.add_argument("-I","--informat",help="Input format [usfm, usx, usj]")
262
+ parser.add_argument("-g","--grammar",help="Grammar file to use, if needed")
263
+ parser.add_argument("-e","--esids",action="store_true",help="Add esids, vids, sids, etc. to USX output")
264
+ parser.add_argument("-v","--version",default=None,help="Set USFM version [3.1]")
265
+ parser.add_argument("-x","--extfiles",action="append",default=[],help="markers.ext files to include")
266
+ parser.add_argument("--qusfm",action="store_true",help="Use quick nonvalidating USFM parser")
267
+ parser.add_argument("-C","--canonical",action="store_true",help="Do not canonicalise")
268
+ parser.add_argument("-l","--logging",help="Set logging level to usfmxtest.log")
269
+ parser.add_argument("-q","--quiet",action="store_true",help="Don't say much")
270
+ parser.add_argument("--nooutput",action="store_true",help="Don't output any data")
271
+ if hookcli is not None:
272
+ hookcli(parser)
273
+ args = parser.parse_args()
274
+
275
+ if args.logging:
276
+ try:
277
+ loglevel = int(args.logging)
278
+ except ValueError:
279
+ loglevel = getattr(logging, args.logging.upper(), None)
280
+ if isinstance(loglevel, int):
281
+ parms = {'level': loglevel, 'datefmt': '%d/%b/%Y %H:%M:%S',
282
+ 'format': '%(asctime)s.%(msecs)03d %(levelname)s:%(module)s(%(lineno)d) %(message)s'}
283
+ logfh = open("usfmconv.log", "w", encoding="utf-8")
284
+ parms.update(stream=logfh, filemode="w") #, encoding="utf-8")
285
+ try:
286
+ logging.basicConfig(**parms)
287
+ except FileNotFoundError as e: # no write access to the log
288
+ print("Exception", e)
289
+ log = logging.getLogger('usfmconv')
290
+ else:
291
+ log = None
292
+
293
+ def doerror(msg, doexit=True):
294
+ if log:
295
+ log.error(msg)
296
+ if not args.quiet:
297
+ print(msg)
298
+ if doexit:
299
+ sys.exit(1)
300
+
301
+ fileexts = {"usx": ".xml", "usfm": ".usfm", "usj": ".json"}
302
+
303
+ def _makeoutfile(infile, oformat):
304
+ outext = fileexts.get(oformat, None)
305
+ inroot, ext = os.path.splitext(infile)
306
+ return inroot + outext if outext else None
307
+
308
+ infiles = sum((glob(x) for x in args.infile), [])
309
+ if not len(infiles):
310
+ doerror("No files found in {args.infile}")
311
+
312
+ root, ext = os.path.splitext(infiles[0])
313
+ args.informat = args.informat or _filetypes.get(ext.lower(), args.informat)
314
+ if args.outformat is None:
315
+ if args.outfile is not None and not os.path.isdir(args.outfile):
316
+ root, ext = os.path.splitext(args.outfile)
317
+ args.outformat = _filetypes.get(ext.lower(), None)
318
+ ingrammar = None
319
+ outgrammar = None
320
+ if args.informat.startswith("usfm") or args.outformat.startswith("usfm"):
321
+ if args.grammar is None:
322
+ for a in ([], ['..', '..', '..', 'grammar']):
323
+ args.grammar = os.path.join(os.path.dirname(__file__), *a, "usx.rng")
324
+ if os.path.exists(args.grammar):
325
+ break
326
+ if args.informat.startswith("usfm"):
327
+ args.extfiles.append(os.path.join(os.path.dirname(infiles[0]), "markers.ext"))
328
+ exts = [x for x in args.extfiles if os.path.exists(x)]
329
+ ingrammar = usfmGrammar(args.grammar, altparser=args.qusfm, extensions=exts)
330
+ if args.outformat and args.outformat.startswith("usfm"):
331
+ outgrammar = _grammarDoc(args.grammar)
332
+
333
+ for infile in infiles:
334
+ outfile = None
335
+ if args.outfile is None:
336
+ outfile = _makeoutfile(infile, args.format)
337
+ elif os.path.isdir(args.outfile):
338
+ outf = _makeoutfile(infile, args.format)
339
+ if outf is None:
340
+ doerror(f"invalid output format {args.format} in {args.outfile}")
341
+ outfile = os.path.join(args.outfile, os.path.basename(outf))
342
+ elif len(infiles) == 1:
343
+ outfile = args.outfile
344
+
345
+ if infile == "-":
346
+ infile = sys.stdin
347
+ if outfile == "-":
348
+ outfile = sys.stdout
349
+
350
+ usxdoc = None
351
+ if not args.quiet:
352
+ print(f"{infile} -> {outfile}" if outfile else f"{infile}")
353
+ try:
354
+ usxdoc = readFile(infile, informat=args.informat, grammar=ingrammar, altparser=args.qusfm)
355
+ except NoParseError as e:
356
+ doerror(f"Failed to parse {infile}: {e}", False)
357
+
358
+ if len(infiles) == 1 and usxdoc is None:
359
+ doerror(f"Unable to read in {args.infile}")
360
+
361
+ if hookusx is not None:
362
+ hookusx(usxdoc, args)
363
+
364
+ if args.nooutput or outfile is None or usxdoc is None:
365
+ continue
366
+
367
+ version = usxdoc.version
368
+ if version is None:
369
+ usxdoc.version = args.version or "3.1"
370
+ elif args.version is not None:
371
+ usxdoc.version = args.version
372
+
373
+ if not args.canonical:
374
+ usxdoc.canonicalise()
375
+
376
+ usxdoc.saveAs(outfile, outformat=args.outformat, addesids=args.esids, grammar=outgrammar, altparser=args.qusfm)
377
+
378
+ if __name__ == "__main__":
379
+ main()