2021-06-25 14:38:31 +00:00
|
|
|
__filename__ = "markdown.py"
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
__license__ = "AGPL3+"
|
2022-02-03 13:58:20 +00:00
|
|
|
__version__ = "1.3.0"
|
2021-06-25 14:38:31 +00:00
|
|
|
__maintainer__ = "Bob Mottram"
|
2021-09-10 16:14:50 +00:00
|
|
|
__email__ = "bob@libreserver.org"
|
2021-06-25 14:38:31 +00:00
|
|
|
__status__ = "Production"
|
|
|
|
__module_group__ = "Web Interface"
|
|
|
|
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def _markdown_emphasis_html(markdown: str) -> str:
|
2021-06-25 14:38:31 +00:00
|
|
|
"""Add italics and bold html markup to the given markdown
|
|
|
|
"""
|
|
|
|
replacements = {
|
|
|
|
' **': ' <b>',
|
|
|
|
'** ': '</b> ',
|
|
|
|
'**.': '</b>.',
|
|
|
|
'**:': '</b>:',
|
|
|
|
'**;': '</b>;',
|
|
|
|
'**,': '</b>,',
|
|
|
|
'**\n': '</b>\n',
|
|
|
|
' *': ' <i>',
|
|
|
|
'* ': '</i> ',
|
|
|
|
'*.': '</i>.',
|
|
|
|
'*:': '</i>:',
|
|
|
|
'*;': '</i>;',
|
|
|
|
'*,': '</i>,',
|
|
|
|
'*\n': '</i>\n',
|
|
|
|
' _': ' <ul>',
|
|
|
|
'_ ': '</ul> ',
|
|
|
|
'_.': '</ul>.',
|
|
|
|
'_:': '</ul>:',
|
|
|
|
'_;': '</ul>;',
|
|
|
|
'_,': '</ul>,',
|
|
|
|
'_\n': '</ul>\n'
|
|
|
|
}
|
2022-01-02 22:35:39 +00:00
|
|
|
for md_str, html in replacements.items():
|
|
|
|
markdown = markdown.replace(md_str, html)
|
2021-06-25 14:38:31 +00:00
|
|
|
|
|
|
|
if markdown.startswith('**'):
|
|
|
|
markdown = markdown[2:] + '<b>'
|
|
|
|
elif markdown.startswith('*'):
|
|
|
|
markdown = markdown[1:] + '<i>'
|
|
|
|
elif markdown.startswith('_'):
|
|
|
|
markdown = markdown[1:] + '<ul>'
|
|
|
|
|
|
|
|
if markdown.endswith('**'):
|
|
|
|
markdown = markdown[:len(markdown) - 2] + '</b>'
|
|
|
|
elif markdown.endswith('*'):
|
|
|
|
markdown = markdown[:len(markdown) - 1] + '</i>'
|
|
|
|
elif markdown.endswith('_'):
|
|
|
|
markdown = markdown[:len(markdown) - 1] + '</ul>'
|
|
|
|
return markdown
|
|
|
|
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def _markdown_replace_quotes(markdown: str) -> str:
|
2021-06-25 14:38:31 +00:00
|
|
|
"""Replaces > quotes with html blockquote
|
|
|
|
"""
|
|
|
|
if '> ' not in markdown:
|
|
|
|
return markdown
|
|
|
|
lines = markdown.split('\n')
|
|
|
|
result = ''
|
2022-01-02 22:35:39 +00:00
|
|
|
prev_quote_line = None
|
2021-06-25 14:38:31 +00:00
|
|
|
for line in lines:
|
|
|
|
if '> ' not in line:
|
|
|
|
result += line + '\n'
|
2022-01-02 22:35:39 +00:00
|
|
|
prev_quote_line = None
|
2021-06-25 14:38:31 +00:00
|
|
|
continue
|
2022-01-02 22:35:39 +00:00
|
|
|
line_str = line.strip()
|
|
|
|
if not line_str.startswith('> '):
|
2021-06-25 14:38:31 +00:00
|
|
|
result += line + '\n'
|
2022-01-02 22:35:39 +00:00
|
|
|
prev_quote_line = None
|
2021-06-25 14:38:31 +00:00
|
|
|
continue
|
2022-01-02 22:35:39 +00:00
|
|
|
line_str = line_str.replace('> ', '', 1).strip()
|
|
|
|
if prev_quote_line:
|
|
|
|
new_prev_line = prev_quote_line.replace('</i></blockquote>\n', '')
|
|
|
|
result = result.replace(prev_quote_line, new_prev_line) + ' '
|
|
|
|
line_str += '</i></blockquote>\n'
|
2021-06-25 14:38:31 +00:00
|
|
|
else:
|
2022-01-02 22:35:39 +00:00
|
|
|
line_str = '<blockquote><i>' + line_str + '</i></blockquote>\n'
|
|
|
|
result += line_str
|
|
|
|
prev_quote_line = line_str
|
2021-06-25 14:38:31 +00:00
|
|
|
|
|
|
|
if '</blockquote>\n' in result:
|
|
|
|
result = result.replace('</blockquote>\n', '</blockquote>')
|
|
|
|
|
|
|
|
if result.endswith('\n') and \
|
|
|
|
not markdown.endswith('\n'):
|
|
|
|
result = result[:len(result) - 1]
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def _markdown_replace_links(markdown: str, images: bool = False) -> str:
|
2021-06-25 14:38:31 +00:00
|
|
|
"""Replaces markdown links with html
|
|
|
|
Optionally replace image links
|
|
|
|
"""
|
2022-01-02 22:35:39 +00:00
|
|
|
replace_links = {}
|
2021-06-25 14:38:31 +00:00
|
|
|
text = markdown
|
2022-01-02 22:35:39 +00:00
|
|
|
start_chars = '['
|
2021-06-25 14:38:31 +00:00
|
|
|
if images:
|
2022-01-02 22:35:39 +00:00
|
|
|
start_chars = '!['
|
|
|
|
while start_chars in text:
|
2021-06-25 14:38:31 +00:00
|
|
|
if ')' not in text:
|
|
|
|
break
|
2022-01-02 22:35:39 +00:00
|
|
|
text = text.split(start_chars, 1)[1]
|
|
|
|
markdown_link = start_chars + text.split(')')[0] + ')'
|
|
|
|
if ']' not in markdown_link or \
|
|
|
|
'(' not in markdown_link:
|
2021-06-25 14:38:31 +00:00
|
|
|
text = text.split(')', 1)[1]
|
|
|
|
continue
|
|
|
|
if not images:
|
2022-01-02 22:35:39 +00:00
|
|
|
replace_links[markdown_link] = \
|
2021-06-25 14:38:31 +00:00
|
|
|
'<a href="' + \
|
2022-01-02 22:35:39 +00:00
|
|
|
markdown_link.split('(')[1].split(')')[0] + \
|
2021-06-25 14:38:31 +00:00
|
|
|
'" target="_blank" rel="nofollow noopener noreferrer">' + \
|
2022-01-02 22:35:39 +00:00
|
|
|
markdown_link.split(start_chars)[1].split(']')[0] + \
|
2021-06-25 14:38:31 +00:00
|
|
|
'</a>'
|
|
|
|
else:
|
2022-01-02 22:35:39 +00:00
|
|
|
replace_links[markdown_link] = \
|
2021-06-25 14:38:31 +00:00
|
|
|
'<img class="markdownImage" src="' + \
|
2022-01-02 22:35:39 +00:00
|
|
|
markdown_link.split('(')[1].split(')')[0] + \
|
2021-06-25 14:38:31 +00:00
|
|
|
'" alt="' + \
|
2022-01-02 22:35:39 +00:00
|
|
|
markdown_link.split(start_chars)[1].split(']')[0] + \
|
2021-06-25 14:38:31 +00:00
|
|
|
'" />'
|
|
|
|
text = text.split(')', 1)[1]
|
2022-01-02 22:35:39 +00:00
|
|
|
for md_link, html_link in replace_links.items():
|
|
|
|
markdown = markdown.replace(md_link, html_link)
|
2021-06-25 14:38:31 +00:00
|
|
|
return markdown
|
|
|
|
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def markdown_to_html(markdown: str) -> str:
|
2021-06-25 14:38:31 +00:00
|
|
|
"""Converts markdown formatted text to html
|
|
|
|
"""
|
2021-12-29 21:55:09 +00:00
|
|
|
markdown = _markdown_replace_quotes(markdown)
|
|
|
|
markdown = _markdown_emphasis_html(markdown)
|
|
|
|
markdown = _markdown_replace_links(markdown, True)
|
|
|
|
markdown = _markdown_replace_links(markdown)
|
2021-06-25 14:38:31 +00:00
|
|
|
|
|
|
|
# replace headers
|
2022-01-02 22:35:39 +00:00
|
|
|
lines_list = markdown.split('\n')
|
|
|
|
html_str = ''
|
2021-06-25 14:38:31 +00:00
|
|
|
ctr = 0
|
2021-07-03 20:15:34 +00:00
|
|
|
titles = {
|
|
|
|
"h5": '#####',
|
|
|
|
"h4": '####',
|
|
|
|
"h3": '###',
|
|
|
|
"h2": '##',
|
|
|
|
"h1": '#'
|
|
|
|
}
|
2022-01-02 22:35:39 +00:00
|
|
|
for line in lines_list:
|
2021-06-25 14:38:31 +00:00
|
|
|
if ctr > 0:
|
2022-01-02 22:35:39 +00:00
|
|
|
html_str += '<br>'
|
|
|
|
for hsh, hashes in titles.items():
|
2021-07-03 20:15:34 +00:00
|
|
|
if line.startswith(hashes):
|
|
|
|
line = line.replace(hashes, '').strip()
|
2022-01-02 22:35:39 +00:00
|
|
|
line = '<' + hsh + '>' + line + '</' + hsh + '>'
|
2021-07-03 20:15:34 +00:00
|
|
|
ctr = -1
|
|
|
|
break
|
2022-01-02 22:35:39 +00:00
|
|
|
html_str += line
|
2021-06-25 14:38:31 +00:00
|
|
|
ctr += 1
|
2022-01-02 22:35:39 +00:00
|
|
|
return html_str
|