diff --git a/README.rst b/README.rst index 059a68f..387c8da 100644 --- a/README.rst +++ b/README.rst @@ -154,7 +154,8 @@ strip_document Controls whether leading and/or trailing separation newlines are removed from the final converted document. Supported values are ``LSTRIP`` (leading), ``RSTRIP`` (trailing), ``STRIP`` (both), and ``None`` (neither). Newlines - within the document are unaffected. + within the document are unaffected. This also applies when ``convert_soup()`` + receives an individual BeautifulSoup ``Tag`` rather than a complete soup. Defaults to ``STRIP``. strip_pre diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 28cdaf6..363c1d1 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -223,7 +223,14 @@ def convert(self, html): return self.convert_soup(soup) def convert_soup(self, soup): - return self.process_tag(soup, parent_tags=set()) + text = self.process_tag(soup, parent_tags=set()) + # A supplied Tag is also a complete conversion input. BeautifulSoup's + # document node already runs this finalizer during process_tag(). + if soup.name != '[document]': + convert_fn = self.get_conv_fn_cached('[document]') + if convert_fn is not None: + text = convert_fn(soup, text, parent_tags={'[document]'}) + return text def process_element(self, node, parent_tags=None): if isinstance(node, NavigableString): diff --git a/tests/test_custom_converter.py b/tests/test_custom_converter.py index 00a83fc..976132b 100644 --- a/tests/test_custom_converter.py +++ b/tests/test_custom_converter.py @@ -42,3 +42,49 @@ def test_soup(): html = 'test' soup = BeautifulSoup(html, 'html.parser') assert MarkdownConverter().convert_soup(soup) == '**test**' + + +def test_tag_document_stripping(): + from markdownify import LSTRIP, RSTRIP, STRIP + + html = '

hello

world

' + for mode, expected in [(LSTRIP, 'hello\n\nworld\n\n'), + (RSTRIP, '\n\nhello\n\nworld'), + (STRIP, 'hello\n\nworld'), + (None, '\n\nhello\n\nworld\n\n')]: + for name in ['html', 'body', 'div']: + soup = BeautifulSoup(html, 'html.parser') + tag = soup.find(name) + before = str(soup) + assert MarkdownConverter(strip_document=mode).convert_soup(tag) == expected + assert str(soup) == before + assert tag.parent is not None + + +def test_tag_document_invalid_strip_mode(): + import pytest + + soup = BeautifulSoup('

hello

', 'html.parser') + with pytest.raises(ValueError, match='Invalid value for strip_document'): + MarkdownConverter(strip_document='invalid').convert_soup(soup.p) + + +def test_document_converter_called_once(): + class CountingConverter(MarkdownConverter): + calls = 0 + + def convert__document_(self, el, text, parent_tags): + self.calls += 1 + return super().convert__document_(el, text, parent_tags) + + soup = BeautifulSoup('

hello

', 'html.parser') + for root in [soup, soup.div, soup.p]: + converter = CountingConverter() + assert converter.convert_soup(root) == 'hello' + assert converter.calls == 1 + + +def test_tag_document_converter_exclusion(): + soup = BeautifulSoup('

hello

', 'html.parser') + for root in [soup, soup.p]: + assert MarkdownConverter(strip=['[document]']).convert_soup(root) == '\n\nhello\n\n'