diff --git a/CHANGES b/CHANGES
index 8b52dd47c..401cb164e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -72,6 +72,11 @@ Deprecated
* ``BuildEnvironment.dump()`` is deprecated
* ``BuildEnvironment.dumps()`` is deprecated
* ``BuildEnvironment.topickle()`` is deprecated
+* ``sphinx.ext.mathbase.math`` node is deprecated
+* ``sphinx.ext.mathbase.displaymath`` node is deprecated
+* ``sphinx.ext.mathbase.eqref`` node is deprecated
+* ``sphinx.ext.mathbase.is_in_section_title()`` is deprecated
+* ``sphinx.ext.mathbase.MathDomain`` is deprecated
For more details, see `deprecation APIs list
`_
diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst
index b089bdcea..5b9838244 100644
--- a/doc/extdev/index.rst
+++ b/doc/extdev/index.rst
@@ -126,6 +126,31 @@ The following is a list of deprecated interface.
- 4.0
- :meth:`~sphinx.application.Sphinx.add_css_file()`
+ * - ``sphinx.ext.mathbase.MathDomain``
+ - 1.8
+ - 3.0
+ - ``sphinx.domains.math.MathDomain``
+
+ * - ``sphinx.ext.mathbase.is_in_section_title()``
+ - 1.8
+ - 3.0
+ - N/A
+
+ * - ``sphinx.ext.mathbase.math`` (node)
+ - 1.8
+ - 3.0
+ - ``docutils.nodes.math``
+
+ * - ``sphinx.ext.mathbase.displaymath`` (node)
+ - 1.8
+ - 3.0
+ - ``docutils.nodes.math_block``
+
+ * - ``sphinx.ext.mathbase.eqref`` (node)
+ - 1.8
+ - 3.0
+ - ``sphinx.addnodes.math_reference``
+
* - ``viewcode_import`` (config value)
- 1.8
- 3.0
diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py
index e6999bd16..40390f346 100644
--- a/sphinx/addnodes.py
+++ b/sphinx/addnodes.py
@@ -9,8 +9,12 @@
:license: BSD, see LICENSE for details.
"""
+import warnings
+
from docutils import nodes
+from sphinx.deprecation import RemovedInSphinx30Warning
+
if False:
# For type annotation
from typing import List, Sequence # NOQA
@@ -183,6 +187,55 @@ class production(nodes.Part, nodes.Inline, nodes.FixedTextElement):
"""Node for a single grammar production rule."""
+# math nodes
+
+
+class math(nodes.math):
+ """Node for inline equations.
+
+ .. deprecated:: 1.8
+ Use ``docutils.nodes.math`` instead.
+ """
+
+ def __getitem__(self, key):
+ """Special accessor for supporting ``node['latex']``."""
+ if key == 'latex' and 'latex' not in self.attributes:
+ warnings.warn("math node for Sphinx was replaced by docutils'. "
+ "Therefore please use ``node.astext()`` to get an equation instead.",
+ RemovedInSphinx30Warning)
+ return self.astext()
+ else:
+ return nodes.math.__getitem__(self, key)
+
+
+class math_block(nodes.math_block):
+ """Node for block level equations.
+
+ .. deprecated:: 1.8
+ """
+
+ def __getitem__(self, key):
+ if key == 'latex' and 'latex' not in self.attributes:
+ warnings.warn("displaymath node for Sphinx was replaced by docutils'. "
+ "Therefore please use ``node.astext()`` to get an equation instead.",
+ RemovedInSphinx30Warning)
+ return self.astext()
+ else:
+ return nodes.math_block.__getitem__(self, key)
+
+
+class displaymath(math_block):
+ """Node for block level equations.
+
+ .. deprecated:: 1.8
+ """
+
+
+class math_reference(nodes.Inline, nodes.Referential, nodes.TextElement):
+ """Node for a reference for equation."""
+ pass
+
+
# other directive-level nodes
class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
diff --git a/sphinx/application.py b/sphinx/application.py
index 9d3d5de9f..782415e89 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -83,6 +83,7 @@ builtin_extensions = (
'sphinx.domains.c',
'sphinx.domains.cpp',
'sphinx.domains.javascript',
+ 'sphinx.domains.math',
'sphinx.domains.python',
'sphinx.domains.rst',
'sphinx.domains.std',
@@ -97,6 +98,7 @@ builtin_extensions = (
'sphinx.roles',
'sphinx.transforms.post_transforms',
'sphinx.transforms.post_transforms.images',
+ 'sphinx.transforms.post_transforms.compat',
'sphinx.util.compat',
# collectors should be loaded by specific order
'sphinx.environment.collectors.dependencies',
diff --git a/sphinx/config.py b/sphinx/config.py
index 1184f1891..4ad09c08b 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -139,6 +139,9 @@ class Config(object):
numfig_secnum_depth = (1, 'env', []),
numfig_format = ({}, 'env', []), # will be initialized in init_numfig_format()
+ math_number_all = (False, 'env', []),
+ math_eqref_format = (None, 'env', string_classes),
+ math_numfig = (True, 'env', []),
tls_verify = (True, 'env', []),
tls_cacerts = (None, 'env', []),
smartquotes = (True, 'env', []),
diff --git a/sphinx/directives/patches.py b/sphinx/directives/patches.py
index 00be5584d..f84f082bc 100644
--- a/sphinx/directives/patches.py
+++ b/sphinx/directives/patches.py
@@ -8,6 +8,7 @@
"""
from docutils import nodes
+from docutils.nodes import make_id
from docutils.parsers.rst import directives
from docutils.parsers.rst.directives import images, html, tables
@@ -105,6 +106,63 @@ class ListTable(tables.ListTable):
return title, message
+class MathDirective(SphinxDirective):
+
+ has_content = True
+ required_arguments = 0
+ optional_arguments = 1
+ final_argument_whitespace = True
+ option_spec = {
+ 'label': directives.unchanged,
+ 'name': directives.unchanged,
+ 'nowrap': directives.flag,
+ }
+
+ def run(self):
+ # type: () -> List[nodes.Node]
+ latex = '\n'.join(self.content)
+ if self.arguments and self.arguments[0]:
+ latex = self.arguments[0] + '\n\n' + latex
+ node = nodes.math_block(latex, latex,
+ docname=self.state.document.settings.env.docname,
+ number=self.options.get('name'),
+ label=self.options.get('label'),
+ nowrap='nowrap' in self.options)
+ ret = [node]
+ set_source_info(self, node)
+ if hasattr(self, 'src'):
+ node.source = self.src
+ self.add_target(ret)
+ return ret
+
+ def add_target(self, ret):
+ # type: (List[nodes.Node]) -> None
+ node = ret[0]
+
+ # assign label automatically if math_number_all enabled
+ if node['label'] == '' or (self.config.math_number_all and not node['label']):
+ seq = self.env.new_serialno('sphinx.ext.math#equations')
+ node['label'] = "%s:%d" % (self.env.docname, seq)
+
+ # no targets and numbers are needed
+ if not node['label']:
+ return
+
+ # register label to domain
+ domain = self.env.get_domain('math')
+ try:
+ eqno = domain.add_equation(self.env, self.env.docname, node['label']) # type: ignore # NOQA
+ node['number'] = eqno
+
+ # add target node
+ node_id = make_id('equation-%s' % node['label'])
+ target = nodes.target('', '', ids=[node_id])
+ self.state.document.note_explicit_target(target)
+ ret.insert(0, target)
+ except UserWarning as exc:
+ self.state_machine.reporter.warning(exc.args[0], line=self.lineno)
+
+
def setup(app):
# type: (Sphinx) -> Dict
directives.register_directive('figure', Figure)
@@ -112,6 +170,7 @@ def setup(app):
directives.register_directive('table', RSTTable)
directives.register_directive('csv-table', CSVTable)
directives.register_directive('list-table', ListTable)
+ directives.register_directive('math', MathDirective)
return {
'version': 'builtin',
diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py
new file mode 100644
index 000000000..a5de8a892
--- /dev/null
+++ b/sphinx/domains/math.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.domains.math
+ ~~~~~~~~~~~~~~~~~~~
+
+ The math domain.
+
+ :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from docutils import nodes
+from docutils.nodes import make_id
+
+from sphinx.addnodes import math_block as displaymath, math_reference
+from sphinx.domains import Domain
+from sphinx.locale import __
+from sphinx.roles import XRefRole
+from sphinx.util import logging
+from sphinx.util.nodes import make_refnode
+
+if False:
+ # For type annotation
+ from typing import Any, Callable, Dict, Iterable, List, Tuple # NOQA
+ from sphinx.application import Sphinx # NOQA
+ from sphinx.builders import Builder # NOQA
+ from sphinx.environment import BuildEnvironment # NOQA
+
+logger = logging.getLogger(__name__)
+
+
+class MathReferenceRole(XRefRole):
+ def result_nodes(self, document, env, node, is_ref):
+ # type: (nodes.Node, BuildEnvironment, nodes.Node, bool) -> Tuple[List[nodes.Node], List[nodes.Node]] # NOQA
+ node['refdomain'] = 'math'
+ return [node], []
+
+
+class MathDomain(Domain):
+ """Mathematics domain."""
+ name = 'math'
+ label = 'mathematics'
+
+ initial_data = {
+ 'objects': {}, # labelid -> (docname, eqno)
+ } # type: Dict[unicode, Dict[unicode, Tuple[unicode, int]]]
+ dangling_warnings = {
+ 'eq': 'equation not found: %(target)s',
+ }
+ enumerable_nodes = { # node_class -> (figtype, title_getter)
+ displaymath: ('displaymath', None),
+ nodes.math_block: ('displaymath', None),
+ } # type: Dict[nodes.Node, Tuple[unicode, Callable]]
+
+ def clear_doc(self, docname):
+ # type: (unicode) -> None
+ for equation_id, (doc, eqno) in list(self.data['objects'].items()):
+ if doc == docname:
+ del self.data['objects'][equation_id]
+
+ def merge_domaindata(self, docnames, otherdata):
+ # type: (Iterable[unicode], Dict) -> None
+ for labelid, (doc, eqno) in otherdata['objects'].items():
+ if doc in docnames:
+ self.data['objects'][labelid] = (doc, eqno)
+
+ def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
+ # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA
+ assert typ == 'eq'
+ docname, number = self.data['objects'].get(target, (None, None))
+ if docname:
+ if builder.name == 'latex':
+ newnode = math_reference('', **node.attributes)
+ newnode['docname'] = docname
+ newnode['target'] = target
+ return newnode
+ else:
+ # 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:
+ number = env.toc_fignumbers[docname]['displaymath'].get(node_id, ())
+ number = '.'.join(map(str, number))
+ else:
+ number = ''
+ try:
+ eqref_format = env.config.math_eqref_format or "({number})"
+ title = nodes.Text(eqref_format.format(number=number))
+ 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, fromdocname, builder, target, node, contnode):
+ # type: (BuildEnvironment, unicode, Builder, unicode, nodes.Node, nodes.Node) -> List[nodes.Node] # NOQA
+ refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode)
+ if refnode is None:
+ return []
+ else:
+ return [refnode]
+
+ def get_objects(self):
+ # type: () -> List
+ return []
+
+ def add_equation(self, env, docname, labelid):
+ # type: (BuildEnvironment, unicode, unicode) -> int
+ equations = self.data['objects']
+ if labelid in equations:
+ path = env.doc2path(equations[labelid][0])
+ msg = __('duplicate label of equation %s, other instance in %s') % (labelid, path)
+ raise UserWarning(msg)
+ else:
+ eqno = self.get_next_equation_number(docname)
+ equations[labelid] = (docname, eqno)
+ return eqno
+
+ def get_next_equation_number(self, docname):
+ # type: (unicode) -> int
+ targets = [eq for eq in self.data['objects'].values() if eq[0] == docname]
+ return len(targets) + 1
+
+
+def setup(app):
+ # type: (Sphinx) -> Dict[unicode, Any]
+ app.add_domain(MathDomain)
+ app.add_role('eq', MathReferenceRole(warn_dangling=True))
+
+ return {
+ 'version': 'builtin',
+ 'env_version': 1,
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }
diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py
index a1faf2c1b..0cdc9aba4 100644
--- a/sphinx/ext/imgmath.py
+++ b/sphinx/ext/imgmath.py
@@ -37,7 +37,7 @@ if False:
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.config import Config # NOQA
- from sphinx.ext.mathbase import math as math_node, displaymath # NOQA
+ from sphinx.ext.mathbase import displaymath # NOQA
logger = logging.getLogger(__name__)
@@ -285,27 +285,27 @@ def cleanup_tempdir(app, exc):
def get_tooltip(self, node):
- # type: (nodes.NodeVisitor, math_node) -> unicode
+ # type: (nodes.NodeVisitor, nodes.math) -> unicode
if self.builder.config.imgmath_add_tooltips:
- return ' alt="%s"' % self.encode(node['latex']).strip()
+ return ' alt="%s"' % self.encode(node.astext()).strip()
return ''
def html_visit_math(self, node):
- # type: (nodes.NodeVisitor, math_node) -> None
+ # type: (nodes.NodeVisitor, nodes.math) -> None
try:
- fname, depth = render_math(self, '$' + node['latex'] + '$')
+ fname, depth = render_math(self, '$' + node.astext() + '$')
except MathExtError as exc:
msg = text_type(exc)
sm = nodes.system_message(msg, type='WARNING', level=2,
- backrefs=[], source=node['latex'])
+ backrefs=[], source=node.astext())
sm.walkabout(self)
- logger.warning(__('display latex %r: %s'), node['latex'], msg)
+ logger.warning(__('display latex %r: %s'), node.astext(), msg)
raise nodes.SkipNode
if fname is None:
# something failed -- use text-only as a bad substitute
self.body.append('%s' %
- self.encode(node['latex']).strip())
+ self.encode(node.astext()).strip())
else:
c = ('
None
if node['nowrap']:
- latex = node['latex']
+ latex = node.astext()
else:
- latex = wrap_displaymath(node['latex'], None,
+ latex = wrap_displaymath(node.astext(), None,
self.builder.config.math_number_all)
try:
fname, depth = render_math(self, latex)
except MathExtError as exc:
msg = text_type(exc)
sm = nodes.system_message(msg, type='WARNING', level=2,
- backrefs=[], source=node['latex'])
+ backrefs=[], source=node.astext())
sm.walkabout(self)
- logger.warning(__('inline latex %r: %s'), node['latex'], msg)
+ logger.warning(__('inline latex %r: %s'), node.astext(), msg)
raise nodes.SkipNode
self.body.append(self.starttag(node, 'div', CLASS='math'))
self.body.append('')
@@ -340,7 +340,7 @@ def html_visit_displaymath(self, node):
if fname is None:
# something failed -- use text-only as a bad substitute
self.body.append('%s
\n' %
- self.encode(node['latex']).strip())
+ self.encode(node.astext()).strip())
else:
self.body.append(('
\n')
diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py
index 97ea400a3..7381da42b 100644
--- a/sphinx/ext/jsmath.py
+++ b/sphinx/ext/jsmath.py
@@ -27,7 +27,7 @@ if False:
def html_visit_math(self, node):
# type: (nodes.NodeVisitor, nodes.Node) -> None
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
- self.body.append(self.encode(node['latex']) + '')
+ self.body.append(self.encode(node.astext()) + '')
raise nodes.SkipNode
@@ -35,10 +35,10 @@ def html_visit_displaymath(self, node):
# type: (nodes.NodeVisitor, nodes.Node) -> None
if node['nowrap']:
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
- self.body.append(self.encode(node['latex']))
+ self.body.append(self.encode(node.astext()))
self.body.append('')
raise nodes.SkipNode
- for i, part in enumerate(node['latex'].split('\n\n')):
+ for i, part in enumerate(node.astext().split('\n\n')):
part = self.encode(part)
if i == 0:
# necessary to e.g. set the id property correctly
diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py
index e6a6929e6..448f329b2 100644
--- a/sphinx/ext/mathbase.py
+++ b/sphinx/ext/mathbase.py
@@ -9,135 +9,21 @@
:license: BSD, see LICENSE for details.
"""
-from docutils import nodes, utils
-from docutils.nodes import make_id
-from docutils.parsers.rst import directives
+import warnings
-from sphinx.config import string_classes
-from sphinx.domains import Domain
-from sphinx.locale import __
-from sphinx.roles import XRefRole
-from sphinx.util import logging
-from sphinx.util.docutils import SphinxDirective
-from sphinx.util.nodes import make_refnode, set_source_info
+from docutils import nodes
+
+from sphinx.addnodes import math, math_block as displaymath
+from sphinx.addnodes import math_reference as eqref # NOQA # to keep compatibility
+from sphinx.deprecation import RemovedInSphinx30Warning
+from sphinx.domains.math import MathDomain # NOQA # to keep compatibility
+from sphinx.domains.math import MathReferenceRole as EqXRefRole # NOQA # to keep compatibility
if False:
# For type annotation
- from typing import Any, Callable, Dict, Iterable, List, Tuple # NOQA
- from docutils.parsers.rst.states import Inliner # NOQA
+ from typing import Any, Callable, List, Tuple # NOQA
from docutils.writers.html4css1 import Writer # NOQA
from sphinx.application import Sphinx # NOQA
- from sphinx.builders import Builder # NOQA
- from sphinx.environment import BuildEnvironment # NOQA
-
-logger = logging.getLogger(__name__)
-
-
-class math(nodes.Inline, nodes.TextElement):
- pass
-
-
-class displaymath(nodes.Part, nodes.Element):
- pass
-
-
-class eqref(nodes.Inline, nodes.TextElement):
- pass
-
-
-class EqXRefRole(XRefRole):
- def result_nodes(self, document, env, node, is_ref):
- # type: (nodes.Node, BuildEnvironment, nodes.Node, bool) -> Tuple[List[nodes.Node], List[nodes.Node]] # NOQA
- node['refdomain'] = 'math'
- return [node], []
-
-
-class MathDomain(Domain):
- """Mathematics domain."""
- name = 'math'
- label = 'mathematics'
-
- initial_data = {
- 'objects': {}, # labelid -> (docname, eqno)
- } # type: Dict[unicode, Dict[unicode, Tuple[unicode, int]]]
- dangling_warnings = {
- 'eq': 'equation not found: %(target)s',
- }
- enumerable_nodes = { # node_class -> (figtype, title_getter)
- displaymath: ('displaymath', None),
- } # type: Dict[nodes.Node, Tuple[unicode, Callable]]
-
- def clear_doc(self, docname):
- # type: (unicode) -> None
- for equation_id, (doc, eqno) in list(self.data['objects'].items()):
- if doc == docname:
- del self.data['objects'][equation_id]
-
- def merge_domaindata(self, docnames, otherdata):
- # type: (Iterable[unicode], Dict) -> None
- for labelid, (doc, eqno) in otherdata['objects'].items():
- if doc in docnames:
- self.data['objects'][labelid] = (doc, eqno)
-
- def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
- # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA
- assert typ == 'eq'
- docname, number = self.data['objects'].get(target, (None, None))
- if docname:
- if builder.name == 'latex':
- newnode = eqref('', **node.attributes)
- newnode['docname'] = docname
- newnode['target'] = target
- return newnode
- else:
- # 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:
- number = env.toc_fignumbers[docname]['displaymath'].get(node_id, ())
- number = '.'.join(map(str, number))
- else:
- number = ''
- try:
- eqref_format = env.config.math_eqref_format or "({number})"
- title = nodes.Text(eqref_format.format(number=number))
- 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, fromdocname, builder, target, node, contnode):
- # type: (BuildEnvironment, unicode, Builder, unicode, nodes.Node, nodes.Node) -> List[nodes.Node] # NOQA
- refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode)
- if refnode is None:
- return []
- else:
- return [refnode]
-
- def get_objects(self):
- # type: () -> List
- return []
-
- def add_equation(self, env, docname, labelid):
- # type: (BuildEnvironment, unicode, unicode) -> int
- equations = self.data['objects']
- if labelid in equations:
- path = env.doc2path(equations[labelid][0])
- msg = __('duplicate label of equation %s, other instance in %s') % (labelid, path)
- raise UserWarning(msg)
- else:
- eqno = self.get_next_equation_number(docname)
- equations[labelid] = (docname, eqno)
- return eqno
-
- def get_next_equation_number(self, docname):
- # type: (unicode) -> int
- targets = [eq for eq in self.data['objects'].values() if eq[0] == docname]
- return len(targets) + 1
def get_node_equation_number(writer, node):
@@ -195,17 +81,14 @@ def wrap_displaymath(math, label, numbering):
return '%s\n%s%s' % (begin, ''.join(equations), end)
-def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
- # type: (unicode, unicode, unicode, int, Inliner, Dict, List[unicode]) -> Tuple[List[nodes.Node], List[nodes.Node]] # NOQA
- latex = utils.unescape(text, restore_backslashes=True)
- return [math(latex=latex)], []
-
-
def is_in_section_title(node):
# type: (nodes.Node) -> bool
"""Determine whether the node is in a section title"""
from sphinx.util.nodes import traverse_parent
+ warnings.warn('is_in_section_title() is deprecated.',
+ RemovedInSphinx30Warning)
+
for ancestor in traverse_parent(node):
if isinstance(ancestor, nodes.title) and \
isinstance(ancestor.parent, nodes.section):
@@ -213,181 +96,9 @@ def is_in_section_title(node):
return False
-class MathDirective(SphinxDirective):
-
- has_content = True
- required_arguments = 0
- optional_arguments = 1
- final_argument_whitespace = True
- option_spec = {
- 'label': directives.unchanged,
- 'name': directives.unchanged,
- 'nowrap': directives.flag,
- }
-
- def run(self):
- # type: () -> List[nodes.Node]
- latex = '\n'.join(self.content)
- if self.arguments and self.arguments[0]:
- latex = self.arguments[0] + '\n\n' + latex
- node = displaymath()
- node['latex'] = latex
- node['number'] = None
- node['label'] = None
- if 'name' in self.options:
- node['label'] = self.options['name']
- if 'label' in self.options:
- node['label'] = self.options['label']
- node['nowrap'] = 'nowrap' in self.options
- node['docname'] = self.env.docname
- ret = [node]
- set_source_info(self, node)
- if hasattr(self, 'src'):
- node.source = self.src
- self.add_target(ret)
- return ret
-
- def add_target(self, ret):
- # type: (List[nodes.Node]) -> None
- node = ret[0]
-
- # assign label automatically if math_number_all enabled
- if node['label'] == '' or (self.config.math_number_all and not node['label']):
- seq = self.env.new_serialno('sphinx.ext.math#equations')
- node['label'] = "%s:%d" % (self.env.docname, seq)
-
- # no targets and numbers are needed
- if not node['label']:
- return
-
- # register label to domain
- domain = self.env.get_domain('math')
- try:
- eqno = domain.add_equation(self.env, self.env.docname, node['label']) # type: ignore # NOQA
- node['number'] = eqno
-
- # add target node
- node_id = make_id('equation-%s' % node['label'])
- target = nodes.target('', '', ids=[node_id])
- self.state.document.note_explicit_target(target)
- ret.insert(0, target)
- except UserWarning as exc:
- self.state_machine.reporter.warning(exc.args[0], line=self.lineno)
-
-
-def latex_visit_math(self, node):
- # type: (nodes.NodeVisitor, math) -> None
- if is_in_section_title(node):
- protect = r'\protect'
- else:
- protect = ''
- equation = protect + r'\(' + node['latex'] + protect + r'\)'
- self.body.append(equation)
- raise nodes.SkipNode
-
-
-def latex_visit_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- if not node['label']:
- label = None
- else:
- label = "equation:%s:%s" % (node['docname'], node['label'])
-
- if node['nowrap']:
- if label:
- self.body.append(r'\label{%s}' % label)
- self.body.append(node['latex'])
- else:
- self.body.append(wrap_displaymath(node['latex'], label,
- self.builder.config.math_number_all))
- raise nodes.SkipNode
-
-
-def latex_visit_eqref(self, node):
- # type: (nodes.NodeVisitor, eqref) -> None
- label = "equation:%s:%s" % (node['docname'], node['target'])
- eqref_format = self.builder.config.math_eqref_format
- if eqref_format:
- try:
- ref = '\\ref{%s}' % label
- self.body.append(eqref_format.format(number=ref))
- except KeyError as exc:
- logger.warning(__('Invalid math_eqref_format: %r'), exc,
- location=node)
- self.body.append('\\eqref{%s}' % label)
- else:
- self.body.append('\\eqref{%s}' % label)
- raise nodes.SkipNode
-
-
-def text_visit_math(self, node):
- # type: (nodes.NodeVisitor, math) -> None
- self.add_text(node['latex'])
- raise nodes.SkipNode
-
-
-def text_visit_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- self.new_state()
- self.add_text(node['latex'])
- self.end_state()
- raise nodes.SkipNode
-
-
-def man_visit_math(self, node):
- # type: (nodes.NodeVisitor, math) -> None
- self.body.append(node['latex'])
- raise nodes.SkipNode
-
-
-def man_visit_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- self.visit_centered(node)
-
-
-def man_depart_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- self.depart_centered(node)
-
-
-def texinfo_visit_math(self, node):
- # type: (nodes.NodeVisitor, math) -> None
- self.body.append('@math{' + self.escape_arg(node['latex']) + '}')
- raise nodes.SkipNode
-
-
-def texinfo_visit_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- if node.get('label'):
- self.add_anchor(node['label'], node)
- self.body.append('\n\n@example\n%s\n@end example\n\n' %
- self.escape_arg(node['latex']))
-
-
-def texinfo_depart_displaymath(self, node):
- # type: (nodes.NodeVisitor, displaymath) -> None
- pass
-
-
def setup_math(app, htmlinlinevisitors, htmldisplayvisitors):
# type: (Sphinx, Tuple[Callable, Any], Tuple[Callable, Any]) -> None
- app.add_config_value('math_number_all', False, 'env')
- app.add_config_value('math_eqref_format', None, 'env', string_classes)
- app.add_config_value('math_numfig', True, 'env')
- app.add_domain(MathDomain)
app.add_node(math, override=True,
- latex=(latex_visit_math, None),
- text=(text_visit_math, None),
- man=(man_visit_math, None),
- texinfo=(texinfo_visit_math, None),
html=htmlinlinevisitors)
- app.add_node(displaymath,
- latex=(latex_visit_displaymath, None),
- text=(text_visit_displaymath, None),
- man=(man_visit_displaymath, man_depart_displaymath),
- texinfo=(texinfo_visit_displaymath, texinfo_depart_displaymath),
+ app.add_node(displaymath, override=True,
html=htmldisplayvisitors)
- app.add_node(eqref, latex=(latex_visit_eqref, None))
- app.add_role('math', math_role)
- app.add_role('eq', EqXRefRole(warn_dangling=True))
- app.add_directive('math', MathDirective)
diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py
index 2bb7eec09..39fbb8539 100644
--- a/sphinx/ext/mathjax.py
+++ b/sphinx/ext/mathjax.py
@@ -29,7 +29,7 @@ def html_visit_math(self, node):
# type: (nodes.NodeVisitor, nodes.Node) -> None
self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight'))
self.body.append(self.builder.config.mathjax_inline[0] +
- self.encode(node['latex']) +
+ self.encode(node.astext()) +
self.builder.config.mathjax_inline[1] + '')
raise nodes.SkipNode
@@ -38,7 +38,7 @@ def html_visit_displaymath(self, node):
# type: (nodes.NodeVisitor, nodes.Node) -> None
self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight'))
if node['nowrap']:
- self.body.append(self.encode(node['latex']))
+ self.body.append(self.encode(node.astext()))
self.body.append('')
raise nodes.SkipNode
@@ -49,7 +49,7 @@ def html_visit_displaymath(self, node):
self.add_permalink_ref(node, _('Permalink to this equation'))
self.body.append('')
self.body.append(self.builder.config.mathjax_display[0])
- parts = [prt for prt in node['latex'].split('\n\n') if prt.strip()]
+ parts = [prt for prt in node.astext().split('\n\n') if prt.strip()]
if len(parts) > 1: # Add alignment if there are more than 1 equation
self.body.append(r' \begin{align}\begin{aligned}')
for i, part in enumerate(parts):
diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py
index 76ed154fd..94ac9a6e7 100644
--- a/sphinx/testing/fixtures.py
+++ b/sphinx/testing/fixtures.py
@@ -163,7 +163,7 @@ def make_app(test_params, monkeypatch):
yield make
sys.path[:] = syspath
- for app_ in apps:
+ for app_ in reversed(apps): # clean up applications from the new ones
app_.cleanup()
diff --git a/sphinx/transforms/post_transforms/compat.py b/sphinx/transforms/post_transforms/compat.py
new file mode 100644
index 000000000..94360f038
--- /dev/null
+++ b/sphinx/transforms/post_transforms/compat.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.transforms.post_transforms.compat
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Post transforms for compatibility
+
+ :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import warnings
+from typing import TYPE_CHECKING
+
+from docutils import nodes
+from docutils.writers.docutils_xml import XMLTranslator
+
+from sphinx.addnodes import math_block, displaymath
+from sphinx.deprecation import RemovedInSphinx30Warning
+from sphinx.transforms import SphinxTransform
+from sphinx.util import logging
+
+if TYPE_CHECKING:
+ from typing import Any, Callable, Dict, Iterable, List, Tuple # NOQA
+ from docutils.parsers.rst.states import Inliner # NOQA
+ from docutils.writers.html4css1 import Writer # NOQA
+ from sphinx.application import Sphinx # NOQA
+ from sphinx.builders import Builder # NOQA
+ from sphinx.environment import BuildEnvironment # NOQA
+
+logger = logging.getLogger(__name__)
+
+
+class MathNodeMigrator(SphinxTransform):
+ """Migrate a math node to docutils'.
+
+ For a long time, Sphinx uses an original node for math. Since 1.8,
+ Sphinx starts to use a math node of docutils'. This transform converts
+ old and new nodes to keep compatibility.
+ """
+ default_priority = 999
+
+ def apply(self):
+ # type: () -> None
+ for node in self.document.traverse(nodes.math):
+ # case: old styled ``math`` node generated by old extensions
+ if len(node) == 0:
+ warnings.warn("math node for Sphinx was replaced by docutils'. "
+ "Please use ``docutils.nodes.math`` instead.",
+ RemovedInSphinx30Warning)
+ equation = node['latex']
+ node += nodes.Text(equation, equation)
+
+ translator = self.app.builder.get_translator_class()
+ if hasattr(translator, 'visit_displaymath') and translator != XMLTranslator:
+ # case: old translators which does not support ``math_block`` node
+ warnings.warn("Translator for %s does not support math_block node'. "
+ "Please update your extension." % translator,
+ RemovedInSphinx30Warning)
+ for node in self.document.traverse(math_block):
+ alt = displaymath(latex=node.astext(),
+ number=node.get('number'),
+ label=node.get('label'),
+ nowrap=node.get('nowrap'),
+ docname=node.get('docname'))
+ node.replace(alt)
+ else:
+ # case: old styled ``displaymath`` node generated by old extensions
+ for node in self.document.traverse(math_block):
+ if len(node) == 0:
+ warnings.warn("math node for Sphinx was replaced by docutils'. "
+ "Please use ``docutils.nodes.math_block`` instead.",
+ RemovedInSphinx30Warning)
+ latex = node['latex']
+ node += nodes.Text(latex, latex)
+
+
+def setup(app):
+ # type: (Sphinx) -> Dict[unicode, Any]
+ app.add_post_transform(MathNodeMigrator)
+
+ return {
+ 'version': 'builtin',
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index 2c0cc0424..0ccc46e4b 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -2542,13 +2542,51 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_math(self, node):
# type: (nodes.Node) -> None
- logger.warning(__('using "math" markup without a Sphinx math extension '
- 'active, please use one of the math extensions '
- 'described at http://sphinx-doc.org/en/master/ext/math.html'),
- location=(self.curfilestack[-1], node.line))
+ if self.in_title:
+ self.body.append(r'\protect\(%s\protect\)' % node.astext())
+ else:
+ self.body.append(r'\(%s\)' % node.astext())
raise nodes.SkipNode
- visit_math_block = visit_math
+ def visit_math_block(self, node):
+ # type: (nodes.Node) -> None
+ if node.get('label'):
+ label = "equation:%s:%s" % (node['docname'], node['label'])
+ else:
+ label = None
+
+ if node.get('nowrap'):
+ if label:
+ self.body.append(r'\label{%s}' % label)
+ self.body.append(node.astext())
+ else:
+ def is_equation(part):
+ # type: (unicode) -> unicode
+ return part.strip()
+
+ from sphinx.ext.mathbase import wrap_displaymath
+ self.body.append(wrap_displaymath(node.astext(), label,
+ self.builder.config.math_number_all))
+ raise nodes.SkipNode
+
+ def visit_math_reference(self, node):
+ # type: (nodes.Node) -> None
+ label = "equation:%s:%s" % (node['docname'], node['target'])
+ eqref_format = self.builder.config.math_eqref_format
+ if eqref_format:
+ try:
+ ref = r'\ref{%s}' % label
+ self.body.append(eqref_format.format(number=ref))
+ except KeyError as exc:
+ logger.warning(__('Invalid math_eqref_format: %r'), exc,
+ location=node)
+ self.body.append(r'\eqref{%s}' % label)
+ else:
+ self.body.append(r'\eqref{%s}' % label)
+
+ def depart_math_reference(self, node):
+ # type: (nodes.Node) -> None
+ pass
def unknown_visit(self, node):
# type: (nodes.Node) -> None
diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py
index c6c8723dd..02540a53d 100644
--- a/sphinx/writers/manpage.py
+++ b/sphinx/writers/manpage.py
@@ -18,7 +18,7 @@ from docutils.writers.manpage import (
import sphinx.util.docutils
from sphinx import addnodes
-from sphinx.locale import admonitionlabels, _, __
+from sphinx.locale import admonitionlabels, _
from sphinx.util import logging
from sphinx.util.i18n import format_date
@@ -513,12 +513,19 @@ class ManualPageTranslator(BaseTranslator):
def visit_math(self, node):
# type: (nodes.Node) -> None
- logger.warning(__('using "math" markup without a Sphinx math extension '
- 'active, please use one of the math extensions '
- 'described at http://sphinx-doc.org/en/master/ext/math.html'))
- raise nodes.SkipNode
+ pass
- visit_math_block = visit_math
+ def depart_math(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
+ def visit_math_block(self, node):
+ # type: (nodes.Node) -> None
+ self.visit_centered(node)
+
+ def depart_math_block(self, node):
+ # type: (nodes.Node) -> None
+ self.depart_centered(node)
def unknown_visit(self, node):
# type: (nodes.Node) -> None
diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py
index e58d659ab..4f76c6cf9 100644
--- a/sphinx/writers/texinfo.py
+++ b/sphinx/writers/texinfo.py
@@ -1731,9 +1731,13 @@ class TexinfoTranslator(nodes.NodeVisitor):
def visit_math(self, node):
# type: (nodes.Node) -> None
- logger.warning(__('using "math" markup without a Sphinx math extension '
- 'active, please use one of the math extensions '
- 'described at http://sphinx-doc.org/en/master/ext/math.html'))
+ self.body.append('@math{' + self.escape_arg(node.astext()) + '}')
raise nodes.SkipNode
- visit_math_block = visit_math
+ def visit_math_block(self, node):
+ # type: (nodes.Node) -> None
+ if node.get('label'):
+ self.add_anchor(node['label'], node)
+ self.body.append('\n\n@example\n%s\n@end example\n\n' %
+ self.escape_arg(node.astext()))
+ raise nodes.SkipNode
diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py
index 379f06b46..0f9b45fbe 100644
--- a/sphinx/writers/text.py
+++ b/sphinx/writers/text.py
@@ -18,7 +18,7 @@ from docutils.utils import column_width
from six.moves import zip_longest
from sphinx import addnodes
-from sphinx.locale import admonitionlabels, _, __
+from sphinx.locale import admonitionlabels, _
from sphinx.util import logging
if False:
@@ -1179,13 +1179,19 @@ class TextTranslator(nodes.NodeVisitor):
def visit_math(self, node):
# type: (nodes.Node) -> None
- logger.warning(__('using "math" markup without a Sphinx math extension '
- 'active, please use one of the math extensions '
- 'described at http://sphinx-doc.org/en/master/ext/math.html'),
- location=(self.builder.current_docname, node.line))
- raise nodes.SkipNode
+ pass
- visit_math_block = visit_math
+ def depart_math(self, node):
+ # type: (nodes.Node) -> None
+ pass
+
+ def visit_math_block(self, node):
+ # type: (nodes.Node) -> None
+ self.new_state()
+
+ def depart_math_block(self, node):
+ # type: (nodes.Node) -> None
+ self.end_state()
def unknown_visit(self, node):
# type: (nodes.Node) -> None
diff --git a/tests/roots/test-ext-math-compat/conf.py b/tests/roots/test-ext-math-compat/conf.py
new file mode 100644
index 000000000..c4f6005ea
--- /dev/null
+++ b/tests/roots/test-ext-math-compat/conf.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from docutils.parsers.rst import Directive
+
+from sphinx.ext.mathbase import math, displaymath
+
+master_doc = 'index'
+extensions = ['sphinx.ext.mathjax']
+
+
+def my_math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
+ return [math(latex='E = mc^2')], []
+
+
+class MyMathDirective(Directive):
+ def run(self):
+ return [displaymath(latex='E = mc^2')]
+
+
+def setup(app):
+ app.add_role('my_math', my_math_role)
+ app.add_directive('my-math', MyMathDirective)
diff --git a/tests/roots/test-ext-math-compat/index.rst b/tests/roots/test-ext-math-compat/index.rst
new file mode 100644
index 000000000..208878c36
--- /dev/null
+++ b/tests/roots/test-ext-math-compat/index.rst
@@ -0,0 +1,21 @@
+Test Math
+=========
+
+inline
+------
+
+Inline: :math:`E=mc^2`
+Inline my math: :my_math:`:-)`
+
+block
+-----
+
+.. math:: a^2+b^2=c^2
+
+Second math
+
+.. math:: e^{i\pi}+1=0
+
+Multi math equations
+
+.. my-math::
diff --git a/tests/test_config.py b/tests/test_config.py
index e3b79c835..6a910ecfc 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -173,25 +173,19 @@ def test_needs_sphinx(make_app_with_empty_project):
make_app = make_app_with_empty_project
# micro version
app = make_app(confoverrides={'needs_sphinx': '1.3.3'}) # OK: less
- app.cleanup()
app = make_app(confoverrides={'needs_sphinx': '1.3.4'}) # OK: equals
- app.cleanup()
with pytest.raises(VersionRequirementError):
make_app(confoverrides={'needs_sphinx': '1.3.5'}) # NG: greater
# minor version
app = make_app(confoverrides={'needs_sphinx': '1.2'}) # OK: less
- app.cleanup()
app = make_app(confoverrides={'needs_sphinx': '1.3'}) # OK: equals
- app.cleanup()
with pytest.raises(VersionRequirementError):
make_app(confoverrides={'needs_sphinx': '1.4'}) # NG: greater
# major version
app = make_app(confoverrides={'needs_sphinx': '0'}) # OK: less
- app.cleanup()
app = make_app(confoverrides={'needs_sphinx': '1'}) # OK: equals
- app.cleanup()
with pytest.raises(VersionRequirementError):
make_app(confoverrides={'needs_sphinx': '2'}) # NG: greater
diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py
index 28ce094a8..47465f07e 100644
--- a/tests/test_ext_math.py
+++ b/tests/test_ext_math.py
@@ -12,8 +12,12 @@
import errno
import re
import subprocess
+import warnings
import pytest
+from docutils import nodes
+
+from sphinx.testing.util import assert_node
def has_binary(binary):
@@ -208,3 +212,28 @@ def test_imgmath_numfig_html(app, status, warning):
'href="math.html#equation-foo">(1) and '
'(3).')
assert html in content
+
+
+@pytest.mark.sphinx('dummy', testroot='ext-math-compat')
+def test_math_compat(app, status, warning):
+ with warnings.catch_warnings(record=True):
+ app.builder.build_all()
+ doctree = app.env.get_and_resolve_doctree('index', app.builder)
+
+ assert_node(doctree,
+ [nodes.document, nodes.section, (nodes.title,
+ [nodes.section, (nodes.title,
+ nodes.paragraph)],
+ nodes.section)])
+ assert_node(doctree[0][1][1],
+ ('Inline: ',
+ [nodes.math, "E=mc^2"],
+ '\nInline my math: ',
+ [nodes.math, "E = mc^2"]))
+ assert_node(doctree[0][2],
+ ([nodes.title, "block"],
+ [nodes.math_block, "a^2+b^2=c^2\n\n"],
+ [nodes.paragraph, "Second math"],
+ [nodes.math_block, "e^{i\\pi}+1=0\n\n"],
+ [nodes.paragraph, "Multi math equations"],
+ [nodes.math_block, "E = mc^2"]))