Skip to content

Commit 8c810eb

Browse files
committed
Merge branch 'develop'
2 parents 383847e + f6c8daf commit 8c810eb

File tree

9 files changed

+93
-15
lines changed

9 files changed

+93
-15
lines changed

MANIFEST.in

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
include README.rst
2+
prune tests

README.rst

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
|build| |version| |license| |downloads|
22

3-
.. |build| image:: https://img.shields.io/github/workflow/status/matthewwithanm/python-markdownify/Python%20application/develop
3+
.. |build| image:: https://img.shields.io/github/actions/workflow/status/matthewwithanm/python-markdownify/python-app.yml?branch=develop
44
:alt: GitHub Workflow Status
5-
:target: https://github.com/matthewwithanm/python-markdownify/actions?query=workflow%3A%22Python+application%22
5+
:target: https://github.com/matthewwithanm/python-markdownify/actions/workflows/python-app.yml?query=workflow%3A%22Python+application%22
66

77
.. |version| image:: https://img.shields.io/pypi/v/markdownify
88
:alt: Pypi version
@@ -87,7 +87,11 @@ strong_em_symbol
8787
sub_symbol, sup_symbol
8888
Define the chars that surround ``<sub>`` and ``<sup>`` text. Defaults to an
8989
empty string, because this is non-standard behavior. Could be something like
90-
``~`` and ``^`` to result in ``~sub~`` and ``^sup^``.
90+
``~`` and ``^`` to result in ``~sub~`` and ``^sup^``. If the value starts
91+
with ``<`` and ends with ``>``, it is treated as an HTML tag and a ``/`` is
92+
inserted after the ``<`` in the string used after the text; this allows
93+
specifying ``<sub>`` to use raw HTML in the output for subscripts, for
94+
example.
9195

9296
newline_style
9397
Defines the style of marking linebreaks (``<br>``) in markdown. The default
@@ -123,6 +127,11 @@ escape_underscores
123127
If set to ``False``, do not escape ``_`` to ``\_`` in text.
124128
Defaults to ``True``.
125129

130+
escape_misc
131+
If set to ``False``, do not escape miscellaneous punctuation characters
132+
that sometimes have Markdown significance in text.
133+
Defaults to ``True``.
134+
126135
keep_inline_images_in
127136
Images are converted to their alt-text when the images are located inside
128137
headlines or table cells. If some inline images should be converted to

markdownify/__init__.py

+18-7
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,22 @@ def abstract_inline_conversion(markup_fn):
4343
"""
4444
This abstracts all simple inline tags like b, em, del, ...
4545
Returns a function that wraps the chomped text in a pair of the string
46-
that is returned by markup_fn. markup_fn is necessary to allow for
46+
that is returned by markup_fn, with '/' inserted in the string used after
47+
the text if it looks like an HTML tag. markup_fn is necessary to allow for
4748
references to self.strong_em_symbol etc.
4849
"""
4950
def implementation(self, el, text, convert_as_inline):
50-
markup = markup_fn(self)
51+
markup_prefix = markup_fn(self)
52+
if markup_prefix.startswith('<') and markup_prefix.endswith('>'):
53+
markup_suffix = '</' + markup_prefix[1:]
54+
else:
55+
markup_suffix = markup_prefix
56+
if el.find_parent(['pre', 'code', 'kbd', 'samp']):
57+
return text
5158
prefix, suffix, text = chomp(text)
5259
if not text:
5360
return ''
54-
return '%s%s%s%s%s' % (prefix, markup, text, markup, suffix)
61+
return '%s%s%s%s%s' % (prefix, markup_prefix, text, markup_suffix, suffix)
5562
return implementation
5663

5764

@@ -69,6 +76,7 @@ class DefaultOptions:
6976
default_title = False
7077
escape_asterisks = True
7178
escape_underscores = True
79+
escape_misc = True
7280
heading_style = UNDERLINED
7381
keep_inline_images_in = []
7482
newline_style = SPACES
@@ -199,6 +207,9 @@ def should_convert_tag(self, tag):
199207
def escape(self, text):
200208
if not text:
201209
return ''
210+
if self.options['escape_misc']:
211+
text = re.sub(r'([\\&<`[>~#=+|-])', r'\\\1', text)
212+
text = re.sub(r'([0-9])([.)])', r'\1\\\2', text)
202213
if self.options['escape_asterisks']:
203214
text = text.replace('*', r'\*')
204215
if self.options['escape_underscores']:
@@ -315,7 +326,7 @@ def convert_list(self, el, text, convert_as_inline):
315326
def convert_li(self, el, text, convert_as_inline):
316327
parent = el.parent
317328
if parent is not None and parent.name == 'ol':
318-
if parent.get("start"):
329+
if parent.get("start") and str(parent.get("start")).isnumeric():
319330
start = int(parent.get("start"))
320331
else:
321332
start = 1
@@ -377,13 +388,13 @@ def convert_figcaption(self, el, text, convert_as_inline):
377388

