uEdition 0.8.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of uEdition might be problematic. Click here for more details.
- uedition/CHANGELOG.md +75 -0
- uedition/__about__.py +1 -1
- uedition/__init__.py +3 -3
- uedition/__main__.py +1 -2
- uedition/cli/__init__.py +8 -12
- uedition/cli/build.py +72 -31
- uedition/cli/check.py +24 -44
- uedition/cli/language.py +4 -10
- uedition/cli/serve.py +7 -8
- uedition/cli/update.py +1 -1
- uedition/ext/__init__.py +1 -1
- uedition/ext/config.py +33 -52
- uedition/ext/language_switcher.js +3 -3
- uedition/ext/language_switcher.py +8 -9
- uedition/ext/tei/__init__.py +32 -0
- uedition/ext/tei/builder.py +297 -0
- uedition/ext/{tei.py → tei/parser.py} +17 -35
- uedition/ext/tei/tei_download.js +34 -0
- uedition/settings.py +28 -30
- {uedition-0.8.0.dist-info → uedition-1.0.0.dist-info}/METADATA +16 -6
- uedition-1.0.0.dist-info/RECORD +26 -0
- {uedition-0.8.0.dist-info → uedition-1.0.0.dist-info}/WHEEL +1 -1
- uedition-1.0.0.dist-info/entry_points.txt +5 -0
- uedition-0.8.0.dist-info/RECORD +0 -22
- uedition-0.8.0.dist-info/entry_points.txt +0 -2
- {uedition-0.8.0.dist-info → uedition-1.0.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function() {
|
|
1
|
+
(function () {
|
|
2
2
|
async function setup() {
|
|
3
3
|
try {
|
|
4
4
|
let buttonContainer = document.querySelector('.article-header-buttons');
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
link.classList.add('fw-bold');
|
|
40
40
|
}
|
|
41
41
|
link.innerHTML = langConfig.label;
|
|
42
|
-
link.setAttribute('href',
|
|
42
|
+
link.setAttribute('href', document.querySelector("html").getAttribute("data-content_root") + '../' + langConfig.path + '/' + DOCUMENTATION_OPTIONS.pagename + DOCUMENTATION_OPTIONS.LINK_SUFFIX);
|
|
43
43
|
|
|
44
44
|
item.append(link);
|
|
45
45
|
menu.append(item);
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
container.append(button);
|
|
51
51
|
container.append(menu);
|
|
52
52
|
buttonContainer.prepend(container);
|
|
53
|
-
} catch(e) {
|
|
53
|
+
} catch (e) {
|
|
54
54
|
console.error(e);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -3,25 +3,23 @@
|
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
4
|
"""The Language Switcher extension."""
|
|
5
5
|
import json
|
|
6
|
-
|
|
7
|
-
from importlib.resources import files, as_file
|
|
6
|
+
from importlib.resources import as_file, files
|
|
8
7
|
from os import path
|
|
8
|
+
|
|
9
9
|
from sphinx.application import Sphinx
|
|
10
10
|
from sphinx.util.fileutil import copy_asset_file
|
|
11
11
|
|
|
12
|
-
from
|
|
12
|
+
from uedition.settings import settings
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def add_language_switcher(app: Sphinx) -> None:
|
|
16
16
|
"""Add the language switcher in-line and file JavaScript."""
|
|
17
|
-
app.add_js_file(
|
|
18
|
-
None, body=f"""DOCUMENTATION_OPTIONS.UEDITION = {json.dumps(settings)}"""
|
|
19
|
-
)
|
|
17
|
+
app.add_js_file(None, body=f"""DOCUMENTATION_OPTIONS.UEDITION = {json.dumps(settings)}""")
|
|
20
18
|
app.add_js_file("language_switcher.js")
|
|
21
19
|
app.add_css_file("language_switcher.css")
|
|
22
20
|
|
|
23
21
|
|
|
24
|
-
def copy_custom_files(app: Sphinx, exc: bool) -> None:
|
|
22
|
+
def copy_custom_files(app: Sphinx, exc: bool) -> None: # noqa: FBT001
|
|
25
23
|
"""Copy the language_switcher.js file from the package."""
|
|
26
24
|
if app.builder.format == "html" and not exc:
|
|
27
25
|
staticdir = path.join(app.builder.outdir, "_static")
|
|
@@ -34,5 +32,6 @@ def copy_custom_files(app: Sphinx, exc: bool) -> None:
|
|
|
34
32
|
|
|
35
33
|
def setup(app: Sphinx) -> None:
|
|
36
34
|
"""Set up the Language switcher extension."""
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
if len(settings["languages"]) > 1:
|
|
36
|
+
app.connect("builder-inited", add_language_switcher)
|
|
37
|
+
app.connect("build-finished", copy_custom_files)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""TEI extension for Sphinx."""
|
|
2
|
+
|
|
3
|
+
from importlib.resources import as_file, files
|
|
4
|
+
from os import path
|
|
5
|
+
|
|
6
|
+
from sphinx.application import Sphinx
|
|
7
|
+
from sphinx.util.fileutil import copy_asset_file
|
|
8
|
+
|
|
9
|
+
from uedition.ext.tei import parser
|
|
10
|
+
from uedition.settings import settings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def add_language_switcher(app: Sphinx) -> None:
|
|
14
|
+
"""Add the language switcher in-line and file JavaScript."""
|
|
15
|
+
app.add_js_file("tei_download.js")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def copy_custom_files(app: Sphinx, exc: bool) -> None: # noqa: FBT001
|
|
19
|
+
"""Copy the language_switcher.js file from the package."""
|
|
20
|
+
if app.builder.format == "html" and not exc:
|
|
21
|
+
staticdir = path.join(app.builder.outdir, "_static")
|
|
22
|
+
ext_pkg = files("uedition.ext.tei")
|
|
23
|
+
with as_file(ext_pkg / "tei_download.js") as js_file:
|
|
24
|
+
copy_asset_file(str(js_file), staticdir)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def setup(app: Sphinx) -> None:
|
|
28
|
+
"""Set up the TEI Sphinx extension."""
|
|
29
|
+
parser.setup(app)
|
|
30
|
+
if settings["output"]["tei"]:
|
|
31
|
+
app.connect("builder-inited", add_language_switcher)
|
|
32
|
+
app.connect("build-finished", copy_custom_files)
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Builder for generating TEI XML outputs."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import xml.sax.saxutils
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
|
|
8
|
+
import sphinx
|
|
9
|
+
import sphinx_jupyterbook_latex
|
|
10
|
+
from docutils import nodes
|
|
11
|
+
from docutils.io import StringOutput
|
|
12
|
+
from sphinx.application import Sphinx
|
|
13
|
+
from sphinx.builders.xml import XMLBuilder
|
|
14
|
+
from sphinx.locale import __
|
|
15
|
+
from sphinx.util.osutil import ensuredir, os_path
|
|
16
|
+
from sphinx.writers.xml import XMLWriter
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
MAPPINGS = [
|
|
21
|
+
{"cls": nodes.document, "tagname": "body", "type": "block"},
|
|
22
|
+
{"cls": nodes.section, "tagname": "div", "type": "block"},
|
|
23
|
+
{"cls": nodes.paragraph, "tagname": "p", "type": "block"},
|
|
24
|
+
{"cls": nodes.title, "tagname": "head", "type": "block"},
|
|
25
|
+
{"cls": nodes.bullet_list, "tagname": "list", "type": "block", "attrs": [{"target": "rend", "value": "bulleted"}]},
|
|
26
|
+
{
|
|
27
|
+
"cls": nodes.enumerated_list,
|
|
28
|
+
"tagname": "list",
|
|
29
|
+
"type": "block",
|
|
30
|
+
"attrs": [{"target": "rend", "value": "numbered"}],
|
|
31
|
+
},
|
|
32
|
+
{"cls": nodes.list_item, "tagname": "item", "type": "block"},
|
|
33
|
+
{
|
|
34
|
+
"cls": nodes.literal_block,
|
|
35
|
+
"tagname": "div",
|
|
36
|
+
"type": "block",
|
|
37
|
+
"attrs": [{"target": "type", "value": "literal-block"}, {"source": "language", "target": "subtype"}],
|
|
38
|
+
},
|
|
39
|
+
{"cls": nodes.compound, "tagname": "div", "type": "block"},
|
|
40
|
+
{"cls": sphinx.addnodes.toctree},
|
|
41
|
+
{
|
|
42
|
+
"cls": nodes.footnote,
|
|
43
|
+
"tagname": "note",
|
|
44
|
+
"type": "block",
|
|
45
|
+
"attrs": [
|
|
46
|
+
{"target": "type", "value": "footnote"},
|
|
47
|
+
{"target": "target", "source": "backrefs", "format": "#{value}", "join": " "},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{"cls": sphinx_jupyterbook_latex.nodes.HiddenCellNode},
|
|
51
|
+
{
|
|
52
|
+
"cls": nodes.transition,
|
|
53
|
+
"tagname": "div",
|
|
54
|
+
"type": "empty",
|
|
55
|
+
"attrs": [{"target": "rend", "value": "horizontal-line"}],
|
|
56
|
+
},
|
|
57
|
+
{"cls": nodes.inline, "tagname": "hi", "type": "inline"},
|
|
58
|
+
{"cls": nodes.literal, "tagname": "hi", "type": "inline"},
|
|
59
|
+
{"cls": nodes.strong, "tagname": "hi", "type": "inline", "attrs": [{"target": "rend", "value": "bold"}]},
|
|
60
|
+
{"cls": nodes.emphasis, "tagname": "hi", "type": "inline", "attrs": [{"target": "rend", "value": "italic"}]},
|
|
61
|
+
{"cls": nodes.label, "tagname": "label", "type": "inline"},
|
|
62
|
+
{
|
|
63
|
+
"cls": nodes.reference,
|
|
64
|
+
"tagname": "ref",
|
|
65
|
+
"type": "inline",
|
|
66
|
+
"attrs": [{"source": "refuri", "target": "target"}],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"cls": nodes.footnote_reference,
|
|
70
|
+
"tagname": "ref",
|
|
71
|
+
"type": "inline",
|
|
72
|
+
"attrs": [{"source": "refid", "target": "target", "format": "#{value}"}],
|
|
73
|
+
},
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TEITranslator(nodes.GenericNodeVisitor):
|
|
78
|
+
"""Translator for converting a Docutils document to TEI XML."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, document: nodes.document) -> None:
|
|
81
|
+
"""Initialise the translator."""
|
|
82
|
+
nodes.NodeVisitor.__init__(self, document)
|
|
83
|
+
|
|
84
|
+
# Reporter
|
|
85
|
+
self.warn = self.document.reporter.warning
|
|
86
|
+
self.error = self.document.reporter.error
|
|
87
|
+
|
|
88
|
+
# Settings
|
|
89
|
+
self.settings = settings = document.settings
|
|
90
|
+
self.indent = self.newline = ""
|
|
91
|
+
if settings.newlines:
|
|
92
|
+
self.newline = "\n"
|
|
93
|
+
if settings.indents:
|
|
94
|
+
self.newline = "\n"
|
|
95
|
+
self.indent = " "
|
|
96
|
+
self.level = 0
|
|
97
|
+
self.inline_level = 0
|
|
98
|
+
self.fixed_level = 0
|
|
99
|
+
|
|
100
|
+
self.output = []
|
|
101
|
+
if settings.xml_declaration:
|
|
102
|
+
self.output.append(f'<?xml version="1.0" encoding="{settings.output_encoding}"?>\n')
|
|
103
|
+
|
|
104
|
+
def rule_for_node(self, node: nodes.Element) -> dict:
|
|
105
|
+
"""Get the transformation rule for a node."""
|
|
106
|
+
for rule in MAPPINGS:
|
|
107
|
+
if isinstance(node, rule["cls"]):
|
|
108
|
+
return rule
|
|
109
|
+
self.warn(f"Unknown node {node.__class__.__module__}.{node.__class__.__qualname__} ({node.attlist()})")
|
|
110
|
+
return {"tagname": "div", "type": "block"}
|
|
111
|
+
|
|
112
|
+
def default_visit(self, node: nodes.Element) -> None:
|
|
113
|
+
"""Visit a generic node."""
|
|
114
|
+
rule = self.rule_for_node(node)
|
|
115
|
+
# Skip the node if it has no associated TEI "tagname"
|
|
116
|
+
if "tagname" not in rule:
|
|
117
|
+
raise nodes.SkipNode
|
|
118
|
+
# Special handling for the root document, which needs the TEI structure wrapping
|
|
119
|
+
if isinstance(node, nodes.document):
|
|
120
|
+
self.output.append('<tei:TEI xmlns:tei="http://www.tei-c.org/ns/1.0">\n')
|
|
121
|
+
self.output.append(" <tei:text>\n")
|
|
122
|
+
self.level += 2
|
|
123
|
+
self.output.append(self.indent * self.level)
|
|
124
|
+
self.output.append(f"<tei:{rule['tagname']}")
|
|
125
|
+
# Process the default "ids" and "classes" list
|
|
126
|
+
output_attrs = {}
|
|
127
|
+
for attr in node.attlist():
|
|
128
|
+
if attr[0] == "ids":
|
|
129
|
+
output_attrs["xml:id"] = attr[1][0]
|
|
130
|
+
# self.output.append(f' xml:id={xml.sax.saxutils.quoteattr()}')
|
|
131
|
+
elif attr[0] == "classes":
|
|
132
|
+
if isinstance(attr[1], list):
|
|
133
|
+
output_attrs["rend"] = " ".join(attr[1])
|
|
134
|
+
else:
|
|
135
|
+
output_attrs["rend"] = str(attr[1])
|
|
136
|
+
# Process all configured attributes
|
|
137
|
+
if "attrs" in rule:
|
|
138
|
+
for attr_rule in rule["attrs"]:
|
|
139
|
+
if "value" in attr_rule:
|
|
140
|
+
output_attrs[attr_rule["target"]] = attr_rule["value"]
|
|
141
|
+
for attr in node.attlist():
|
|
142
|
+
if "source" in attr_rule and attr[0] == attr_rule["source"]:
|
|
143
|
+
output_attrs[attr_rule["target"]] = attr[1]
|
|
144
|
+
if attr_rule["target"] in output_attrs and isinstance(output_attrs[attr_rule["target"]], list):
|
|
145
|
+
joiner = attr_rule["join"] if "join" in attr_rule else " "
|
|
146
|
+
if "format" in attr_rule:
|
|
147
|
+
output_attrs[attr_rule["target"]] = joiner.join(
|
|
148
|
+
[attr_rule["format"].replace("{value}", v) for v in output_attrs[attr_rule["target"]]]
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
output_attrs[attr_rule["target"]] = joiner.join(output_attrs[attr_rule["target"]])
|
|
152
|
+
elif "format" in attr_rule:
|
|
153
|
+
output_attrs[attr_rule["target"]] = attr_rule["format"].replace(
|
|
154
|
+
"{value}", output_attrs[attr_rule["target"]]
|
|
155
|
+
)
|
|
156
|
+
# Write the sorted attributes
|
|
157
|
+
sorted_output_attrs = list(output_attrs.items())
|
|
158
|
+
sorted_output_attrs.sort()
|
|
159
|
+
for name, value in sorted_output_attrs:
|
|
160
|
+
self.output.append(f" {name}={xml.sax.saxutils.quoteattr(value)}")
|
|
161
|
+
if rule["type"] == "empty":
|
|
162
|
+
self.output.append("/>")
|
|
163
|
+
self.output.append(self.newline)
|
|
164
|
+
raise nodes.SkipNode
|
|
165
|
+
else:
|
|
166
|
+
self.output.append(">")
|
|
167
|
+
# Update the indentation levels
|
|
168
|
+
self.level += 1
|
|
169
|
+
if isinstance(node, nodes.FixedTextElement):
|
|
170
|
+
self.fixed_level += 1
|
|
171
|
+
if rule["type"] == "inline":
|
|
172
|
+
self.inline_level += 1
|
|
173
|
+
elif rule["type"] == "block":
|
|
174
|
+
self.output.append(self.newline)
|
|
175
|
+
|
|
176
|
+
def default_departure(self, node: nodes.Element) -> None:
|
|
177
|
+
"""Depart a generic node."""
|
|
178
|
+
rule = self.rule_for_node(node)
|
|
179
|
+
self.level -= 1
|
|
180
|
+
# Only indent if we are not inside an inline element
|
|
181
|
+
if self.inline_level == 0:
|
|
182
|
+
self.output.append(self.indent * self.level)
|
|
183
|
+
self.output.append(f"</tei:{rule['tagname']}>")
|
|
184
|
+
# Update the indentation levels
|
|
185
|
+
if isinstance(node, nodes.FixedTextElement):
|
|
186
|
+
self.fixed_level -= 1
|
|
187
|
+
self.output.append(self.newline)
|
|
188
|
+
if rule["type"] == "inline":
|
|
189
|
+
self.inline_level -= 1
|
|
190
|
+
if isinstance(node, nodes.document):
|
|
191
|
+
self.output.append(" </tei:text>\n")
|
|
192
|
+
self.output.append("</tei:TEI>")
|
|
193
|
+
self.level -= 2
|
|
194
|
+
|
|
195
|
+
# specific visit and depart methods
|
|
196
|
+
# ---------------------------------
|
|
197
|
+
|
|
198
|
+
def visit_Text(self, node: nodes.TextElement) -> None: # noqa: N802
|
|
199
|
+
"""Output the Text node content."""
|
|
200
|
+
text = xml.sax.saxutils.escape(node.astext())
|
|
201
|
+
if self.inline_level > 0:
|
|
202
|
+
if self.fixed_level > 0:
|
|
203
|
+
self.output.append(text)
|
|
204
|
+
else:
|
|
205
|
+
self.output.append(text)
|
|
206
|
+
else:
|
|
207
|
+
self.output.append(f"{self.indent*self.level}<tei:span>{text}</tei:span>\n")
|
|
208
|
+
|
|
209
|
+
def depart_Text(self, node: nodes.TextElement) -> None: # noqa: N802
|
|
210
|
+
"""Unused."""
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
def visit_raw(self, node: nodes.raw) -> None:
|
|
214
|
+
"""Visit a raw node."""
|
|
215
|
+
if "tei" not in node.get("format", "").split():
|
|
216
|
+
raise nodes.SkipNode
|
|
217
|
+
xml_string = node.astext()
|
|
218
|
+
self.output.append(xml_string)
|
|
219
|
+
raise nodes.SkipNode
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class TEIBuilder(XMLBuilder):
|
|
223
|
+
"""Builds TEI representations of the μEdition."""
|
|
224
|
+
|
|
225
|
+
name = "tei"
|
|
226
|
+
format = "tei"
|
|
227
|
+
epilog = __("The TEI files are in %(outdir)s.")
|
|
228
|
+
|
|
229
|
+
out_suffix = ".tei"
|
|
230
|
+
allow_parallel = True
|
|
231
|
+
|
|
232
|
+
_writer_class: type[XMLWriter] = XMLWriter
|
|
233
|
+
writer: XMLWriter
|
|
234
|
+
default_translator_class = TEITranslator
|
|
235
|
+
|
|
236
|
+
def get_outdated_docs(self) -> Iterator[str]:
|
|
237
|
+
"""Determine the list of outdated documents."""
|
|
238
|
+
for docname in self.env.found_docs:
|
|
239
|
+
if docname not in self.env.all_docs:
|
|
240
|
+
yield docname
|
|
241
|
+
continue
|
|
242
|
+
targetname = os.path.join(self.outdir, docname + self.out_suffix)
|
|
243
|
+
try:
|
|
244
|
+
targetmtime = os.path.getmtime(targetname)
|
|
245
|
+
except Exception:
|
|
246
|
+
targetmtime = 0
|
|
247
|
+
try:
|
|
248
|
+
srcmtime = os.path.getmtime(self.env.doc2path(docname))
|
|
249
|
+
if srcmtime > targetmtime:
|
|
250
|
+
yield docname
|
|
251
|
+
except OSError:
|
|
252
|
+
# source doesn't exist anymore
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
def get_target_uri(self, docname: str, typ: str | None = None) -> str: # noqa: ARG002
|
|
256
|
+
"""Return the `docname` as the target_uri."""
|
|
257
|
+
return docname
|
|
258
|
+
|
|
259
|
+
def prepare_writing(self, docnames: set[str]) -> None: # noqa: ARG002
|
|
260
|
+
"""Prepare for writing."""
|
|
261
|
+
self.writer = self._writer_class(self)
|
|
262
|
+
|
|
263
|
+
def write_doc(self, docname: str, doctree: nodes.Node) -> None:
|
|
264
|
+
"""Write a single document."""
|
|
265
|
+
# work around multiple string % tuple issues in docutils;
|
|
266
|
+
# replace tuples in attribute values with lists
|
|
267
|
+
doctree = doctree.deepcopy()
|
|
268
|
+
for domain in self.env.domains.values():
|
|
269
|
+
xmlns = "xmlns:" + domain.name
|
|
270
|
+
doctree[xmlns] = "https://www.sphinx-doc.org/" # type: ignore
|
|
271
|
+
for node in doctree.findall(nodes.Element):
|
|
272
|
+
for att, value in node.attributes.items():
|
|
273
|
+
if isinstance(value, tuple):
|
|
274
|
+
node.attributes[att] = list(value)
|
|
275
|
+
value = node.attributes[att] # noqa: PLW2901
|
|
276
|
+
if isinstance(value, list):
|
|
277
|
+
for i, val in enumerate(value):
|
|
278
|
+
if isinstance(val, tuple):
|
|
279
|
+
value[i] = list(val)
|
|
280
|
+
destination = StringOutput(encoding="utf-8")
|
|
281
|
+
self.writer.write(doctree, destination)
|
|
282
|
+
outfilename = os.path.join(self.outdir, os_path(docname) + self.out_suffix)
|
|
283
|
+
ensuredir(os.path.dirname(outfilename))
|
|
284
|
+
try:
|
|
285
|
+
with open(outfilename, "w", encoding="utf-8") as f:
|
|
286
|
+
f.write(self.writer.output)
|
|
287
|
+
except OSError as err:
|
|
288
|
+
logger.warning(__("error writing file %s: %s"), outfilename, err)
|
|
289
|
+
|
|
290
|
+
def finish(self) -> None:
|
|
291
|
+
"""Unused."""
|
|
292
|
+
pass
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def setup(app: Sphinx) -> None:
|
|
296
|
+
"""Set up the TEI Sphinx extension."""
|
|
297
|
+
app.add_builder(TEIBuilder)
|
|
@@ -8,7 +8,6 @@ from sphinx.application import Sphinx
|
|
|
8
8
|
from sphinx.parsers import Parser as SphinxParser
|
|
9
9
|
from sphinx.writers.html import HTMLWriter
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
namespaces = {"tei": "http://www.tei-c.org/ns/1.0"}
|
|
13
12
|
|
|
14
13
|
|
|
@@ -52,7 +51,7 @@ class TEIParser(SphinxParser):
|
|
|
52
51
|
:param document: The root docutils node to add AST elements to
|
|
53
52
|
:type document: :class:`~docutils.nodes.document`
|
|
54
53
|
"""
|
|
55
|
-
root = etree.fromstring(inputstring.encode("UTF-8"))
|
|
54
|
+
root = etree.fromstring(inputstring.encode("UTF-8")) # noqa: S320
|
|
56
55
|
title = root.xpath(
|
|
57
56
|
"string(/tei:TEI/tei:teiHeader/tei:fileDesc/tei:titleStmt/tei:title)",
|
|
58
57
|
namespaces=namespaces,
|
|
@@ -86,9 +85,7 @@ class TEIParser(SphinxParser):
|
|
|
86
85
|
self._parse_list_field(fields, field, root)
|
|
87
86
|
document.append(doc_section)
|
|
88
87
|
|
|
89
|
-
def _walk_tree(
|
|
90
|
-
self: "TEIParser", node: etree.Element, parent: nodes.Element, rules: list
|
|
91
|
-
) -> None:
|
|
88
|
+
def _walk_tree(self: "TEIParser", node: etree.Element, parent: nodes.Element, rules: list) -> None:
|
|
92
89
|
"""Walk the XML tree and create the appropriate AST nodes.
|
|
93
90
|
|
|
94
91
|
Uses the mapping rules defined in :mod:`~uEdition.extensions.config` to determine what to
|
|
@@ -96,9 +93,7 @@ class TEIParser(SphinxParser):
|
|
|
96
93
|
"""
|
|
97
94
|
is_leaf = len(node) == 0
|
|
98
95
|
text_only_in_leaf_nodes = (
|
|
99
|
-
self.config.uEdition["tei"]["text_only_in_leaf_nodes"]
|
|
100
|
-
if "tei" in self.config.uEdition
|
|
101
|
-
else False
|
|
96
|
+
self.config.uEdition["tei"]["text_only_in_leaf_nodes"] if "tei" in self.config.uEdition else False
|
|
102
97
|
)
|
|
103
98
|
attributes = {}
|
|
104
99
|
# Get the first matching rule for the current node
|
|
@@ -107,7 +102,7 @@ class TEIParser(SphinxParser):
|
|
|
107
102
|
for key, value in node.attrib.items():
|
|
108
103
|
# Always strip the namespace from the `id` attribute
|
|
109
104
|
if key == "{http://www.w3.org/XML/1998/namespace}id":
|
|
110
|
-
key = "id"
|
|
105
|
+
key = "id" # noqa: PLW2901
|
|
111
106
|
if rule and "attributes" in rule:
|
|
112
107
|
processed = False
|
|
113
108
|
for attr_rule in rule["attributes"]:
|
|
@@ -120,7 +115,7 @@ class TEIParser(SphinxParser):
|
|
|
120
115
|
processed = True
|
|
121
116
|
elif attr_rule["action"] == "set":
|
|
122
117
|
if key == attr_rule["attr"]:
|
|
123
|
-
value = attr_rule["value"]
|
|
118
|
+
value = attr_rule["value"] # noqa: PLW2901
|
|
124
119
|
# if the attribute did not match any attribute transform
|
|
125
120
|
if not processed:
|
|
126
121
|
# The id attribute is always output as is, all other attributes are prefixed with `data-`
|
|
@@ -128,7 +123,7 @@ class TEIParser(SphinxParser):
|
|
|
128
123
|
attributes["id"] = value
|
|
129
124
|
else:
|
|
130
125
|
attributes[f"data-{key}"] = value
|
|
131
|
-
else:
|
|
126
|
+
else: # noqa: PLR5501
|
|
132
127
|
# The id attribute is always output as is, all other attributes are prefixed with `data-`
|
|
133
128
|
if key == "id":
|
|
134
129
|
attributes["id"] = value
|
|
@@ -143,15 +138,11 @@ class TEIParser(SphinxParser):
|
|
|
143
138
|
parent.append(new_element)
|
|
144
139
|
if rule is not None and "text" in rule and rule["text"]:
|
|
145
140
|
# If there is a `text` key in the rule, use that to set the text
|
|
146
|
-
if
|
|
147
|
-
rule["text"]["action"] == "from-attribute"
|
|
148
|
-
and rule["text"]["attr"] in node.attrib
|
|
149
|
-
):
|
|
141
|
+
if rule["text"]["action"] == "from-attribute" and rule["text"]["attr"] in node.attrib:
|
|
150
142
|
new_element.append(nodes.Text(node.attrib[rule["text"]["attr"]]))
|
|
151
|
-
|
|
152
|
-
if
|
|
153
|
-
|
|
154
|
-
new_element.append(nodes.Text(node.text))
|
|
143
|
+
elif node.text and (is_leaf or not text_only_in_leaf_nodes):
|
|
144
|
+
# Only create text content if there is text and we either are in a leaf node or are adding all text
|
|
145
|
+
new_element.append(nodes.Text(node.text))
|
|
155
146
|
# Process any children
|
|
156
147
|
for child in node:
|
|
157
148
|
self._walk_tree(child, new_element, rules)
|
|
@@ -159,9 +150,7 @@ class TEIParser(SphinxParser):
|
|
|
159
150
|
if node.tail and not text_only_in_leaf_nodes:
|
|
160
151
|
parent.append(nodes.Text(node.tail))
|
|
161
152
|
|
|
162
|
-
def _wrap_sections(
|
|
163
|
-
self: "TEIParser", section: nodes.Element, tmp: nodes.Element
|
|
164
|
-
) -> None:
|
|
153
|
+
def _wrap_sections(self: "TEIParser", section: nodes.Element, tmp: nodes.Element) -> None:
|
|
165
154
|
"""Ensure that sections are correctly wrapped."""
|
|
166
155
|
section_stack = [(0, section)]
|
|
167
156
|
for node in tmp.children:
|
|
@@ -203,9 +192,7 @@ class TEIParser(SphinxParser):
|
|
|
203
192
|
if not in_heading:
|
|
204
193
|
section_stack[-1][1].append(node)
|
|
205
194
|
|
|
206
|
-
def _rule_for_node(
|
|
207
|
-
self: "TEIParser", node: etree.Element, rules: list[dict]
|
|
208
|
-
) -> dict:
|
|
195
|
+
def _rule_for_node(self: "TEIParser", node: etree.Element, rules: list[dict]) -> dict:
|
|
209
196
|
"""Determine the first matching mapping rule for the node from the configured rules."""
|
|
210
197
|
tei_tag = node.tag
|
|
211
198
|
for rule in rules:
|
|
@@ -213,10 +200,7 @@ class TEIParser(SphinxParser):
|
|
|
213
200
|
if "attributes" in rule["selector"]:
|
|
214
201
|
attr_match = True
|
|
215
202
|
for attr_rule in rule["selector"]["attributes"]:
|
|
216
|
-
if
|
|
217
|
-
attr_rule["attr"] not in node.attrib
|
|
218
|
-
or node.attrib[attr_rule["attr"]] != attr_rule["value"]
|
|
219
|
-
):
|
|
203
|
+
if attr_rule["attr"] not in node.attrib or node.attrib[attr_rule["attr"]] != attr_rule["value"]:
|
|
220
204
|
attr_match = False
|
|
221
205
|
break
|
|
222
206
|
if not attr_match:
|
|
@@ -224,9 +208,8 @@ class TEIParser(SphinxParser):
|
|
|
224
208
|
return rule
|
|
225
209
|
return None
|
|
226
210
|
|
|
227
|
-
def _parse_single_field(
|
|
228
|
-
|
|
229
|
-
) -> None:
|
|
211
|
+
def _parse_single_field(self: "TEIParser", parent: etree.Element, field: dict, root: etree.Element) -> None:
|
|
212
|
+
"""Parse a single metadata field."""
|
|
230
213
|
content = root.xpath(field["content"], namespaces=namespaces)
|
|
231
214
|
if len(content) > 0:
|
|
232
215
|
content = content[0]
|
|
@@ -239,9 +222,8 @@ class TEIParser(SphinxParser):
|
|
|
239
222
|
li.append(dd)
|
|
240
223
|
parent.append(li)
|
|
241
224
|
|
|
242
|
-
def _parse_list_field(
|
|
243
|
-
|
|
244
|
-
) -> None:
|
|
225
|
+
def _parse_list_field(self: "TEIParser", parent: etree.Element, field: dict, root: etree.Element) -> None:
|
|
226
|
+
"""Parse a list of metadata fields."""
|
|
245
227
|
content = root.xpath(field["content"], namespaces=namespaces)
|
|
246
228
|
if len(content) > 0:
|
|
247
229
|
li = nodes.definition_list_item()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
function setup() {
|
|
3
|
+
try {
|
|
4
|
+
const dropdownMenu = document.querySelector(".article-header-buttons .dropdown-download-buttons .dropdown-menu");
|
|
5
|
+
if (dropdownMenu) {
|
|
6
|
+
const item = document.createElement("li");
|
|
7
|
+
const link = document.createElement("a");
|
|
8
|
+
link.classList.add("btn");
|
|
9
|
+
link.classList.add("btn-sm");
|
|
10
|
+
link.classList.add("dropdown-item");
|
|
11
|
+
if (location.href.endsWith("/")) {
|
|
12
|
+
link.setAttribute("href", location.href + "index.tei");
|
|
13
|
+
} else {
|
|
14
|
+
link.setAttribute("href", location.href.substring(0, location.href.lastIndexOf(".")) + ".tei");
|
|
15
|
+
}
|
|
16
|
+
const icon = document.createElement("span");
|
|
17
|
+
icon.classList.add("btn__icon-container");
|
|
18
|
+
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" class="svg-inline--fa fa-code" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="code" data-fa-i2svg=""><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM153 289l-31 31 31 31c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0L71 337c-9.4-9.4-9.4-24.6 0-33.9l48-48c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9zM265 255l48 48c9.4 9.4 9.4 24.6 0 33.9l-48 48c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l31-31-31-31c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0z"/></svg>';
|
|
19
|
+
const text = document.createElement("text");
|
|
20
|
+
text.classList.add("btn__text-container");
|
|
21
|
+
text.innerHTML = ".tei";
|
|
22
|
+
|
|
23
|
+
link.appendChild(icon);
|
|
24
|
+
link.appendChild(text);
|
|
25
|
+
item.appendChild(link);
|
|
26
|
+
dropdownMenu.appendChild(item);
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error(e);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
window.addEventListener('DOMContentLoaded', setup);
|
|
34
|
+
})();
|