1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
"""
sphinx.domains.math
~~~~~~~~~~~~~~~~~~~
The math domain.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Tuple
from docutils import nodes
from docutils.nodes import Element, Node, make_id, system_message
from sphinx.addnodes import pending_xref
from sphinx.domains import Domain
from sphinx.environment import BuildEnvironment
from sphinx.locale import __
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.nodes import make_refnode
if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.builders import Builder
logger = logging.getLogger(__name__)
class MathReferenceRole(XRefRole):
def result_nodes(self, document: nodes.document, env: BuildEnvironment, node: Element,
is_ref: bool) -> Tuple[List[Node], List[system_message]]:
node['refdomain'] = 'math'
return [node], []
class MathDomain(Domain):
"""Mathematics domain."""
name = 'math'
label = 'mathematics'
initial_data = {
'objects': {}, # labelid -> (docname, eqno)
'has_equations': {}, # docname -> bool
} # type: Dict
dangling_warnings = {
'eq': 'equation not found: %(target)s',
}
enumerable_nodes = { # node_class -> (figtype, title_getter)
nodes.math_block: ('displaymath', None),
}
roles = {
'numref': MathReferenceRole(),
}
@property
def equations(self) -> Dict[str, Tuple[str, int]]:
return self.data.setdefault('objects', {}) # labelid -> (docname, eqno)
def note_equation(self, docname: str, labelid: str, location: Any = None) -> None:
if labelid in self.equations:
other = self.equations[labelid][0]
logger.warning(__('duplicate label of equation %s, other instance in %s') %
(labelid, other), location=location)
self.equations[labelid] = (docname, self.env.new_serialno('eqno') + 1)
def get_equation_number_for(self, labelid: str) -> int:
if labelid in self.equations:
return self.equations[labelid][1]
else:
return None
def process_doc(self, env: BuildEnvironment, docname: str,
document: nodes.document) -> None:
def math_node(node: Node) -> bool:
return isinstance(node, (nodes.math, nodes.math_block))
self.data['has_equations'][docname] = any(document.traverse(math_node))
def clear_doc(self, docname: str) -> None:
for equation_id, (doc, eqno) in list(self.equations.items()):
if doc == docname:
del self.equations[equation_id]
self.data['has_equations'].pop(docname, None)
def merge_domaindata(self, docnames: Iterable[str], otherdata: Dict) -> None:
for labelid, (doc, eqno) in otherdata['objects'].items():
if doc in docnames:
self.equations[labelid] = (doc, eqno)
for docname in docnames:
self.data['has_equations'][docname] = otherdata['has_equations'][docname]
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element
) -> Element:
assert typ in ('eq', 'numref')
docname, number = self.equations.get(target, (None, None))
if docname:
# TODO: perhaps use rather a sphinx-core provided prefix here?
node_id = make_id('equation-%s' % target)
if env.config.math_numfig and env.config.numfig:
if docname in env.toc_fignumbers:
numbers = env.toc_fignumbers[docname]['displaymath'].get(node_id, ())
eqno = '.'.join(map(str, numbers))
else:
eqno = ''
else:
eqno = str(number)
try:
eqref_format = env.config.math_eqref_format or "({number})"
title = nodes.Text(eqref_format.format(number=eqno))
except KeyError as exc:
logger.warning(__('Invalid math_eqref_format: %r'), exc,
location=node)
title = nodes.Text("(%d)" % number)
title = nodes.Text("(%d)" % number)
return make_refnode(builder, fromdocname, docname, node_id, title)
else:
return None
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: "Builder",
target: str, node: pending_xref, contnode: Element
) -> List[Tuple[str, Element]]:
refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode)
if refnode is None:
return []
else:
return [('eq', refnode)]
def get_objects(self) -> List:
return []
def has_equations(self, docname: str = None) -> bool:
if docname:
return self.data['has_equations'].get(docname, False)
else:
return any(self.data['has_equations'].values())
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_domain(MathDomain)
app.add_role('eq', MathReferenceRole(warn_dangling=True))
return {
'version': 'builtin',
'env_version': 2,
'parallel_read_safe': True,
'parallel_write_safe': True,
}
|