Repository: Python-Markdown/markdown
Pull request #693 (2018-07-27 20:28:32)

 Move isBlockLevel to class.
Allows users and/or extensions to alter the list of block level elements. The old implementation remains with a DeprecationWarning. Fixes  #575.

The first commit implements the API change but leaves the old code in-place. All tests pass with DeprecationWarnings. The second commit applies the new API to the code, removing all of the warnings.



Patch:
diff --git a/markdown/core.py b/markdown/core.py
index 98c9252..f2bd2fb 100644
--- a/markdown/core.py
+++ b/markdown/core.py
@@ -52,6 +52,18 @@ class Markdown(object):
         'xhtml':  to_xhtml_string,
     }
 
+    block_level_elements = [
+        # Elements which are invalid to wrap in a `<p>` tag.
+        # See http://w3c.github.io/html/grouping-content.html#the-p-element
+        'address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl',
+        'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3',
+        'h4', 'h5', 'h6', 'header', 'hr', 'main', 'menu', 'nav', 'ol', 'p', 'pre',
+        'section', 'table', 'ul',
+        # Other elements which Markdown should not be mucking up the contents of.
+        'canvas', 'dd', 'dt', 'group', 'iframe', 'li', 'math', 'noscript', 'output',
+        'progress', 'script', 'style', 'tbody', 'td', 'th', 'thead', 'tr', 'video'
+    ]
+
     def __init__(self, **kwargs):
         """
         Creates a new Markdown instance.
@@ -207,6 +219,13 @@ class Markdown(object):
             raise
         return self
 
+    def is_block_level(self, tag):
+        """Check if the tag is a block level HTML tag."""
+        if isinstance(tag, util.string_type):
+            return tag.lower().rstrip('/') in self.block_level_elements
+        # Some ElementTree tags are not strings, so return False.
+        return False
+
     def convert(self, source):
         """
         Convert markdown to serialized XHTML or HTML.
diff --git a/markdown/extensions/attr_list.py b/markdown/extensions/attr_list.py
index 84b852d..ab7d6b9 100644
--- a/markdown/extensions/attr_list.py
+++ b/markdown/extensions/attr_list.py
@@ -21,7 +21,6 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from . import Extension
 from ..treeprocessors import Treeprocessor
-from ..util import isBlockLevel
 import re
 
 
@@ -79,7 +78,7 @@ class AttrListTreeprocessor(Treeprocessor):
 
     def run(self, doc):
         for elem in doc.iter():
-            if isBlockLevel(elem.tag):
+            if self.md.is_block_level(elem.tag):
                 # Block level: check for attrs on last line of text
                 RE = self.BLOCK_RE
                 if isheader(elem) or elem.tag == 'dt':
diff --git a/markdown/postprocessors.py b/markdown/postprocessors.py
index 541da5d..cecb4ad 100644
--- a/markdown/postprocessors.py
+++ b/markdown/postprocessors.py
@@ -91,7 +91,7 @@ class RawHtmlPostprocessor(Postprocessor):
             if m.group(1)[0] in ('!', '?', '@', '%'):
                 # Comment, php etc...
                 return True
-            return util.isBlockLevel(m.group(1))
+            return self.md.is_block_level(m.group(1))
         return False
 
 
diff --git a/markdown/preprocessors.py b/markdown/preprocessors.py
index 50537dd..89f4baa 100644
--- a/markdown/preprocessors.py
+++ b/markdown/preprocessors.py
@@ -235,11 +235,11 @@ class HtmlBlockPreprocessor(Preprocessor):
                                                                 block)
                     # keep checking conditions below and maybe just append
 
-                    if data_index < len(block) and (util.isBlockLevel(left_tag) or left_tag == '--'):
+                    if data_index < len(block) and (self.md.is_block_level(left_tag) or left_tag == '--'):
                         text.insert(0, block[data_index:])
                         block = block[:data_index]
 
-                    if not (util.isBlockLevel(left_tag) or block[1] in ["!", "?", "@", "%"]):
+                    if not (self.md.is_block_level(left_tag) or block[1] in ["!", "?", "@", "%"]):
                         new_blocks.append(block)
                         continue
 
@@ -261,7 +261,7 @@ class HtmlBlockPreprocessor(Preprocessor):
                     else:
                         # if is block level tag and is not complete
                         if (not self._equal_tags(left_tag, right_tag)) and \
-                           (util.isBlockLevel(left_tag) or left_tag == "--"):
+                           (self.md.is_block_level(left_tag) or left_tag == "--"):
                             items.append(block.strip())
                             in_tag = True
                         else:
