Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/markdown/core.py: 69%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Python Markdown
3# A Python implementation of John Gruber's Markdown.
5# Documentation: https://python-markdown.github.io/
6# GitHub: https://github.com/Python-Markdown/markdown/
7# PyPI: https://pypi.org/project/Markdown/
9# Started by Manfred Stienstra (http://www.dwerg.net/).
10# Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
11# Currently maintained by Waylan Limberg (https://github.com/waylan),
12# Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
14# Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later)
15# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
16# Copyright 2004 Manfred Stienstra (the original version)
18# License: BSD (see LICENSE.md for details).
20from __future__ import annotations
22import codecs
23import sys
24import logging
25import importlib
26from typing import TYPE_CHECKING, Any, BinaryIO, Callable, ClassVar, Mapping, Sequence
27from . import util
28from .preprocessors import build_preprocessors
29from .blockprocessors import build_block_parser
30from .treeprocessors import build_treeprocessors
31from .inlinepatterns import build_inlinepatterns, DelimiterProcessor
32from .postprocessors import build_postprocessors
33from .extensions import Extension
34from .serializers import to_html_string, to_xhtml_string
35from .util import BLOCK_LEVEL_ELEMENTS
37if TYPE_CHECKING: # pragma: no cover
38 from xml.etree.ElementTree import Element
40__all__ = ['Markdown', 'markdown', 'markdownFromFile']
43logger = logging.getLogger('MARKDOWN')
46class Markdown:
47 """
48 A parser which converts Markdown to HTML.
50 Attributes:
51 Markdown.tab_length (int): The number of spaces which correspond to a single tab. Default: `4`.
52 Markdown.ESCAPED_CHARS (list[str]): List of characters which get the backslash escape treatment.
53 Markdown.block_level_elements (list[str]): List of HTML tags which get treated as block-level elements.
54 See [`markdown.util.BLOCK_LEVEL_ELEMENTS`][] for the full list of elements.
55 Markdown.registeredExtensions (list[Extension]): List of extensions which have called
56 [`registerExtension`][markdown.Markdown.registerExtension] during setup.
57 Markdown.doc_tag (str): Element used to wrap document. Default: `div`.
58 Markdown.stripTopLevelTags (bool): Indicates whether the `doc_tag` should be removed. Default: 'True'.
59 Markdown.references (dict[str, tuple[str, str]]): A mapping of link references found in a parsed document
60 where the key is the reference name and the value is a tuple of the URL and title.
61 Markdown.htmlStash (util.HtmlStash): The instance of the `HtmlStash` used by an instance of this class.
62 Markdown.output_formats (dict[str, Callable[xml.etree.ElementTree.Element]]): A mapping of known output
63 formats by name and their respective serializers. Each serializer must be a callable which accepts an
64 [`Element`][xml.etree.ElementTree.Element] and returns a `str`.
65 Markdown.output_format (str): The output format set by
66 [`set_output_format`][markdown.Markdown.set_output_format].
67 Markdown.serializer (Callable[xml.etree.ElementTree.Element]): The serializer set by
68 [`set_output_format`][markdown.Markdown.set_output_format].
69 Markdown.preprocessors (util.Registry): A collection of [`preprocessors`][markdown.preprocessors].
70 Markdown.parser (blockparser.BlockParser): A collection of [`blockprocessors`][markdown.blockprocessors].
71 Markdown.inlinePatterns (util.Registry): A collection of [`inlinepatterns`][markdown.inlinepatterns].
72 Markdown.treeprocessors (util.Registry): A collection of [`treeprocessors`][markdown.treeprocessors].
73 Markdown.postprocessors (util.Registry): A collection of [`postprocessors`][markdown.postprocessors].
75 """
77 doc_tag = "div" # Element used to wrap document - later removed
79 output_formats: ClassVar[dict[str, Callable[[Element], str]]] = {
80 'html': to_html_string,
81 'xhtml': to_xhtml_string,
82 }
83 """
84 A mapping of known output formats by name and their respective serializers. Each serializer must be a
85 callable which accepts an [`Element`][xml.etree.ElementTree.Element] and returns a `str`.
86 """
88 def __init__(self, **kwargs):
89 """
90 Creates a new Markdown instance.
92 Keyword Arguments:
93 extensions (list[Extension | str]): A list of extensions.
95 If an item is an instance of a subclass of [`markdown.extensions.Extension`][],
96 the instance will be used as-is. If an item is of type `str`, it is passed
97 to [`build_extension`][markdown.Markdown.build_extension] with its corresponding
98 `extension_configs` and the returned instance of [`markdown.extensions.Extension`][]
99 is used.
100 extension_configs (dict[str, dict[str, Any]]): Configuration settings for extensions.
101 output_format (str): Format of output. Supported formats are:
103 * `xhtml`: Outputs XHTML style tags. Default.
104 * `html`: Outputs HTML style tags.
105 tab_length (int): Length of tabs in the source. Default: `4`
107 """
109 self.last_run: float = 0.0
110 self.tab_length: int = kwargs.get('tab_length', 4)
112 self.ESCAPED_CHARS: list[str] = [
113 '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!'
114 ]
115 """ List of characters which get the backslash escape treatment. """
117 self.block_level_elements: list[str] = BLOCK_LEVEL_ELEMENTS.copy()
119 self.registeredExtensions: list[Extension] = []
120 self.docType = "" # TODO: Maybe delete this. It does not appear to be used anymore.
121 self.stripTopLevelTags: bool = True
122 self.delimiters: DelimiterProcessor | None = None
124 self.build_parser()
126 self.references: dict[str, tuple[str, str]] = {}
127 self.htmlStash: util.HtmlStash = util.HtmlStash()
128 self.registerExtensions(extensions=kwargs.get('extensions', []),
129 configs=kwargs.get('extension_configs', {}))
130 self.set_output_format(kwargs.get('output_format', 'xhtml'))
131 self.reset()
133 def build_parser(self) -> Markdown:
134 """
135 Build the parser from the various parts.
137 Assigns a value to each of the following attributes on the class instance:
139 * **`Markdown.preprocessors`** ([`Registry`][markdown.util.Registry]) -- A collection of
140 [`preprocessors`][markdown.preprocessors].
141 * **`Markdown.parser`** ([`BlockParser`][markdown.blockparser.BlockParser]) -- A collection of
142 [`blockprocessors`][markdown.blockprocessors].
143 * **`Markdown.inlinePatterns`** ([`Registry`][markdown.util.Registry]) -- A collection of
144 [`inlinepatterns`][markdown.inlinepatterns].
145 * **`Markdown.treeprocessors`** ([`Registry`][markdown.util.Registry]) -- A collection of
146 [`treeprocessors`][markdown.treeprocessors].
147 * **`Markdown.postprocessors`** ([`Registry`][markdown.util.Registry]) -- A collection of
148 [`postprocessors`][markdown.postprocessors].
150 This method could be redefined in a subclass to build a custom parser which is made up of a different
151 combination of processors and patterns.
153 """
154 self.preprocessors = build_preprocessors(self)
155 self.parser = build_block_parser(self)
156 self.inlinePatterns = build_inlinepatterns(self)
157 self.treeprocessors = build_treeprocessors(self)
158 self.postprocessors = build_postprocessors(self)
159 return self
161 def registerExtensions(
162 self,
163 extensions: Sequence[Extension | str],
164 configs: Mapping[str, dict[str, Any]]
165 ) -> Markdown:
166 """
167 Load a list of extensions into an instance of the `Markdown` class.
169 Arguments:
170 extensions (list[Extension | str]): A list of extensions.
172 If an item is an instance of a subclass of [`markdown.extensions.Extension`][],
173 the instance will be used as-is. If an item is of type `str`, it is passed
174 to [`build_extension`][markdown.Markdown.build_extension] with its corresponding `configs` and the
175 returned instance of [`markdown.extensions.Extension`][] is used.
176 configs (dict[str, dict[str, Any]]): Configuration settings for extensions.
178 """
179 for ext in extensions:
180 if isinstance(ext, str):
181 ext = self.build_extension(ext, configs.get(ext, {}))
182 if isinstance(ext, Extension):
183 ext.extendMarkdown(self)
184 logger.debug(
185 'Successfully loaded extension "%s.%s".'
186 % (ext.__class__.__module__, ext.__class__.__name__)
187 )
188 elif ext is not None:
189 raise TypeError(
190 'Extension "{}.{}" must be of type: "{}.{}"'.format(
191 ext.__class__.__module__, ext.__class__.__name__,
192 Extension.__module__, Extension.__name__
193 )
194 )
195 return self
197 def build_extension(self, ext_name: str, configs: Mapping[str, Any]) -> Extension:
198 """
199 Build extension from a string name, then return an instance using the given `configs`.
201 Arguments:
202 ext_name: Name of extension as a string.
203 configs: Configuration settings for extension.
205 Returns:
206 An instance of the extension with the given configuration settings.
208 First attempt to load an entry point. The string name must be registered as an entry point in the
209 `markdown.extensions` group which points to a subclass of the [`markdown.extensions.Extension`][] class.
210 If multiple distributions have registered the same name, the first one found is returned.
212 If no entry point is found, assume dot notation (`path.to.module:ClassName`). Load the specified class and
213 return an instance. If no class is specified, import the module and call a `makeExtension` function and return
214 the [`markdown.extensions.Extension`][] instance returned by that function.
215 """
216 configs = dict(configs)
218 entry_points = [ep for ep in util.get_installed_extensions() if ep.name == ext_name]
219 if entry_points:
220 ext = entry_points[0].load()
221 return ext(**configs)
223 # Get class name (if provided): `path.to.module:ClassName`
224 ext_name, class_name = ext_name.split(':', 1) if ':' in ext_name else (ext_name, '')
226 try:
227 module = importlib.import_module(ext_name)
228 logger.debug(
229 'Successfully imported extension module "%s".' % ext_name
230 )
231 except ImportError as e:
232 message = 'Failed loading extension "%s".' % ext_name
233 e.args = (message,) + e.args[1:]
234 raise
236 if class_name:
237 # Load given class name from module.
238 return getattr(module, class_name)(**configs)
239 else:
240 # Expect `makeExtension()` function to return a class.
241 try:
242 return module.makeExtension(**configs)
243 except AttributeError as e:
244 message = e.args[0]
245 message = "Failed to initiate extension " \
246 "'%s': %s" % (ext_name, message)
247 e.args = (message,) + e.args[1:]
248 raise
250 def registerExtension(self, extension: Extension) -> Markdown:
251 """
252 Register an extension as having a resettable state.
254 Arguments:
255 extension: An instance of the extension to register.
257 This should get called once by an extension during setup. A "registered" extension's
258 `reset` method is called by [`Markdown.reset()`][markdown.Markdown.reset]. Not all extensions have or need a
259 resettable state, and so it should not be assumed that all extensions are "registered."
261 """
262 self.registeredExtensions.append(extension)
263 return self
265 def reset(self) -> Markdown:
266 """
267 Resets all state variables to prepare the parser instance for new input.
269 Called once upon creation of a class instance. Should be called manually between calls
270 to [`Markdown.convert`][markdown.Markdown.convert].
271 """
272 self.htmlStash.reset()
273 self.references.clear()
275 if self.delimiters is not None:
276 self.delimiters.reset()
277 if self.delimiters not in self.inlinePatterns:
278 self.delimiters = None
280 for extension in self.registeredExtensions:
281 if hasattr(extension, 'reset'):
282 extension.reset()
284 return self
286 def set_output_format(self, format: str) -> Markdown:
287 """
288 Set the output format for the class instance.
290 Arguments:
291 format: Must be a known value in `Markdown.output_formats`.
293 """
294 self.output_format = format.lower().rstrip('145') # ignore number
295 try:
296 self.serializer = self.output_formats[self.output_format]
297 except KeyError as e:
298 valid_formats = list(self.output_formats.keys())
299 valid_formats.sort()
300 message = 'Invalid Output Format: "%s". Use one of %s.' \
301 % (self.output_format,
302 '"' + '", "'.join(valid_formats) + '"')
303 e.args = (message,) + e.args[1:]
304 raise
305 return self
307 # Note: the `tag` argument is type annotated `Any` as ElementTree uses many various objects as tags.
308 # As there is no standardization in ElementTree, the type of a given tag is unpredictable.
309 def is_block_level(self, tag: Any) -> bool:
310 """
311 Check if the given `tag` is a block level HTML tag.
313 Returns `True` for any string listed in `Markdown.block_level_elements`. A `tag` which is
314 not a string always returns `False`.
316 """
317 if isinstance(tag, str):
318 return tag.lower().rstrip('/') in self.block_level_elements
319 # Some ElementTree tags are not strings, so return False.
320 return False
322 def convert(self, source: str) -> str:
323 """
324 Convert a Markdown string to a string in the specified output format.
326 Arguments:
327 source: Markdown formatted text as Unicode or ASCII string.
329 Returns:
330 A string in the specified output format.
332 Markdown parsing takes place in five steps:
334 1. A bunch of [`preprocessors`][markdown.preprocessors] munge the input text.
335 2. A [`BlockParser`][markdown.blockparser.BlockParser] parses the high-level structural elements of the
336 pre-processed text into an [`ElementTree`][xml.etree.ElementTree.ElementTree] object.
337 3. A bunch of [`treeprocessors`][markdown.treeprocessors] are run against the
338 [`ElementTree`][xml.etree.ElementTree.ElementTree] object. One such `treeprocessor`
339 ([`markdown.treeprocessors.InlineProcessor`][]) runs [`inlinepatterns`][markdown.inlinepatterns]
340 against the [`ElementTree`][xml.etree.ElementTree.ElementTree] object, parsing inline markup.
341 4. Some [`postprocessors`][markdown.postprocessors] are run against the text after the
342 [`ElementTree`][xml.etree.ElementTree.ElementTree] object has been serialized into text.
343 5. The output is returned as a string.
345 !!! warning
346 The Python-Markdown library does ***not*** sanitize its HTML output.
347 If you are processing Markdown input from an untrusted source, it is your
348 responsibility to ensure that it is properly sanitized. For more
349 information see [Sanitizing HTML Output](../../sanitization.md).
351 """
353 # Fix up the source text
354 if not source.strip():
355 return '' # a blank Unicode string
357 try:
358 source = str(source)
359 except UnicodeDecodeError as e: # pragma: no cover
360 # Customize error message while maintaining original traceback
361 e.reason += '. -- Note: Markdown only accepts Unicode input!'
362 raise
364 # Split into lines and run the line preprocessors.
365 self.lines = source.split("\n")
366 for prep in self.preprocessors:
367 self.lines = prep.run(self.lines)
369 # Parse the high-level elements.
370 root = self.parser.parseDocument(self.lines).getroot()
372 # Run the tree-processors
373 for treeprocessor in self.treeprocessors:
374 newRoot = treeprocessor.run(root)
375 if newRoot is not None:
376 root = newRoot
378 # Serialize _properly_. Strip top-level tags.
379 output = self.serializer(root)
380 if self.stripTopLevelTags:
381 try:
382 start = output.index(
383 '<%s>' % self.doc_tag) + len(self.doc_tag) + 2
384 end = output.rindex('</%s>' % self.doc_tag)
385 output = output[start:end].strip()
386 except ValueError as e: # pragma: no cover
387 if output.strip().endswith('<%s />' % self.doc_tag):
388 # We have an empty document
389 output = ''
390 else:
391 # We have a serious problem
392 raise ValueError('Markdown failed to strip top-level '
393 'tags. Document=%r' % output.strip()) from e
395 # Run the text post-processors
396 for pp in self.postprocessors:
397 output = pp.run(output)
399 return output.strip()
401 def convertFile(
402 self,
403 input: str | BinaryIO | None = None,
404 output: str | BinaryIO | None = None,
405 encoding: str | None = None,
406 ) -> Markdown:
407 """
408 Read Markdown text from a file or stream and write HTML output to a file or stream.
410 Decodes the input file using the provided encoding (defaults to `utf-8`),
411 passes the file content to markdown, and outputs the HTML to either
412 the provided stream or the file with provided name, using the same
413 encoding as the source file. The
414 [`xmlcharrefreplace`](https://docs.python.org/3/library/codecs.html#error-handlers)
415 error handler is used when encoding the output.
417 **Note:** This is the only place that decoding and encoding of Unicode
418 takes place in Python-Markdown. (All other code is Unicode-in /
419 Unicode-out.)
421 Arguments:
422 input: File object or path. Reads from `stdin` if `None`.
423 output: File object or path. Writes to `stdout` if `None`.
424 encoding: Encoding of input and output files. Defaults to `utf-8`.
426 !!! warning
427 The Python-Markdown library does ***not*** sanitize its HTML output.
428 As `Markdown.convertFile` writes directly to the file system, there is no
429 easy way to sanitize the output from Python code. Therefore, it is
430 recommended that the `Markdown.convertFile` method not be used on input
431 from an untrusted source. For more information see [Sanitizing HTML
432 Output](../../sanitization.md).
434 """
436 encoding = encoding or "utf-8"
438 # Read the source
439 if input:
440 if isinstance(input, str):
441 input_file = open(input, mode="r", encoding=encoding)
442 else:
443 input_file = codecs.getreader(encoding)(input)
444 text = input_file.read()
445 input_file.close()
446 else:
447 text = sys.stdin.read()
449 text = text.lstrip('\ufeff') # remove the byte-order mark
451 # Convert
452 html = self.convert(text)
454 # Write to file or stdout
455 if output:
456 if isinstance(output, str):
457 output_file = codecs.open(output, "w",
458 encoding=encoding,
459 errors="xmlcharrefreplace")
460 output_file.write(html)
461 output_file.close()
462 else:
463 writer = codecs.getwriter(encoding)
464 output_file = writer(output, errors="xmlcharrefreplace")
465 output_file.write(html)
466 # Don't close here. User may want to write more.
467 else:
468 # Encode manually and write bytes to stdout.
469 html = html.encode(encoding, "xmlcharrefreplace")
470 sys.stdout.buffer.write(html)
472 return self
475"""
476EXPORTED FUNCTIONS
477=============================================================================
479Those are the two functions we really mean to export: `markdown()` and
480`markdownFromFile()`.
481"""
484def markdown(text: str, **kwargs: Any) -> str:
485 """
486 Convert a markdown string to HTML and return HTML as a Unicode string.
488 This is a shortcut function for [`Markdown`][markdown.Markdown] class to cover the most
489 basic use case. It initializes an instance of [`Markdown`][markdown.Markdown], loads the
490 necessary extensions and runs the parser on the given text.
492 Arguments:
493 text: Markdown formatted text as Unicode or ASCII string.
495 Keyword arguments:
496 **kwargs: Any arguments accepted by the Markdown class.
498 Returns:
499 A string in the specified output format.
501 !!! warning
502 The Python-Markdown library does ***not*** sanitize its HTML output.
503 If you are processing Markdown input from an untrusted source, it is your
504 responsibility to ensure that it is properly sanitized. For more
505 information see [Sanitizing HTML Output](../../sanitization.md).
507 """
508 md = Markdown(**kwargs)
509 return md.convert(text)
512def markdownFromFile(**kwargs: Any):
513 """
514 Read Markdown text from a file or stream and write HTML output to a file or stream.
516 This is a shortcut function which initializes an instance of [`Markdown`][markdown.Markdown],
517 and calls the [`convertFile`][markdown.Markdown.convertFile] method rather than
518 [`convert`][markdown.Markdown.convert].
520 Keyword arguments:
521 input (str | BinaryIO): A file name or readable object.
522 output (str | BinaryIO): A file name or writable object.
523 encoding (str): Encoding of input and output.
524 **kwargs: Any arguments accepted by the `Markdown` class.
526 !!! warning
527 The Python-Markdown library does ***not*** sanitize its HTML output.
528 As `markdown.markdownFromFile` writes directly to the file system, there is no
529 easy way to sanitize the output from Python code. Therefore, it is
530 recommended that the `markdown.markdownFromFile` function not be used on input
531 from an untrusted source. For more information see [Sanitizing HTML
532 Output](../../sanitization.md).
534 """
535 md = Markdown(**kwargs)
536 md.convertFile(kwargs.get('input', None),
537 kwargs.get('output', None),
538 kwargs.get('encoding', None))