Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions markdownify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <pre>), 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 <pre>), ignore adjacent whitespace elements.
return True
else:
Expand All @@ -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.
Expand Down
48 changes: 48 additions & 0 deletions tests/test_advanced.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import pytest
from bs4 import BeautifulSoup, Comment
from markdownify import MarkdownConverter

from .utils import md


Expand Down Expand Up @@ -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 + ('<!-- comment -->' + separator) * count
html = '<p>line 1</p>' + comments + '<p>line 2</p>'
assert MarkdownConverter().convert(html) == 'line 1\n\nline 2'


def test_ignore_comment_whitespace_inside_block_boundaries():
html = '<div><!-- a --> \n<!-- b --> <p>text</p> <!-- c --> \n<!-- d --></div>'
assert md(html) == '\n\ntext\n\n'


@pytest.mark.parametrize('html, expected', [
('one <!-- comment --> <b>two</b>', 'one **two**'),
('one<code> a <!-- comment --> b </code>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<!-- comment -->\n\n b '
if code:
content = '<code>' + content + '</code>'
assert md('<pre>' + content + '</pre>', strip_pre=None) == '\n\n```\n a\t\n\n\n b \n```\n\n'


def test_comment_whitespace_conversion_preserves_soup():
soup = BeautifulSoup('<p>one</p>\n<!-- a -->\n<!-- b -->\n<p>two</p>', '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('<code>></code>') == "`>`"
assert md('<code>/home/</code><b>username</b>') == "`/home/`**username**"
Expand Down