Repository: Python-Markdown/markdown (python)

Issue:
Bug related to Admontion and List Interaction 
Source:

```py
import markdown

md = """
- List

    !!! note "Admontion"

        - Paragraph

            Paragraph
"""

print(markdown.markdown(md, extensions=['admonition']))
```

Output:

```html
<ul>
<li>
<p>List</p>
<div class="admonition note">
<p class="admonition-title">Admontion</p>
<ul>
<li>Paragraph</li>
</ul>
<pre><code>Paragraph
</code></pre>
</div>
</li>
</ul>
```

Somehow, things get out of sync. Second paragraph under admonition should not be transformed into code.

Pull request description:
Fix complex scenarios with lists and admonitions
Add better logic to admonitions to account for more complex list cases

Fixes #1004

Patch:
diff --git a/docs/change_log/release-3.3.md b/docs/change_log/release-3.3.md
index dfbb384..eb7fe7c 100644
--- a/docs/change_log/release-3.3.md
+++ b/docs/change_log/release-3.3.md
@@ -75,6 +75,7 @@ The following bug fixes are included in the 3.3 release:
 * Avoid a `RecursionError` from deeply nested blockquotes (#799).
 * Fix issues with complex emphasis (#979).
 * Fix unescaping of HTML characters `<>` in CodeHilite (#990).
+* Fix complex scenarios involving lists and admonitions (#1004)
 
 [spec]: https://www.w3.org/TR/html5/text-level-semantics.html#the-code-element
 [fenced_code]: ../extensions/fenced_code_blocks.md
diff --git a/markdown/extensions/admonition.py b/markdown/extensions/admonition.py
index 3926628..01f9940 100644
--- a/markdown/extensions/admonition.py
+++ b/markdown/extensions/admonition.py
@@ -40,19 +40,82 @@ class AdmonitionProcessor(BlockProcessor):
     RE = re.compile(r'(?:^|\n)!!! ?([\w\-]+(?: +[\w\-]+)*)(?: +"(.*?)")? *(?:\n|$)')
     RE_SPACES = re.compile('  +')
 
-    def test(self, parent, block):
+    def __init__(self, parser):
+        """Initialization."""
+
+        super().__init__(parser)
+
+        self.current_sibling = None
+        self.content_indention = 0
+
+    def get_sibling(self, parent, block):
+        """Get sibling admontion.
+
+        Retrieve the appropriate siblimg element. This can get trickly when
+        dealing with lists.
+
+        """
+
+        # We already acquired the block via test
+        if self.current_sibling is not None:
+            sibling = self.current_sibling
+            block = block[self.content_indent:]
+            self.current_sibling = None
+            self.content_indent = 0
+            return sibling, block
+
         sibling = self.lastChild(parent)
-        return self.RE.search(block) or \
-            (block.startswith(' ' * self.tab_length) and sibling is not None and
-             sibling.get('class', '').find(self.CLASSNAME) != -1)
+
+        if sibling is None or sibling.get('class', '').find(self.CLASSNAME) == -1:
+            sibling = None
+        else:
+            # If the last child is a list and the content is idented sufficient
+            # to be under it, then the content's is sibling is in the list.
+            last_child = self.lastChild(sibling)
+            indent = 0
+            while last_child:
+                if (
+                    sibling and block.startswith(' ' * self.tab_length * 2) and
+                    last_child and last_child.tag in ('ul', 'ol', 'dl')
+                ):
+
+                    # The expectation is that we'll find an <li> or <dt>.
+                    # We should get it's last child as well.
+                    sibling = self.lastChild(last_child)
+                    last_child = self.lastChild(sibling) if sibling else None
+
+                    # Context has been lost at this point, so we must adjust the
+                    # text's identation level so it will be evaluated correctly
+                    # under the list.
+                    block = block[self.tab_length:]
+                    indent += self.tab_length
+                else:
+                    last_child = None
+
+            if not block.startswith(' ' * self.tab_length):
+                sibling = None
+
+            if sibling is not None:
+                self.current_sibling = sibling
+                self.content_indent = indent
+
+        return sibling, block
+
+    def test(self, parent, block):
+
+        if self.RE.search(block):
+            return True
+        else:
+            return self.get_sibling(parent, block)[0] is not None
 
     def run(self, parent, blocks):
-        sibling = self.lastChild(parent)
         block = blocks.pop(0)
         m = self.RE.search(block)
 
         if m:
             block = block[m.end():]  # removes the first line
+        else:
+            sibling, block = self.get_sibling(parent, block)
 
         block, theRest = self.detab(block)
 
@@ -65,6 +128,13 @@ class AdmonitionProcessor(BlockProcessor):
                 p.text = title
                 p.set('class', self.CLASSNAME_TITLE)
         else:
+            # Sibling is a list item, but we need to wrap it's content should be wrapped in <p>
+            if sibling.tag in ('li', 'dd') and sibling.text:
+                text = sibling.text
+                sibling.text = ''
+                p = etree.SubElement(sibling, 'p')
+                p.text = text
+
             div = sibling
 
         self.parser.parseChunk(div, block)


Test patch:
diff --git a/tests/test_syntax/extensions/test_admonition.py b/tests/test_syntax/extensions/test_admonition.py
new file mode 100644
index 0000000..39be997
--- /dev/null
+++ b/tests/test_syntax/extensions/test_admonition.py
@@ -0,0 +1,194 @@
+"""
+Python Markdown
+
+A Python implementation of John Gruber's Markdown.
+
+Documentation: https://python-markdown.github.io/
+GitHub: https://github.com/Python-Markdown/markdown/
+PyPI: https://pypi.org/project/Markdown/
+
+Started by Manfred Stienstra (http://www.dwerg.net/).
+Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
+Currently maintained by Waylan Limberg (https://github.com/waylan),
+Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
+
+Copyright 2007-2019 The Python Markdown Project (v. 1.7 and later)
+Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
+Copyright 2004 Manfred Stienstra (the original version)
+
+License: BSD (see LICENSE.md for details).
+"""
+
+from markdown.test_tools import TestCase
+
+
+class TestAdmonition(TestCase):
+
+    def test_with_lists(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                '''
+                - List
+
+                    !!! note "Admontion"
+
+                        - Paragraph
+
+                            Paragraph
+                '''
+            ),
+            self.dedent(
+                '''
+                <ul>
+                <li>
+                <p>List</p>
+                <div class="admonition note">
+                <p class="admonition-title">Admontion</p>
+                <ul>
+                <li>
+                <p>Paragraph</p>
+                <p>Paragraph</p>
+                </li>
+                </ul>
+                </div>
+                </li>
+                </ul>
+                '''
+            ),
+            extensions=['admonition']
+        )
+
+    def test_with_big_lists(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                '''
+                - List
+
+                    !!! note "Admontion"
+
+                        - Paragraph
+
+                            Paragraph
+
+                        - Paragraph
+
+                            paragraph
+                '''
+            ),
+            self.dedent(
+                '''
+                <ul>
+                <li>
+                <p>List</p>
+                <div class="admonition note">
+                <p class="admonition-title">Admontion</p>
+                <ul>
+                <li>
+                <p>Paragraph</p>
+                <p>Paragraph</p>
+                </li>
+                <li>
+                <p>Paragraph</p>
+                <p>paragraph</p>
+                </li>
+                </ul>
+                </div>
+                </li>
+                </ul>
+                '''
+            ),
+            extensions=['admonition']
+        )
+
+    def test_with_complex_lists(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                '''
+                - List
+
+                    !!! note "Admontion"
+
+                        - Paragraph
+
+                            !!! note "Admontion"
+
+                                1. Paragraph
+
+                                    Paragraph
+                '''
+            ),
+            self.dedent(
+                '''
+                <ul>
+                <li>
+                <p>List</p>
+                <div class="admonition note">
+                <p class="admonition-title">Admontion</p>
+                <ul>
+                <li>
+                <p>Paragraph</p>
+                <div class="admonition note">
+                <p class="admonition-title">Admontion</p>
+                <ol>
+                <li>
+                <p>Paragraph</p>
+                <p>Paragraph</p>
+                </li>
+                </ol>
+                </div>
+                </li>
+                </ul>
+                </div>
+                </li>
+                </ul>
+                '''
+            ),
+            extensions=['admonition']
+        )
+
+    def test_definition_list(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                '''
+                - List
+
+                    !!! note "Admontion"
+
+                        Term
+
+                        :   Definition
+
+                            More text
+
+                        :   Another
+                            definition
+
+                            Even more text
+                '''
+            ),
+            self.dedent(
+                '''
+                <ul>
+                <li>
+                <p>List</p>
+                <div class="admonition note">
+                <p class="admonition-title">Admontion</p>
+                <dl>
+                <dt>Term</dt>
+                <dd>
+                <p>Definition</p>
+                <p>More text</p>
+                </dd>
+                <dd>
+                <p>Another
+                definition</p>
+                <p>Even more text</p>
+                </dd>
+                </dl>
+                </div>
+                </li>
+                </ul>
+                '''
+            ),
+            extensions=['admonition', 'def_list']
+        )