diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 28cdaf6..e32c87d 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -240,7 +240,7 @@ def process_tag(self, node, parent_tags=None): # adjacent to the inner/outer boundaries of block elements. should_remove_inside = should_remove_whitespace_inside(node) - def _can_ignore(el): + def _can_ignore(el, previous_content, next_content): if isinstance(el, Tag): # Tags are always processed. return False @@ -252,10 +252,10 @@ def _can_ignore(el): if six.text_type(el).strip() != '': # Non-whitespace text nodes are always processed. return False - elif should_remove_inside and (not el.previous_sibling or not el.next_sibling): + elif should_remove_inside and (previous_content is None or next_content is None): # Inside block elements (excluding
), ignore adjacent whitespace elements.
return True
- elif should_remove_whitespace_outside(el.previous_sibling) or should_remove_whitespace_outside(el.next_sibling):
+ elif should_remove_whitespace_outside(previous_content) or should_remove_whitespace_outside(next_content):
# Outside block elements (including ), ignore adjacent whitespace elements.
return True
else:
@@ -265,7 +265,18 @@ def _can_ignore(el):
else:
raise ValueError('Unexpected element type: %s' % type(el))
- children_to_convert = [el for el in node.children if not _can_ignore(el)]
+ # Ignore comments and whitespace when locating block boundaries. Advance
+ # through content siblings once, rather than rescanning long comment runs.
+ content_siblings = (el for el in node.children if _is_block_content_element(el))
+ previous_content = None
+ next_content = next(content_siblings, None)
+ children_to_convert = []
+ for el in node.children:
+ if el is next_content:
+ previous_content = el
+ next_content = next(content_siblings, None)
+ if not _can_ignore(el, previous_content, next_content):
+ children_to_convert.append(el)
# Create a copy of this tag's parent context, then update it to include this tag
# to propagate down into the children.
diff --git a/tests/test_advanced.py b/tests/test_advanced.py
index 6123d8c..e6cd97c 100644
--- a/tests/test_advanced.py
+++ b/tests/test_advanced.py
@@ -1,3 +1,7 @@
+import pytest
+from bs4 import BeautifulSoup, Comment
+from markdownify import MarkdownConverter
+
from .utils import md
@@ -27,6 +31,50 @@ def test_ignore_comments_with_other_tags():
assert text == "[example link](http://example.com/)"
+@pytest.mark.parametrize('separator', ['\n', ' ', '\t'])
+@pytest.mark.parametrize('count', [1, 2, 4])
+def test_ignore_whitespace_between_comments_and_blocks(separator, count):
+ comments = separator + ('' + separator) * count
+ html = 'line 1
' + comments + 'line 2
'
+ assert MarkdownConverter().convert(html) == 'line 1\n\nline 2'
+
+
+def test_ignore_comment_whitespace_inside_block_boundaries():
+ html = ' \n text
\n'
+ assert md(html) == '\n\ntext\n\n'
+
+
+@pytest.mark.parametrize('html, expected', [
+ ('one two', 'one **two**'),
+ ('one a b two', 'one `a b` two'),
+])
+def test_comment_whitespace_between_inline_content(html, expected):
+ assert md(html) == expected
+
+
+@pytest.mark.parametrize('code', [False, True])
+def test_comment_whitespace_in_pre(code):
+ content = ' a\t\n\n\n b '
+ if code:
+ content = '' + content + ''
+ assert md('' + content + '
', strip_pre=None) == '\n\n```\n a\t\n\n\n b \n```\n\n'
+
+
+def test_comment_whitespace_conversion_preserves_soup():
+ soup = BeautifulSoup('one
\n\n\ntwo
', 'html.parser')
+ original = str(soup)
+ comments = soup.find_all(string=lambda node: isinstance(node, Comment))
+ positions = [(node.parent, node.previous_sibling, node.next_sibling) for node in comments]
+ converter = MarkdownConverter()
+ assert converter.convert_soup(soup) == 'one\n\ntwo'
+ assert converter.convert_soup(soup) == 'one\n\ntwo'
+ assert str(soup) == original
+ for node, (parent, previous, following) in zip(comments, positions):
+ assert node.parent is parent
+ assert node.previous_sibling is previous
+ assert node.next_sibling is following
+
+
def test_code_with_tricky_content():
assert md('>') == "`>`"
assert md('/home/username') == "`/home/`**username**"