Skip to content

Commit 5c38bfc

Browse files
authored
Add support to handle custom HTML tags (#2559)
1 parent dc2cda9 commit 5c38bfc

File tree

6 files changed

+119
-3
lines changed

6 files changed

+119
-3
lines changed

docs/src/markdown/about/changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 10.14
4+
5+
- **NEW**: Blocks.HTML: Add new `custom` option to specify tags and the assumed handling for them when automatic mode
6+
is assumed. This can also be used to override the handling for recognized tags with automatic handling.
7+
- **FIX**: Fix tests to pass with Pygments 2.19+.
8+
39
## 10.13
410

511
- **NEW**: Snippets: Allow multiple line numbers or line number blocks separated by `,`.

docs/src/markdown/extensions/blocks/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ Result\ Value | Description
290290
When using `raw` mode, all text will be accumulated under the specified element as an [`AtomicString`][atomic]. If
291291
nothing is done with the content during the [`on_end` event](#on_end-event), all the content will be HTML escaped by the
292292
Python Markdown parser. If desired, the content can be placed into the Python Markdown [HTML stash][stash] which will
293-
protect it from any other rouge Treeprocessors. Keep in mind, if the content is stashed HTML escaping will not be
293+
protect it from any other rouge Treeprocessors. Keep in mind, if the content is stashed, HTML escaping will not be
294294
applied automatically, so HTML escape if it is required.
295295

296296
/// warning | Indent Raw Content

docs/src/markdown/extensions/blocks/plugins/html.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,49 @@ some *markdown* content
109109
////
110110
///
111111

112+
## Custom Tag Handling
113+
114+
It is possible that in the future new tags could be added to the HTML spec, and it is also possible that the extension
115+
may not recognize such tags for some length of time. While such tags can be added in future releases, the HTML extension
116+
allows for users to specify such tags to navigate around such issues, or even make up their own tags.
117+
118+
To teach the HTML extension about unknown tags, simply use the `custom` [option](#global-options) to specify a tag name
119+
and the assumed default `mode`.
120+
121+
```py3
122+
import markdown
123+
from pymdownx.blocks.html import HTML
124+
extensions = ['pymdownx.blocks.html']
125+
extension_configs = {
126+
'pymdown.blocks.html': {
127+
'custom': [
128+
{'tag': 'sometag', 'mode': 'block'}
129+
]
130+
}
131+
}
132+
md = markdown.Markdown(extensions=extensions, extension_configs=extension_configs)
133+
```
134+
135+
Then you can use your custom tag knowing it will be handled properly.
136+
137+
```
138+
/// html | sometag
139+
140+
Some block content.
141+
142+
- List item A.
143+
144+
- List item B.
145+
///
146+
147+
```
148+
149+
## Global Options
150+
151+
Options | Type | Descriptions
152+
-------- | --------------- | ------------
153+
`custom` | \[dictionary\] | Specify custom tags with assumed default handling.
154+
112155
## Per Block Options
113156

114157
Options | Type | Descriptions

pymdownx/__meta__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,5 +185,5 @@ def parse_version(ver, pre=False):
185185
return Version(major, minor, micro, release, pre, post, dev)
186186

187187

188-
__version_info__ = Version(10, 13, 0, "final")
188+
__version_info__ = Version(10, 14, 0, "final")
189189
__version__ = __version_info__._get_canonical()

pymdownx/blocks/html.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
RE_ATTR = re.compile(fr'(?P<attr_name>{IDENTIFIER}){ATTR}', flags=re.I | re.X)
4040

4141
ATTRIBUTES = {'id': RE_ID, 'class': RE_CLASS, 'attr': RE_ATTRS}
42+
VALID_MODES = {'auto', 'inline', 'block', 'raw', 'html'}
4243

4344

4445
def parse_selectors(selector):
@@ -125,13 +126,17 @@ class HTML(Block):
125126
NAME = 'html'
126127
ARGUMENT = True
127128
OPTIONS = {
128-
'markdown': ['auto', type_string_in(['auto', 'inline', 'block', 'raw', 'html'])]
129+
'markdown': ['auto', type_string_in(VALID_MODES)]
129130
}
130131

131132
def __init__(self, length, tracker, md, config):
132133
"""Initialize."""
133134

134135
self.markdown = None
136+
self.custom = {}
137+
for entry in config.get('custom'):
138+
mode = entry.get('mode', 'auto')
139+
self.custom[entry['tag']] = mode if mode in VALID_MODES else 'auto'
135140
super().__init__(length, tracker, md, config)
136141

137142
def on_validate(self, parent):
@@ -148,6 +153,10 @@ def on_markdown(self):
148153
"""Check if this is atomic."""
149154

150155
mode = self.options['markdown']
156+
if mode == 'auto':
157+
tag = self.tag.lower()
158+
mode = self.custom.get(tag, mode)
159+
151160
if mode == 'html':
152161
mode = 'raw'
153162
return mode
@@ -167,6 +176,10 @@ def on_end(self, block):
167176
"""On end event."""
168177

169178
mode = self.options['markdown']
179+
if mode == 'auto':
180+
tag = self.tag.lower()
181+
mode = self.custom.get(tag, mode)
182+
170183
if (mode == 'auto' and self.is_html(block)) or mode == 'html':
171184
block.text = self.md.htmlStash.store(block.text)
172185
elif (mode == 'auto' and self.is_raw(block)) or mode == 'raw':
@@ -176,6 +189,18 @@ def on_end(self, block):
176189
class HTMLExtension(BlocksExtension):
177190
"""HTML Blocks Extension."""
178191

192+
def __init__(self, *args, **kwargs):
193+
"""Initialize."""
194+
195+
self.config = {
196+
"custom": [
197+
[],
198+
"Specify handling for custom blocks."
199+
]
200+
}
201+
202+
super().__init__(*args, **kwargs)
203+
179204
def extendMarkdownBlocks(self, md, block_mgr):
180205
"""Extend Markdown blocks."""
181206

tests/test_extensions/test_blocks/test_html.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ class TestBlocksHTML(util.MdCase):
66
"""Test Blocks HTML cases."""
77

88
extension = ['pymdownx.blocks.html', 'md_in_html']
9+
extension_configs = {
10+
'pymdownx.blocks.html': {
11+
'custom': [
12+
{'tag': 'custom', 'mode': 'block'}
13+
]
14+
}
15+
}
916

1017
def test_bad_tag(self):
1118
"""Test bad HTML tag."""
@@ -323,3 +330,38 @@ def test_html_and_script(self):
323330
''',
324331
True
325332
)
333+
334+
def test_custom(self):
335+
"""Test custom block handling."""
336+
337+
self.check_markdown(
338+
R'''
339+
/// html | custom
340+
- a
341+
- b
342+
///
343+
''',
344+
'''
345+
<custom><ul><li>a</li><li>b</li></ul></custom>
346+
''',
347+
True
348+
)
349+
350+
def test_custom_override(self):
351+
"""Test custom block handling but mode is overridden."""
352+
353+
self.check_markdown(
354+
R'''
355+
/// html | custom
356+
markdown: inline
357+
358+
- a
359+
- b
360+
///
361+
''',
362+
'''
363+
<custom>- a
364+
- b</custom>
365+
''',
366+
True
367+
)

0 commit comments

Comments
 (0)