Repository: matthewwithanm/python-markdownify
Pull request #76 (2022-09-04 15:09:43)

Strip text before adding blockquote markers
Hello!

@johannesdewit and I noticed there are quite a few cases where converted blockquotes generate redundant `>` markers, for example when newlines are inside the blockquote:

```pycon
In [0]: print(md("<blockquote>\nHello\n</blockquote>"))

> 
> Hello
> 
```

But also when nesting a paragraph inside a blockquote tag:

```pycon
In [1]: print(md("<blockquote><p>Hello</p></blockquote>"))

> Hello
> 
> 
```

And finally, nesting blockquotes:

```pycon
In [2]: print(md("<blockquote>And she was like <blockquote>Hello</blockquote></blockquote>"))

> And she was like 
> > Hello
> 
> 
```

If we apply a simple `.strip()` on the text before adding the blockquote markers, we can avoid these all. I have done that in this PR, and it produces these results:

```pycon
In [3]: print(md("<blockquote>\nHello\n</blockquote>"))

> Hello

In [4]: print(md("<blockquote><p>Hello</p></blockquote>"))

> Hello

In [5]: print(md("<blockquote>And she was like <blockquote>Hello</blockquote></blockquote>"))

> And she was like 
> > Hello
```

Also note that it still preserves blockquote markers between paragraphs, e.g.:

```pycon
In [6]: print(md("<blockquote><p>Hello</p><p>Hello again</p></blockquote>"))

> Hello
> 
> Hello again
```

This PR also incorporates these examples in the tests.

clach04: :+1: lgtm

Patch:
diff --git a/README.rst b/README.rst
index 06f319b..21a5972 100644
--- a/README.rst
+++ b/README.rst
@@ -173,6 +173,22 @@ change:
     def md(html, **options):
         return ImageBlockConverter(**options).convert(html)
 
+.. code:: python
+
+    from markdownify import MarkdownConverter
+
+    class NoCssConverter(MarkdownConverter):
+        """
+        Create a custom MarkdownConverter that removes the CSS code by ignoring the `style` tag
+        """
+        def convert_style(self, el, text, convert_as_inline):
+            return ''
+
+    # Create shorthand method for conversion
+    def md(html, **options):
+        return NoCssConverter(**options).convert(html)
+
+
 
 Command Line Interface
 ======================
diff --git a/markdownify/__init__.py b/markdownify/__init__.py
index e15ecd4..1e7b787 100644
--- a/markdownify/__init__.py
+++ b/markdownify/__init__.py
@@ -152,13 +152,12 @@ class MarkdownConverter(object):
     def process_text(self, el):
         text = six.text_type(el) or ''
 
-        # dont remove any whitespace when handling pre or code in pre
-        if not (el.parent.name == 'pre'
-                or (el.parent.name == 'code'
-                    and el.parent.parent.name == 'pre')):
+        # normalize whitespace if we're not inside a preformatted element
+        if not el.find_parent('pre'):
             text = whitespace_re.sub(' ', text)
 
-        if el.parent.name != 'code' and el.parent.name != 'pre':
+        # escape special characters if we're not inside a preformatted or code element
+        if not el.find_parent(['pre', 'code', 'kbd', 'samp']):
             text = self.escape(text)
 
         # remove trailing whitespaces if any of the following condition is true:
@@ -238,7 +237,7 @@ class MarkdownConverter(object):
         if convert_as_inline:
             return text
 
-        return '\n' + (line_beginning_re.sub('> ', text) + '\n\n') if text else ''
+        return '\n' + (line_beginning_re.sub('> ', text.strip()) + '\n\n') if text else ''
 
     def convert_br(self, el, text, convert_as_inline):
         if convert_as_inline:
@@ -266,7 +265,7 @@ class MarkdownConverter(object):
             return text
 
         style = self.options['heading_style'].lower()
-        text = text.rstrip()
+        text = text.strip()
         if style == UNDERLINED and n <= 2:
             line = '=' if n == 1 else '-'
             return self.underline(text, line)
@@ -351,6 +350,12 @@ class MarkdownConverter(object):
 
         return '\n```%s\n%s\n```\n' % (code_language, text)
 
+    def convert_script(self, el, text, convert_as_inline):
+        return ''
+
+    def convert_style(self, el, text, convert_as_inline):
+        return ''
+
     convert_s = convert_del
 
     convert_strong = convert_b
@@ -365,7 +370,7 @@ class MarkdownConverter(object):
         return '\n\n' + text + '\n'
 
     def convert_td(self, el, text, convert_as_inline):
-        return ' ' + text + ' |'
+        return ' ' + text.strip().replace("\n", " ") + ' |'
 
     def convert_th(self, el, text, convert_as_inline):
         return ' ' + text + ' |'


Test patch:
diff --git a/tests/test_conversions.py b/tests/test_conversions.py
index da78649..ae56837 100644
--- a/tests/test_conversions.py
+++ b/tests/test_conversions.py
@@ -52,6 +52,12 @@ def test_b_spaces():
 
 def test_blockquote():
     assert md('<blockquote>Hello</blockquote>') == '\n> Hello\n\n'
+    assert md('<blockquote>\nHello\n</blockquote>') == '\n> Hello\n\n'
+
+
+def test_blockquote_with_nested_paragraph():
+    assert md('<blockquote><p>Hello</p></blockquote>') == '\n> Hello\n\n'
+    assert md('<blockquote><p>Hello</p><p>Hello again</p></blockquote>') == '\n> Hello\n> \n> Hello again\n\n'
 
 
 def test_blockquote_with_paragraph():