378389
def convert_td(self, el, text, convert_as_inline):
379390
colspan = 1
380-
if 'colspan' in el.attrs:
391+
if 'colspan' in el.attrs and el['colspan'].isdigit():
381392
colspan = int(el['colspan'])
382393
return ' ' + text.strip().replace("\n", " ") + ' |' * colspan
383394

384395
def convert_th(self, el, text, convert_as_inline):
385396
colspan = 1
386-
if 'colspan' in el.attrs:
397+
if 'colspan' in el.attrs and el['colspan'].isdigit():
387398
colspan = int(el['colspan'])
388399
return ' ' + text.strip().replace("\n", " ") + ' |' * colspan
389400

@@ -400,7 +411,7 @@ def convert_tr(self, el, text, convert_as_inline):
400411
# first row and is headline: print headline underline
401412
full_colspan = 0
402413
for cell in cells:
403-
if "colspan" in cell.attrs:
414+
if 'colspan' in cell.attrs and cell['colspan'].isdigit():
404415
full_colspan += int(cell["colspan"])
405416
else:
406417
full_colspan += 1

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
pkgmeta = {
1010
'__title__': 'markdownify',
1111
'__author__': 'Matthew Tretter',
12-
'__version__': '0.12.1',
12+
'__version__': '0.13.0',
1313
}
1414

1515
read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read()

tests/test_conversions.py

+23
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ def test_code():
8787
assert md('<code><span>*this_should_not_escape*</span></code>') == '`*this_should_not_escape*`'
8888
assert md('<code>this should\t\tnormalize</code>') == '`this should normalize`'
8989
assert md('<code><span>this should\t\tnormalize</span></code>') == '`this should normalize`'
90+
assert md('<code>foo<b>bar</b>baz</code>') == '`foobarbaz`'
91+
assert md('<kbd>foo<i>bar</i>baz</kbd>') == '`foobarbaz`'
92+
assert md('<samp>foo<del> bar </del>baz</samp>') == '`foo bar baz`'
93+
assert md('<samp>foo <del>bar</del> baz</samp>') == '`foo bar baz`'
94+
assert md('<code>foo<em> bar </em>baz</code>') == '`foo bar baz`'
95+
assert md('<code>foo<code> bar </code>baz</code>') == '`foo bar baz`'
96+
assert md('<code>foo<strong> bar </strong>baz</code>') == '`foo bar baz`'
97+
assert md('<code>foo<s> bar </s>baz</code>') == '`foo bar baz`'
98+
assert md('<code>foo<sup>bar</sup>baz</code>', sup_symbol='^') == '`foobarbaz`'
99+
assert md('<code>foo<sub>bar</sub>baz</code>', sub_symbol='^') == '`foobarbaz`'
90100

91101

92102
def test_del():
@@ -215,6 +225,17 @@ def test_pre():
215225
assert md('<pre><span>*this_should_not_escape*</span></pre>') == '\n```\n*this_should_not_escape*\n```\n'
216226
assert md('<pre>\t\tthis should\t\tnot normalize</pre>') == '\n```\n\t\tthis should\t\tnot normalize\n```\n'
217227
assert md('<pre><span>\t\tthis should\t\tnot normalize</span></pre>') == '\n```\n\t\tthis should\t\tnot normalize\n```\n'
228+
assert md('<pre>foo<b>\nbar\n</b>baz</pre>') == '\n```\nfoo\nbar\nbaz\n```\n'
229+
assert md('<pre>foo<i>\nbar\n</i>baz</pre>') == '\n```\nfoo\nbar\nbaz\n```\n'
230+
assert md('<pre>foo\n<i>bar</i>\nbaz</pre>') == '\n```\nfoo\nbar\nbaz\n```\n'
231+
assert md('<pre>foo<i>\n</i>baz</pre>') == '\n```\nfoo\nbaz\n```\n'
232+
assert md('<pre>foo<del>\nbar\n</del>baz</pre>') == '\n```\nfoo\nbar\nbaz\n```\n'
233+
assert md('<pre>foo<em>\nbar\n</em>baz</pre>') == '\n```\nfoo\nbar\nbaz\n```\n'
234+
assert md('<pre>foo<code>\nbar\n</code>baz</pre>') == '\n```\nfoo\nbar\nbaz\n```\n'
235+
assert md('<pre>foo<strong>\nbar\n</strong>baz</pre>') == '\n```\nfoo\nbar\nbaz\n```\n'
236+
assert md('<pre>foo<s>\nbar\n</s>baz</pre>') == '\n```\nfoo\nbar\nbaz\n```\n'
237+
assert md('<pre>foo<sup>\nbar\n</sup>baz</pre>', sup_symbol='^') == '\n```\nfoo\nbar\nbaz\n```\n'
238+
assert md('<pre>foo<sub>\nbar\n</sub>baz</pre>', sub_symbol='^') == '\n```\nfoo\nbar\nbaz\n```\n'
218239

