Repository: matthewwithanm/python-markdownify
Pull request #37 (2021-02-15 15:45:17)

Add `strong_em_symbol` and `newline` options to the converter
Hi,

Thanks for this great package! I like it a lot, though two options I would have liked to see are the following:
1. Both symbols `*` and `_` can be used in the same manner to encode bold and italics in markdown. While they are equivalent, I thought it would be nice to be able to switch between them (this converter only uses `*`). I have added the option `strong_em_symbol` to pass the prefered symbol (defaults to `*`)
2. While `   \n` (two spaces and a newline) is the standard for encoding a line break in markdown, it not always preferred, both because it is not very visible and because many editors trim whitespaces at the end of a line. A popular alternative is to use `\\n` (a backslash and a newline), which also many interpreters support. I have added the option `newline` to the converter, which can switch to this backslash convention by setting it to `'backslash'`.

This PR achieves these things, and they have been documented in the README.


Patch:
diff --git a/README.rst b/README.rst
index 4d21411..1e245c1 100644
--- a/README.rst
+++ b/README.rst
@@ -75,6 +75,18 @@ bullets
   lists are nested. Otherwise, the bullet will alternate based on nesting
   level. Defaults to ``'*+-'``.
 
+strong_em_symbol
+  In markdown, both ``*`` and ``_`` are used to encode **strong** or
+  *emphasized* texts. Either of these symbols can be chosen by the options
+  ``ASTERISK`` (default) or ``UNDERSCORE`` respectively.
+
+newline_style
+  Defines the style of marking linebreaks (``<br>``) in markdown. The default
+  value ``SPACES`` of this option will adopt the usual two spaces and a newline,
+  while ``BACKSLASH`` will convert a linebreak to ``\\n`` (a backslash an a
+  newline). While the latter convention is non-standard, it is commonly
+  preferred and supported by a lot of interpreters.
+
 Options may be specified as kwargs to the ``markdownify`` function, or as a
 nested ``Options`` class in ``MarkdownConverter`` subclasses.
 
diff --git a/markdownify/__init__.py b/markdownify/__init__.py
index 5c008d3..6f90d73 100644
--- a/markdownify/__init__.py
+++ b/markdownify/__init__.py
@@ -15,6 +15,14 @@ ATX_CLOSED = 'atx_closed'
 UNDERLINED = 'underlined'
 SETEXT = UNDERLINED
 
+# Newline style
+SPACES = 'spaces'
+BACKSLASH = 'backslash'
+
+# Strong and emphasis style
+ASTERISK = '*'
+UNDERSCORE = '_'
+
 
 def escape(text):
     if not text:
@@ -46,6 +54,8 @@ class MarkdownConverter(object):
         autolinks = True
         heading_style = UNDERLINED
         bullets = '*+-'  # An iterable of bullet types.
+        strong_em_symbol = ASTERISK
+        newline_style = SPACES
 
     class Options(DefaultOptions):
         pass
@@ -154,19 +164,23 @@ class MarkdownConverter(object):
         if convert_as_inline:
             return ""
 
-        return '  \n'
+        if self.options['newline_style'].lower() == BACKSLASH:
+            return '\\\n'
+        else:
+            return '  \n'
 
     def convert_em(self, el, text, convert_as_inline):
+        em_tag = self.options['strong_em_symbol']
         prefix, suffix, text = chomp(text)
         if not text:
             return ''
-        return '%s*%s*%s' % (prefix, text, suffix)
+        return '%s%s%s%s%s' % (prefix, em_tag, text, em_tag, suffix)
 
     def convert_hn(self, n, el, text, convert_as_inline):
         if convert_as_inline:
             return text
 
-        style = self.options['heading_style']
+        style = self.options['heading_style'].lower()
         text = text.rstrip()
         if style == UNDERLINED and n <= 2:
             line = '=' if n == 1 else '-'
@@ -222,10 +236,11 @@ class MarkdownConverter(object):
         return '%s\n\n' % text if text else ''
 
     def convert_strong(self, el, text, convert_as_inline):
+        strong_tag = 2 * self.options['strong_em_symbol']
         prefix, suffix, text = chomp(text)
         if not text:
             return ''
-        return '%s**%s**%s' % (prefix, text, suffix)
+        return '%s%s%s%s%s' % (prefix, strong_tag, text, strong_tag, suffix)
 
     def convert_img(self, el, text, convert_as_inline):
         alt = el.attrs.get('alt', None) or ''


Test patch:
diff --git a/tests/test_conversions.py b/tests/test_conversions.py
index edaefbc..e974c78 100644
--- a/tests/test_conversions.py
+++ b/tests/test_conversions.py
@@ -1,4 +1,4 @@
-from markdownify import markdownify as md, ATX, ATX_CLOSED
+from markdownify import markdownify as md, ATX, ATX_CLOSED, BACKSLASH, UNDERSCORE
 import re
 
 
@@ -216,3 +216,14 @@ def test_img():
 
 def test_div():
     assert md('Hello</div> World') == 'Hello World'
+
+
+def test_strong_em_symbol():
+    assert md('<strong>Hello</strong>', strong_em_symbol=UNDERSCORE) == '__Hello__'
+    assert md('<b>Hello</b>', strong_em_symbol=UNDERSCORE) == '__Hello__'
+    assert md('<em>Hello</em>', strong_em_symbol=UNDERSCORE) == '_Hello_'
+    assert md('<i>Hello</i>', strong_em_symbol=UNDERSCORE) == '_Hello_'
+
+
+def test_newline_style():
+    assert md('a<br />b<br />c', newline_style=BACKSLASH) == 'a\\\nb\\\nc'