diff --git a/markdown/core.py b/markdown/core.py index 370cb7ec..5b6c1eea 100644 --- a/markdown/core.py +++ b/markdown/core.py @@ -303,12 +303,15 @@ def is_block_level(self, tag: Any) -> bool: """ Check if the given `tag` is a block level HTML tag. - Returns `True` for any string listed in `Markdown.block_level_elements`. A `tag` which is - not a string always returns `False`. + Returns `True` for any string listed in `Markdown.block_level_elements`. + Also returns `True` for valid custom element names (tag names that contain + a hyphen; see https://html.spec.whatwg.org/#valid-custom-element-name). + A `tag` which is not a string always returns `False`. """ if isinstance(tag, str): - return tag.lower().rstrip('/') in self.block_level_elements + tag = tag.lower().rstrip('/') + return tag in self.block_level_elements or '-' in tag # Some ElementTree tags are not strings, so return False. return False diff --git a/markdown/extensions/md_in_html.py b/markdown/extensions/md_in_html.py index 32a9c11a..7c36790c 100644 --- a/markdown/extensions/md_in_html.py +++ b/markdown/extensions/md_in_html.py @@ -34,6 +34,17 @@ from markdown import Markdown +class _BlockLevelTagSet(set): + """A set of block-level tags that also treats custom element names as members. + + Custom elements are required to contain a hyphen in their names. + See https://html.spec.whatwg.org/#valid-custom-element-name. + """ + + def __contains__(self, item: object) -> bool: + return super().__contains__(item) or (isinstance(item, str) and '-' in item) + + class HTMLExtractorExtra(HTMLExtractor): """ Override `HTMLExtractor` and create `etree` `Elements` for any elements which should have content parsed as @@ -52,9 +63,13 @@ def __init__(self, md: Markdown, *args, **kwargs): super().__init__(md, *args, **kwargs) - # Block-level tags in which the content gets parsed as blocks - self.block_tags = set(self.block_level_tags) - (self.span_tags | self.raw_tags | self.empty_tags) - self.span_and_blocks_tags = self.block_tags | self.span_tags + # Block-level tags in which the content gets parsed as blocks. + # Custom element names (tags with a hyphen) are treated as block-level. + self.block_level_tags = _BlockLevelTagSet(self.block_level_tags) + self.block_tags = _BlockLevelTagSet( + set(self.block_level_tags) - (self.span_tags | self.raw_tags | self.empty_tags) + ) + self.span_and_blocks_tags = _BlockLevelTagSet(self.block_tags | self.span_tags) def reset(self): """Reset this instance. Loses all unprocessed data.""" diff --git a/tests/test_syntax/blocks/test_html_blocks.py b/tests/test_syntax/blocks/test_html_blocks.py index ecc10f45..b6745a1d 100644 --- a/tests/test_syntax/blocks/test_html_blocks.py +++ b/tests/test_syntax/blocks/test_html_blocks.py @@ -133,6 +133,30 @@ def test_raw_uppercase_multiline(self): ) ) + def test_raw_custom_element(self): + # Custom element names contain a hyphen and are treated as block-level. + # https://github.com/Python-Markdown/markdown/issues/1246 + self.assertMarkdownRenders( + self.dedent( + """ + + + asdf + + + """ + ), + self.dedent( + """ + + + asdf + + + """ + ) + ) + def test_multiple_raw_single_line(self): self.assertMarkdownRenders( '

*foo*

*bar*
', diff --git a/tests/test_syntax/extensions/test_md_in_html.py b/tests/test_syntax/extensions/test_md_in_html.py index 903766fc..20147a7d 100644 --- a/tests/test_syntax/extensions/test_md_in_html.py +++ b/tests/test_syntax/extensions/test_md_in_html.py @@ -1556,6 +1556,48 @@ def test_noname_tag(self): ) ) + def test_custom_element_markdown_attr(self): + # Custom element names contain a hyphen and are treated as block-level. + # https://github.com/Python-Markdown/markdown/issues/1246 + self.assertMarkdownRenders( + self.dedent( + """ + + + asdf + + + """ + ), + self.dedent( + """ + +

asdf

+
+ """ + ) + ) + + def test_custom_element_md1(self): + # https://github.com/Python-Markdown/markdown/issues/1246 + self.assertMarkdownRenders( + '*foo*', + self.dedent( + """ + +

foo

+
+ """ + ) + ) + + def test_custom_element_inline(self): + # Inline custom elements (not at the start of a block) remain span-level. + self.assertMarkdownRenders( + 'hello world', + '

hello world

' + ) + def load_tests(loader, tests, pattern): """ Ensure `TestHTMLBlocks` doesn't get run twice by excluding it here. """