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.
- usfmtc-0.2/LICENSE.md +20 -0
- usfmtc-0.2/PKG-INFO +61 -0
- usfmtc-0.2/README.md +3 -0
- usfmtc-0.2/pyproject.toml +50 -0
- usfmtc-0.2/python/README.md +22 -0
- usfmtc-0.2/python/lib/usfmtc/__init__.py +379 -0
- usfmtc-0.2/python/lib/usfmtc/diagrams.py +247 -0
- usfmtc-0.2/python/lib/usfmtc/extension.py +79 -0
- usfmtc-0.2/python/lib/usfmtc/grammar.py +461 -0
- usfmtc-0.2/python/lib/usfmtc/parser.py +300 -0
- usfmtc-0.2/python/lib/usfmtc/railroad.py +1320 -0
- usfmtc-0.2/python/lib/usfmtc/reference.py +302 -0
- usfmtc-0.2/python/lib/usfmtc/rnc2rng/__init__.py +13 -0
- usfmtc-0.2/python/lib/usfmtc/rnc2rng/parser.py +741 -0
- usfmtc-0.2/python/lib/usfmtc/rnc2rng/rnctree.py +17 -0
- usfmtc-0.2/python/lib/usfmtc/rnc2rng/serializer.py +255 -0
- usfmtc-0.2/python/lib/usfmtc/sfmparser.py +345 -0
- usfmtc-0.2/python/lib/usfmtc/usfmgenerate.py +156 -0
- usfmtc-0.2/python/lib/usfmtc/usfmparser.py +590 -0
- usfmtc-0.2/python/lib/usfmtc/usjproc.py +88 -0
- usfmtc-0.2/python/lib/usfmtc/usx.rng +2182 -0
- usfmtc-0.2/python/lib/usfmtc/usxgrammar.py +36 -0
- usfmtc-0.2/python/lib/usfmtc/usxmodel.py +520 -0
- usfmtc-0.2/python/lib/usfmtc/usxparser.py +568 -0
- usfmtc-0.2/python/lib/usfmtc/xmlutils.py +139 -0
- usfmtc-0.2/python/lib/usfmtc.egg-info/PKG-INFO +61 -0
- usfmtc-0.2/python/lib/usfmtc.egg-info/SOURCES.txt +30 -0
- usfmtc-0.2/python/lib/usfmtc.egg-info/dependency_links.txt +1 -0
- usfmtc-0.2/python/lib/usfmtc.egg-info/entry_points.txt +2 -0
- usfmtc-0.2/python/lib/usfmtc.egg-info/requires.txt +4 -0
- usfmtc-0.2/python/lib/usfmtc.egg-info/top_level.txt +1 -0
- 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,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()
|