219240

220241
def test_script():
@@ -247,11 +268,13 @@ def test_strong_em_symbol():
247268
def test_sub():
248269
assert md('<sub>foo</sub>') == 'foo'
249270
assert md('<sub>foo</sub>', sub_symbol='~') == '~foo~'
271+
assert md('<sub>foo</sub>', sub_symbol='<sub>') == '<sub>foo</sub>'
250272

251273

252274
def test_sup():
253275
assert md('<sup>foo</sup>') == 'foo'
254276
assert md('<sup>foo</sup>', sup_symbol='^') == '^foo^'
277+
assert md('<sup>foo</sup>', sup_symbol='<sup>') == '<sup>foo</sup>'
255278

256279

257280
def test_lang():

tests/test_escaping.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def test_underscore():
1212

1313

1414
def test_xml_entities():
15-
assert md('&amp;') == '&'
15+
assert md('&amp;') == r'\&'
1616

1717

1818
def test_named_entities():
@@ -25,4 +25,23 @@ def test_hexadecimal_entities():
2525

2626

2727
def test_single_escaping_entities():
28-
assert md('&amp;amp;') == '&amp;'
28+
assert md('&amp;amp;') == r'\&amp;'
29+
30+
31+
def text_misc():
32+
assert md('\\*') == r'\\\*'
33+
assert md('<foo>') == r'\<foo\>'
34+
assert md('# foo') == r'\# foo'
35+
assert md('> foo') == r'\> foo'
36+
assert md('~~foo~~') == r'\~\~foo\~\~'
37+
assert md('foo\n===\n') == 'foo\n\\=\\=\\=\n'
38+
assert md('---\n') == '\\-\\-\\-\n'
39+
assert md('+ x\n+ y\n') == '\\+ x\n\\+ y\n'
40+
assert md('`x`') == r'\`x\`'
41+
assert md('[text](link)') == r'\[text](link)'
42+
assert md('1. x') == r'1\. x'
43+
assert md('not a number. x') == r'not a number. x'
44+
assert md('1) x') == r'1\) x'
45+
assert md('not a number) x') == r'not a number) x'
46+
assert md('|not table|') == r'\|not table\|'
47+
assert md(r'\ <foo> &amp;amp; | ` `', escape_misc=False) == r'\ <foo> &amp; | ` `'

tests/test_lists.py

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
def test_ol():
4444
assert md('<ol><li>a</li><li>b</li></ol>') == '1. a\n2. b\n'
4545
assert md('<ol start="3"><li>a</li><li>b</li></ol>') == '3. a\n4. b\n'
46+
assert md('<ol start="-1"><li>a</li><li>b</li></ol>') == '1. a\n2. b\n'
47+
assert md('<ol start="foo"><li>a</li><li>b</li></ol>') == '1. a\n2. b\n'
48+
assert md('<ol start="1.5"><li>a</li><li>b</li></ol>') == '1. a\n2. b\n'
4649

4750

4851
def test_nested_ols():

tests/test_tables.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@
215215
<th>Age</th>
216216
</tr>
217217
<tr>
218-
<td>Jill</td>
218+
<td colspan="1">Jill</td>
219219
<td>Smith</td>
220220
<td>50</td>
221221
</tr>
@@ -226,6 +226,17 @@
226226
</tr>
227227
</table>"""
228228

229+
table_with_undefined_colspan = """<table>
230+
<tr>
231+
<th colspan="undefined">Name</th>
232+
<th>Age</th>
233+
</tr>
234+
<tr>
235+
<td colspan="-1">Jill</td>
236+
<td>Smith</td>
237+
</tr>
238+
</table>"""
239+
229240

230241
def test_table():
231242
assert md(table) == '\n\n| Firstname | Lastname | Age |\n| --- | --- | --- |\n| Jill | Smith | 50 |\n| Eve | Jackson | 94 |\n\n'
@@ -240,3 +251,4 @@ def test_table():
240251
assert md(table_body) == '\n\n| Firstname | Lastname | Age |\n| --- | --- | --- |\n| Jill | Smith | 50 |\n| Eve | Jackson | 94 |\n\n'
241252
assert md(table_with_caption) == 'TEXT\n\nCaption\n| Firstname | Lastname | Age |\n| --- | --- | --- |\n\n'
242253
assert md(table_with_colspan) == '\n\n| Name | | Age |\n| --- | --- | --- |\n| Jill | Smith | 50 |\n| Eve | Jackson | 94 |\n\n'
254+
assert md(table_with_undefined_colspan) == '\n\n| Name | Age |\n| --- | --- |\n| Jill | Smith |\n\n'

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ envlist = py38
44
[testenv]
55
passenv = PYTHONPATH
66
deps =
7-
pytest
7+
pytest==8
88
flake8
99
restructuredtext_lint
1010
Pygments

0 commit comments

Comments
 (0)