From 5c38bfc0fbaf737e904d4a317c141fbcbd4f5dc3 Mon Sep 17 00:00:00 2001 From: Isaac Muse Date: Mon, 6 Jan 2025 20:38:27 -0700 Subject: [PATCH] Add support to handle custom HTML tags (#2559) --- docs/src/markdown/about/changelog.md | 6 +++ docs/src/markdown/extensions/blocks/api.md | 2 +- .../extensions/blocks/plugins/html.md | 43 +++++++++++++++++++ pymdownx/__meta__.py | 2 +- pymdownx/blocks/html.py | 27 +++++++++++- .../test_extensions/test_blocks/test_html.py | 42 ++++++++++++++++++ 6 files changed, 119 insertions(+), 3 deletions(-) diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md index 2ceb2f136..fc34ee333 100644 --- a/docs/src/markdown/about/changelog.md +++ b/docs/src/markdown/about/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 10.14 + +- **NEW**: Blocks.HTML: Add new `custom` option to specify tags and the assumed handling for them when automatic mode + is assumed. This can also be used to override the handling for recognized tags with automatic handling. +- **FIX**: Fix tests to pass with Pygments 2.19+. + ## 10.13 - **NEW**: Snippets: Allow multiple line numbers or line number blocks separated by `,`. diff --git a/docs/src/markdown/extensions/blocks/api.md b/docs/src/markdown/extensions/blocks/api.md index 812b24be3..2391c5cb2 100644 --- a/docs/src/markdown/extensions/blocks/api.md +++ b/docs/src/markdown/extensions/blocks/api.md @@ -290,7 +290,7 @@ Result\ Value | Description When using `raw` mode, all text will be accumulated under the specified element as an [`AtomicString`][atomic]. If nothing is done with the content during the [`on_end` event](#on_end-event), all the content will be HTML escaped by the Python Markdown parser. If desired, the content can be placed into the Python Markdown [HTML stash][stash] which will -protect it from any other rouge Treeprocessors. Keep in mind, if the content is stashed HTML escaping will not be +protect it from any other rouge Treeprocessors. Keep in mind, if the content is stashed, HTML escaping will not be applied automatically, so HTML escape if it is required. /// warning | Indent Raw Content diff --git a/docs/src/markdown/extensions/blocks/plugins/html.md b/docs/src/markdown/extensions/blocks/plugins/html.md index 40a3ef138..974681d4b 100644 --- a/docs/src/markdown/extensions/blocks/plugins/html.md +++ b/docs/src/markdown/extensions/blocks/plugins/html.md @@ -109,6 +109,49 @@ some *markdown* content //// /// +## Custom Tag Handling + +It is possible that in the future new tags could be added to the HTML spec, and it is also possible that the extension +may not recognize such tags for some length of time. While such tags can be added in future releases, the HTML extension +allows for users to specify such tags to navigate around such issues, or even make up their own tags. + +To teach the HTML extension about unknown tags, simply use the `custom` [option](#global-options) to specify a tag name +and the assumed default `mode`. + +```py3 +import markdown +from pymdownx.blocks.html import HTML +extensions = ['pymdownx.blocks.html'] +extension_configs = { + 'pymdown.blocks.html': { + 'custom': [ + {'tag': 'sometag', 'mode': 'block'} + ] + } +} +md = markdown.Markdown(extensions=extensions, extension_configs=extension_configs) +``` + +Then you can use your custom tag knowing it will be handled properly. + +``` +/// html | sometag + +Some block content. + +- List item A. + +- List item B. +/// + +``` + +## Global Options + +Options | Type | Descriptions +-------- | --------------- | ------------ +`custom` | \[dictionary\] | Specify custom tags with assumed default handling. + ## Per Block Options Options | Type | Descriptions diff --git a/pymdownx/__meta__.py b/pymdownx/__meta__.py index 11a97bcbd..7ef31bd77 100644 --- a/pymdownx/__meta__.py +++ b/pymdownx/__meta__.py @@ -185,5 +185,5 @@ def parse_version(ver, pre=False): return Version(major, minor, micro, release, pre, post, dev) -__version_info__ = Version(10, 13, 0, "final") +__version_info__ = Version(10, 14, 0, "final") __version__ = __version_info__._get_canonical() diff --git a/pymdownx/blocks/html.py b/pymdownx/blocks/html.py index 94c2d7b9e..12f204db9 100644 --- a/pymdownx/blocks/html.py +++ b/pymdownx/blocks/html.py @@ -39,6 +39,7 @@ RE_ATTR = re.compile(fr'(?P{IDENTIFIER}){ATTR}', flags=re.I | re.X) ATTRIBUTES = {'id': RE_ID, 'class': RE_CLASS, 'attr': RE_ATTRS} +VALID_MODES = {'auto', 'inline', 'block', 'raw', 'html'} def parse_selectors(selector): @@ -125,13 +126,17 @@ class HTML(Block): NAME = 'html' ARGUMENT = True OPTIONS = { - 'markdown': ['auto', type_string_in(['auto', 'inline', 'block', 'raw', 'html'])] + 'markdown': ['auto', type_string_in(VALID_MODES)] } def __init__(self, length, tracker, md, config): """Initialize.""" self.markdown = None + self.custom = {} + for entry in config.get('custom'): + mode = entry.get('mode', 'auto') + self.custom[entry['tag']] = mode if mode in VALID_MODES else 'auto' super().__init__(length, tracker, md, config) def on_validate(self, parent): @@ -148,6 +153,10 @@ def on_markdown(self): """Check if this is atomic.""" mode = self.options['markdown'] + if mode == 'auto': + tag = self.tag.lower() + mode = self.custom.get(tag, mode) + if mode == 'html': mode = 'raw' return mode @@ -167,6 +176,10 @@ def on_end(self, block): """On end event.""" mode = self.options['markdown'] + if mode == 'auto': + tag = self.tag.lower() + mode = self.custom.get(tag, mode) + if (mode == 'auto' and self.is_html(block)) or mode == 'html': block.text = self.md.htmlStash.store(block.text) elif (mode == 'auto' and self.is_raw(block)) or mode == 'raw': @@ -176,6 +189,18 @@ def on_end(self, block): class HTMLExtension(BlocksExtension): """HTML Blocks Extension.""" + def __init__(self, *args, **kwargs): + """Initialize.""" + + self.config = { + "custom": [ + [], + "Specify handling for custom blocks." + ] + } + + super().__init__(*args, **kwargs) + def extendMarkdownBlocks(self, md, block_mgr): """Extend Markdown blocks.""" diff --git a/tests/test_extensions/test_blocks/test_html.py b/tests/test_extensions/test_blocks/test_html.py index f97af9ed0..a3ea2275d 100644 --- a/tests/test_extensions/test_blocks/test_html.py +++ b/tests/test_extensions/test_blocks/test_html.py @@ -6,6 +6,13 @@ class TestBlocksHTML(util.MdCase): """Test Blocks HTML cases.""" extension = ['pymdownx.blocks.html', 'md_in_html'] + extension_configs = { + 'pymdownx.blocks.html': { + 'custom': [ + {'tag': 'custom', 'mode': 'block'} + ] + } + } def test_bad_tag(self): """Test bad HTML tag.""" @@ -323,3 +330,38 @@ def test_html_and_script(self): ''', True ) + + def test_custom(self): + """Test custom block handling.""" + + self.check_markdown( + R''' + /// html | custom + - a + - b + /// + ''', + ''' +
  • a
  • b
+ ''', + True + ) + + def test_custom_override(self): + """Test custom block handling but mode is overridden.""" + + self.check_markdown( + R''' + /// html | custom + markdown: inline + + - a + - b + /// + ''', + ''' + - a + - b + ''', + True + )