Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
652714859d | ||
|
|
9cb940cbc0 | ||
|
|
70ef9b6e48 | ||
|
|
91d53ddd5a | ||
|
|
079f32f6cd | ||
|
|
89b577e91e | ||
|
|
4bf2ea44fc | ||
|
|
77797ebb79 | ||
|
|
7685738344 | ||
|
|
92a73c8dfe | ||
|
|
3354f143d8 |
@@ -44,6 +44,22 @@ def chomp(text):
|
|||||||
return (prefix, suffix, text)
|
return (prefix, suffix, text)
|
||||||
|
|
||||||
|
|
||||||
|
def abstract_inline_conversion(markup_fn):
|
||||||
|
"""
|
||||||
|
This abstracts all simple inline tags like b, em, del, ...
|
||||||
|
Returns a function that wraps the chomped text in a pair of the string
|
||||||
|
that is returned by markup_fn. markup_fn is necessary to allow for
|
||||||
|
references to self.strong_em_symbol etc.
|
||||||
|
"""
|
||||||
|
def implementation(self, el, text, convert_as_inline):
|
||||||
|
markup = markup_fn(self)
|
||||||
|
prefix, suffix, text = chomp(text)
|
||||||
|
if not text:
|
||||||
|
return ''
|
||||||
|
return '%s%s%s%s%s' % (prefix, markup, text, markup, suffix)
|
||||||
|
return implementation
|
||||||
|
|
||||||
|
|
||||||
def _todict(obj):
|
def _todict(obj):
|
||||||
return dict((k, getattr(obj, k)) for k in dir(obj) if not k.startswith('_'))
|
return dict((k, getattr(obj, k)) for k in dir(obj) if not k.startswith('_'))
|
||||||
|
|
||||||
@@ -124,12 +140,21 @@ class MarkdownConverter(object):
|
|||||||
|
|
||||||
def process_text(self, el):
|
def process_text(self, el):
|
||||||
text = six.text_type(el)
|
text = six.text_type(el)
|
||||||
|
|
||||||
|
# dont remove any whitespace when handling pre or code in pre
|
||||||
|
if (el.parent.name == 'pre'
|
||||||
|
or (el.parent.name == 'code' and el.parent.parent.name == 'pre')):
|
||||||
|
return escape(text or '')
|
||||||
|
|
||||||
|
cleaned_text = escape(whitespace_re.sub(' ', text or ''))
|
||||||
|
|
||||||
# remove trailing whitespaces if any of the following condition is true:
|
# remove trailing whitespaces if any of the following condition is true:
|
||||||
# - current text node is the last node in li
|
# - current text node is the last node in li
|
||||||
# - current text node is followed by an embedded list
|
# - current text node is followed by an embedded list
|
||||||
if el.parent.name == 'li' and (not el.next_sibling or el.next_sibling.name in ['ul', 'ol']):
|
if el.parent.name == 'li' and (not el.next_sibling or el.next_sibling.name in ['ul', 'ol']):
|
||||||
return escape(all_whitespace_re.sub(' ', text or '')).rstrip()
|
return cleaned_text.rstrip()
|
||||||
return escape(whitespace_re.sub(' ', text or ''))
|
|
||||||
|
return cleaned_text
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
# Handle headings
|
# Handle headings
|
||||||
@@ -179,8 +204,7 @@ class MarkdownConverter(object):
|
|||||||
title_part = ' "%s"' % title.replace('"', r'\"') if title else ''
|
title_part = ' "%s"' % title.replace('"', r'\"') if title else ''
|
||||||
return '%s[%s](%s%s)%s' % (prefix, text, href, title_part, suffix) if href else text
|
return '%s[%s](%s%s)%s' % (prefix, text, href, title_part, suffix) if href else text
|
||||||
|
|
||||||
def convert_b(self, el, text, convert_as_inline):
|
convert_b = abstract_inline_conversion(lambda self: 2 * self.options['strong_em_symbol'])
|
||||||
return self.convert_strong(el, text, convert_as_inline)
|
|
||||||
|
|
||||||
def convert_blockquote(self, el, text, convert_as_inline):
|
def convert_blockquote(self, el, text, convert_as_inline):
|
||||||
|
|
||||||
@@ -198,12 +222,17 @@ class MarkdownConverter(object):
|
|||||||
else:
|
else:
|
||||||
return ' \n'
|
return ' \n'
|
||||||
|
|
||||||
def convert_em(self, el, text, convert_as_inline):
|
def convert_code(self, el, text, convert_as_inline):
|
||||||
em_tag = self.options['strong_em_symbol']
|
if el.parent.name == 'pre':
|
||||||
prefix, suffix, text = chomp(text)
|
return text
|
||||||
if not text:
|
converter = abstract_inline_conversion(lambda self: '`')
|
||||||
return ''
|
return converter(self, el, text, convert_as_inline)
|
||||||
return '%s%s%s%s%s' % (prefix, em_tag, text, em_tag, suffix)
|
|
||||||
|
convert_del = abstract_inline_conversion(lambda self: '~~')
|
||||||
|
|
||||||
|
convert_em = abstract_inline_conversion(lambda self: self.options['strong_em_symbol'])
|
||||||
|
|
||||||
|
convert_kbd = convert_code
|
||||||
|
|
||||||
def convert_hn(self, n, el, text, convert_as_inline):
|
def convert_hn(self, n, el, text, convert_as_inline):
|
||||||
if convert_as_inline:
|
if convert_as_inline:
|
||||||
@@ -219,8 +248,20 @@ class MarkdownConverter(object):
|
|||||||
return '%s %s %s\n\n' % (hashes, text, hashes)
|
return '%s %s %s\n\n' % (hashes, text, hashes)
|
||||||
return '%s %s\n\n' % (hashes, text)
|
return '%s %s\n\n' % (hashes, text)
|
||||||
|
|
||||||
def convert_i(self, el, text, convert_as_inline):
|
def convert_hr(self, el, text, convert_as_inline):
|
||||||
return self.convert_em(el, text, convert_as_inline)
|
return '\n\n---\n\n'
|
||||||
|
|
||||||
|
convert_i = convert_em
|
||||||
|
|
||||||
|
def convert_img(self, el, text, convert_as_inline):
|
||||||
|
alt = el.attrs.get('alt', None) or ''
|
||||||
|
src = el.attrs.get('src', None) or ''
|
||||||
|
title = el.attrs.get('title', None) or ''
|
||||||
|
title_part = ' "%s"' % title.replace('"', r'\"') if title else ''
|
||||||
|
if convert_as_inline:
|
||||||
|
return alt
|
||||||
|
|
||||||
|
return '' % (alt, src, title_part)
|
||||||
|
|
||||||
def convert_list(self, el, text, convert_as_inline):
|
def convert_list(self, el, text, convert_as_inline):
|
||||||
|
|
||||||
@@ -267,26 +308,26 @@ class MarkdownConverter(object):
|
|||||||
return text
|
return text
|
||||||
return '%s\n\n' % text if text else ''
|
return '%s\n\n' % text if text else ''
|
||||||
|
|
||||||
def convert_strong(self, el, text, convert_as_inline):
|
def convert_pre(self, el, text, convert_as_inline):
|
||||||
strong_tag = 2 * self.options['strong_em_symbol']
|
|
||||||
prefix, suffix, text = chomp(text)
|
|
||||||
if not text:
|
if not text:
|
||||||
return ''
|
return ''
|
||||||
return '%s%s%s%s%s' % (prefix, strong_tag, text, strong_tag, suffix)
|
return '\n```\n%s\n```\n' % text
|
||||||
|
|
||||||
def convert_img(self, el, text, convert_as_inline):
|
convert_s = convert_del
|
||||||
alt = el.attrs.get('alt', None) or ''
|
|
||||||
src = el.attrs.get('src', None) or ''
|
|
||||||
title = el.attrs.get('title', None) or ''
|
|
||||||
title_part = ' "%s"' % title.replace('"', r'\"') if title else ''
|
|
||||||
if convert_as_inline:
|
|
||||||
return alt
|
|
||||||
|
|
||||||
return '' % (alt, src, title_part)
|
convert_strong = convert_b
|
||||||
|
|
||||||
|
convert_samp = convert_code
|
||||||
|
|
||||||
def convert_table(self, el, text, convert_as_inline):
|
def convert_table(self, el, text, convert_as_inline):
|
||||||
return '\n\n' + text + '\n'
|
return '\n\n' + text + '\n'
|
||||||
|
|
||||||
|
def convert_td(self, el, text, convert_as_inline):
|
||||||
|
return ' ' + text + ' |'
|
||||||
|
|
||||||
|
def convert_th(self, el, text, convert_as_inline):
|
||||||
|
return ' ' + text + ' |'
|
||||||
|
|
||||||
def convert_tr(self, el, text, convert_as_inline):
|
def convert_tr(self, el, text, convert_as_inline):
|
||||||
cells = el.find_all(['td', 'th'])
|
cells = el.find_all(['td', 'th'])
|
||||||
is_headrow = all([cell.name == 'th' for cell in cells])
|
is_headrow = all([cell.name == 'th' for cell in cells])
|
||||||
@@ -302,15 +343,6 @@ class MarkdownConverter(object):
|
|||||||
overline += '| ' + ' | '.join(['---'] * len(cells)) + ' |' + '\n'
|
overline += '| ' + ' | '.join(['---'] * len(cells)) + ' |' + '\n'
|
||||||
return overline + '|' + text + '\n' + underline
|
return overline + '|' + text + '\n' + underline
|
||||||
|
|
||||||
def convert_th(self, el, text, convert_as_inline):
|
|
||||||
return ' ' + text + ' |'
|
|
||||||
|
|
||||||
def convert_td(self, el, text, convert_as_inline):
|
|
||||||
return ' ' + text + ' |'
|
|
||||||
|
|
||||||
def convert_hr(self, el, text, convert_as_inline):
|
|
||||||
return '\n\n---\n\n'
|
|
||||||
|
|
||||||
|
|
||||||
def markdownify(html, **options):
|
def markdownify(html, **options):
|
||||||
return MarkdownConverter(**options).convert(html)
|
return MarkdownConverter(**options).convert(html)
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -10,7 +10,7 @@ read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read()
|
|||||||
pkgmeta = {
|
pkgmeta = {
|
||||||
'__title__': 'markdownify',
|
'__title__': 'markdownify',
|
||||||
'__author__': 'Matthew Tretter',
|
'__author__': 'Matthew Tretter',
|
||||||
'__version__': '0.7.4',
|
'__version__': '0.8.0',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,3 +14,10 @@ def test_ignore_comments():
|
|||||||
def test_ignore_comments_with_other_tags():
|
def test_ignore_comments_with_other_tags():
|
||||||
text = md("<!-- This is a comment --><a href='http://example.com/'>example link</a>")
|
text = md("<!-- This is a comment --><a href='http://example.com/'>example link</a>")
|
||||||
assert text == "[example link](http://example.com/)"
|
assert text == "[example link](http://example.com/)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_code_with_tricky_content():
|
||||||
|
assert md('<code>></code>') == "`>`"
|
||||||
|
assert md('<code>/home/</code><b>username</b>') == "`/home/`**username**"
|
||||||
|
assert md('First line <code>blah blah<br />blah blah</code> second line') \
|
||||||
|
== "First line `blah blah \nblah blah` second line"
|
||||||
|
|||||||
@@ -240,6 +240,40 @@ def test_em_spaces():
|
|||||||
assert md('foo <em></em> bar') == 'foo bar'
|
assert md('foo <em></em> bar') == 'foo bar'
|
||||||
|
|
||||||
|
|
||||||
|
def inline_tests(tag, markup):
|
||||||
|
# Basically re-use test_em() and test_em_spaces(),
|
||||||
|
assert md(f'<{tag}>Hello</{tag}>') == f'{markup}Hello{markup}'
|
||||||
|
assert md(f'foo <{tag}>Hello</{tag}> bar') == f'foo {markup}Hello{markup} bar'
|
||||||
|
assert md(f'foo<{tag}> Hello</{tag}> bar') == f'foo {markup}Hello{markup} bar'
|
||||||
|
assert md(f'foo <{tag}>Hello </{tag}>bar') == f'foo {markup}Hello{markup} bar'
|
||||||
|
assert md(f'foo <{tag}></{tag}> bar') in ['foo bar', 'foo bar'] # Either is OK
|
||||||
|
|
||||||
|
|
||||||
|
def test_code():
|
||||||
|
inline_tests('code', '`')
|
||||||
|
|
||||||
|
|
||||||
|
def test_samp():
|
||||||
|
inline_tests('samp', '`')
|
||||||
|
|
||||||
|
|
||||||
|
def test_kbd():
|
||||||
|
inline_tests('kbd', '`')
|
||||||
|
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
def test_del():
|
||||||
|
inline_tests('del', '~~')
|
||||||
|
|
||||||
|
|
||||||
|
def test_s():
|
||||||
|
inline_tests('s', '~~')
|
||||||
|
|
||||||
|
|
||||||
def test_h1():
|
def test_h1():
|
||||||
assert md('<h1>Hello</h1>') == 'Hello\n=====\n\n'
|
assert md('<h1>Hello</h1>') == 'Hello\n=====\n\n'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user