diff --git a/markdown/treeprocessors.py b/markdown/treeprocessors.py
index 353826e..5c2be2d 100644
--- a/markdown/treeprocessors.py
+++ b/markdown/treeprocessors.py
@@ -406,12 +406,12 @@ class PrettifyTreeprocessor(Treeprocessor):
         """ Recursively add linebreaks to ElementTree children. """
 
         i = "\n"
-        if util.isBlockLevel(elem.tag) and elem.tag not in ['code', 'pre']:
+        if self.md.is_block_level(elem.tag) and elem.tag not in ['code', 'pre']:
             if (not elem.text or not elem.text.strip()) \
-                    and len(elem) and util.isBlockLevel(elem[0].tag):
+                    and len(elem) and self.md.is_block_level(elem[0].tag):
                 elem.text = i
             for e in elem:
-                if util.isBlockLevel(e.tag):
+                if self.md.is_block_level(e.tag):
                     self._prettifyETree(e)
             if not elem.tail or not elem.tail.strip():
                 elem.tail = i
diff --git a/markdown/util.py b/markdown/util.py
index b40c010..262521c 100644
--- a/markdown/util.py
+++ b/markdown/util.py
@@ -113,6 +113,26 @@ AUXILIARY GLOBAL FUNCTIONS
 """
 
 
+def deprecated(message):
+    """
+    Raise a DeprecationWarning when wrapped function/method is called.
+
+    Borrowed from https://stackoverflow.com/a/48632082/866026
+    """
+    def deprecated_decorator(func):
+        @wraps(func)
+        def deprecated_func(*args, **kwargs):
+            warnings.warn(
+                "'{}' is deprecated. {}".format(func.__name__, message),
+                category=DeprecationWarning,
+                stacklevel=2
+            )
+            return func(*args, **kwargs)
+        return deprecated_func
+    return deprecated_decorator
+
+
+@deprecated("Use 'Markdown.is_block_level' instead.")
 def isBlockLevel(tag):
     """Check if the tag is a block level HTML tag."""
     if isinstance(tag, string_type):
@@ -151,25 +171,6 @@ def code_escape(text):
     return text
 
 
-def deprecated(message):
-    """
-    Raise a DeprecationWarning when wrapped function/method is called.
-
-    Borrowed from https://stackoverflow.com/a/48632082/866026
-    """
-    def deprecated_decorator(func):
-        @wraps(func)
-        def deprecated_func(*args, **kwargs):
-            warnings.warn(
-                "'{}' is deprecated. {}".format(func.__name__, message),
-                category=DeprecationWarning,
-                stacklevel=2
-            )
-            return func(*args, **kwargs)
-        return deprecated_func
-    return deprecated_decorator
-
-
 """
 MISC AUXILIARY CLASSES
 =============================================================================


Test patch:
diff --git a/tests/test_apis.py b/tests/test_apis.py
index 834299b..71a8555 100644
--- a/tests/test_apis.py
+++ b/tests/test_apis.py
@@ -510,7 +510,8 @@ class testETreeComments(unittest.TestCase):
 
     def testCommentIsBlockLevel(self):
         """ Test that an ElementTree Comment is recognized as BlockLevel. """
-        self.assertFalse(markdown.util.isBlockLevel(self.comment.tag))
+        md = markdown.Markdown()
+        self.assertFalse(md.is_block_level(self.comment.tag))
 
     def testCommentSerialization(self):
         """ Test that an ElementTree Comment serializes properly. """
@@ -521,7 +522,7 @@ class testETreeComments(unittest.TestCase):
 
     def testCommentPrettify(self):
         """ Test that an ElementTree Comment is prettified properly. """
-        pretty = markdown.treeprocessors.PrettifyTreeprocessor()
+        pretty = markdown.treeprocessors.PrettifyTreeprocessor(markdown.Markdown())
         pretty.run(self.comment)
         self.assertEqual(
             markdown.serializers.to_html_string(self.comment),
@@ -532,7 +533,7 @@ class testETreeComments(unittest.TestCase):
 class testElementTailTests(unittest.TestCase):
     """ Element Tail Tests """
     def setUp(self):
-        self.pretty = markdown.treeprocessors.PrettifyTreeprocessor()
+        self.pretty = markdown.treeprocessors.PrettifyTreeprocessor(markdown.Markdown())
 
     def testBrTailNoNewline(self):
         """ Test that last <br> in tree has a new line tail """