Revise `HTMLTranslator.prepare_svg()`.
[docutils.git] / docutils / docutils / parsers / recommonmark_wrapper.py
blobe84ae672559e2672f5dcbaebfb741c988444bc6b
1 #!/usr/bin/env python3
2 # :Copyright: © 2020 Günter Milde.
3 # :License: Released under the terms of the `2-Clause BSD license`_, in short:
5 # Copying and distribution of this file, with or without modification,
6 # are permitted in any medium without royalty provided the copyright
7 # notice and this notice are preserved.
8 # This file is offered as-is, without any warranty.
10 # .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause
12 # Revision: $Revision$
13 # Date: $Date$
14 """
15 A parser for CommonMark Markdown text using `recommonmark`__.
17 __ https://pypi.org/project/recommonmark/
19 .. important:: This module is deprecated.
21 * The "recommonmark" package is unmaintained and deprecated.
22 This wrapper module will be removed in Docutils 1.0.
24 * The API is not settled and may change with any minor Docutils version.
25 """
27 from docutils import Component
28 from docutils import nodes
30 try:
31 # If possible, import Sphinx's 'addnodes'
32 from sphinx import addnodes
33 except ImportError:
34 # stub to prevent errors if Sphinx isn't installed
35 import sys
36 import types
38 class pending_xref(nodes.Inline, nodes.Element):
39 ...
41 sys.modules['sphinx'] = sphinx = types.ModuleType('sphinx')
42 sphinx.addnodes = addnodes = types.SimpleNamespace()
43 addnodes.pending_xref = pending_xref
44 try:
45 import recommonmark
46 from recommonmark.parser import CommonMarkParser
47 except ImportError as err:
48 raise ImportError(
49 'Parsing "recommonmark" Markdown flavour requires the\n'
50 ' package https://pypi.org/project/recommonmark.'
51 ) from err
52 else:
53 if recommonmark.__version__ < '0.6.0':
54 raise ImportError('The installed version of "recommonmark" is too old.'
55 ' Update with "pip install -U recommonmark".')
58 # auxiliary function for `document.findall()`
59 def is_literal(node):
60 return isinstance(node, (nodes.literal, nodes.literal_block))
63 class Parser(CommonMarkParser):
64 """MarkDown parser based on recommonmark.
66 This parser is provisional:
67 the API is not settled and may change with any minor Docutils version.
68 """
69 supported = ('recommonmark', 'commonmark', 'markdown', 'md')
70 """Formats this parser supports."""
72 config_section = 'recommonmark parser'
73 config_section_dependencies = ('parsers',)
75 def get_transforms(self):
76 return Component.get_transforms(self) # + [AutoStructify]
78 def parse(self, inputstring, document):
79 """Wrapper of upstream method.
81 Ensure "line-length-limt". Report errors with `document.reporter`.
82 """
83 # check for exorbitantly long lines
84 for i, line in enumerate(inputstring.split('\n')):
85 if len(line) > document.settings.line_length_limit:
86 error = document.reporter.error(
87 'Line %d exceeds the line-length-limit.'%(i+1))
88 document.append(error)
89 return
91 # pass to upstream parser
92 try:
93 CommonMarkParser.parse(self, inputstring, document)
94 except Exception as err:
95 if document.settings.traceback:
96 raise err
97 error = document.reporter.error('Parsing with "recommonmark" '
98 'returned the error:\n%s'%err)
99 document.append(error)
101 # Post-Processing
102 # ---------------
104 def finish_parse(self) -> None:
105 """Finalize parse details. Call at end of `self.parse()`."""
107 document = self.document
109 # merge adjoining Text nodes:
110 for node in document.findall(nodes.TextElement):
111 children = node.children
112 i = 0
113 while i+1 < len(children):
114 if (isinstance(children[i], nodes.Text)
115 and isinstance(children[i+1], nodes.Text)):
116 children[i] = nodes.Text(children[i]+children.pop(i+1))
117 children[i].parent = node
118 else:
119 i += 1
121 # remove empty Text nodes:
122 for node in document.findall(nodes.Text):
123 if not len(node):
124 node.parent.remove(node)
126 # add "code" class argument to literal elements (inline and block)
127 for node in document.findall(is_literal):
128 if 'code' not in node['classes']:
129 node['classes'].append('code')
130 # move "language" argument to classes
131 for node in document.findall(nodes.literal_block):
132 if 'language' in node.attributes:
133 node['classes'].append(node['language'])
134 del node['language']
136 # replace raw nodes if raw is not allowed
137 if not document.settings.raw_enabled:
138 for node in document.findall(nodes.raw):
139 message = document.reporter.warning('Raw content disabled.')
140 if isinstance(node.parent, nodes.TextElement):
141 msgid = document.set_id(message)
142 problematic = nodes.problematic('', node.astext(),
143 refid=msgid)
144 node.parent.replace(node, problematic)
145 prbid = document.set_id(problematic)
146 message.add_backref(prbid)
147 document.append(message)
148 else:
149 node.parent.replace(node, message)
151 # drop pending_xref (Sphinx cross reference extension)
152 for node in document.findall(addnodes.pending_xref):
153 reference = node.children[0]
154 if 'name' not in reference:
155 reference['name'] = nodes.fully_normalize_name(
156 reference.astext())
157 node.parent.replace(node, reference)
158 # now we are ready to call the upstream function:
159 super().finish_parse()
161 def visit_document(self, node) -> None:
162 """Dummy function to prevent spurious warnings.
164 cf. https://github.com/readthedocs/recommonmark/issues/177
167 # Overwrite parent method with version that
168 # doesn't pass deprecated `rawsource` argument to nodes.Text:
169 def visit_text(self, mdnode) -> None:
170 self.current_node.append(nodes.Text(mdnode.literal))