@@ -60,7 +66,7 @@ def test_blockquote_with_paragraph():
 
 def test_blockquote_nested():
     text = md('<blockquote>And she was like <blockquote>Hello</blockquote></blockquote>')
-    assert text == '\n> And she was like \n> > Hello\n> \n> \n\n'
+    assert text == '\n> And she was like \n> > Hello\n\n'
 
 
 def test_br():
@@ -70,7 +76,12 @@ def test_br():
 
 def test_code():
     inline_tests('code', '`')
-    assert md('<code>this_should_not_escape</code>') == '`this_should_not_escape`'
+    assert md('<code>*this_should_not_escape*</code>') == '`*this_should_not_escape*`'
+    assert md('<kbd>*this_should_not_escape*</kbd>') == '`*this_should_not_escape*`'
+    assert md('<samp>*this_should_not_escape*</samp>') == '`*this_should_not_escape*`'
+    assert md('<code><span>*this_should_not_escape*</span></code>') == '`*this_should_not_escape*`'
+    assert md('<code>this  should\t\tnormalize</code>') == '`this should normalize`'
+    assert md('<code><span>this  should\t\tnormalize</span></code>') == '`this should normalize`'
 
 
 def test_del():
@@ -85,6 +96,14 @@ def test_em():
     inline_tests('em', '*')
 
 
+def test_header_with_space():
+    assert md('<h3>\n\nHello</h3>') == '### Hello\n\n'
+    assert md('<h4>\n\nHello</h4>') == '#### Hello\n\n'
+    assert md('<h5>\n\nHello</h5>') == '##### Hello\n\n'
+    assert md('<h5>\n\nHello\n\n</h5>') == '##### Hello\n\n'
+    assert md('<h5>\n\nHello   \n\n</h5>') == '##### Hello\n\n'
+
+
 def test_h1():
     assert md('<h1>Hello</h1>') == 'Hello\n=====\n\n'
 
@@ -187,7 +206,18 @@ def test_p():
 def test_pre():
     assert md('<pre>test\n    foo\nbar</pre>') == '\n```\ntest\n    foo\nbar\n```\n'
     assert md('<pre><code>test\n    foo\nbar</code></pre>') == '\n```\ntest\n    foo\nbar\n```\n'
-    assert md('<pre>this_should_not_escape</pre>') == '\n```\nthis_should_not_escape\n```\n'
+    assert md('<pre>*this_should_not_escape*</pre>') == '\n```\n*this_should_not_escape*\n```\n'
+    assert md('<pre><span>*this_should_not_escape*</span></pre>') == '\n```\n*this_should_not_escape*\n```\n'
+    assert md('<pre>\t\tthis  should\t\tnot  normalize</pre>') == '\n```\n\t\tthis  should\t\tnot  normalize\n```\n'
+    assert md('<pre><span>\t\tthis  should\t\tnot  normalize</span></pre>') == '\n```\n\t\tthis  should\t\tnot  normalize\n```\n'
+
+
+def test_script():
+    assert md('foo <script>var foo=42;</script> bar') == 'foo  bar'
+
+
+def test_style():
+    assert md('foo <style>h1 { font-size: larger }</style> bar') == 'foo  bar'
 
 
 def test_s():
diff --git a/tests/test_tables.py b/tests/test_tables.py
index e0c07ea..334bfb7 100644
--- a/tests/test_tables.py
+++ b/tests/test_tables.py
@@ -57,6 +57,26 @@ table_with_paragraphs = """<table>
     </tr>
 </table>"""
 
+table_with_linebreaks = """<table>
+    <tr>
+        <th>Firstname</th>
+        <th>Lastname</th>
+        <th>Age</th>
+    </tr>
+    <tr>
+        <td>Jill</td>
+        <td>Smith
+        Jackson</td>
+        <td>50</td>
+    </tr>
+    <tr>
+        <td>Eve</td>
+        <td>Jackson
+        Smith</td>
+        <td>94</td>
+    </tr>
+</table>"""
+
 
 table_with_header_column = """<table>
     <tr>
@@ -164,6 +184,7 @@ def test_table():
     assert md(table) == '\n\n| Firstname | Lastname | Age |\n| --- | --- | --- |\n| Jill | Smith | 50 |\n| Eve | Jackson | 94 |\n\n'
     assert md(table_with_html_content) == '\n\n| Firstname | Lastname | Age |\n| --- | --- | --- |\n| **Jill** | *Smith* | [50](#) |\n| Eve | Jackson | 94 |\n\n'
     assert md(table_with_paragraphs) == '\n\n| Firstname | Lastname | Age |\n| --- | --- | --- |\n| Jill | Smith | 50 |\n| Eve | Jackson | 94 |\n\n'
+    assert md(table_with_linebreaks) == '\n\n| Firstname | Lastname | Age |\n| --- | --- | --- |\n| Jill | Smith  Jackson | 50 |\n| Eve | Jackson  Smith | 94 |\n\n'
     assert md(table_with_header_column) == '\n\n| Firstname | Lastname | Age |\n| --- | --- | --- |\n| Jill | Smith | 50 |\n| Eve | Jackson | 94 |\n\n'
     assert md(table_head_body) == '\n\n| Firstname | Lastname | Age |\n| --- | --- | --- |\n| Jill | Smith | 50 |\n| Eve | Jackson | 94 |\n\n'
     assert md(table_missing_text) == '\n\n|  | Lastname | Age |\n| --- | --- | --- |\n| Jill |  | 50 |\n| Eve | Jackson | 94 |\n\n'