optimize decktext algo a bit
This commit is contained in:
+67
-74
@@ -6,79 +6,66 @@ import re
|
|||||||
|
|
||||||
# internal tag delimiters and boundaries
|
# internal tag delimiters and boundaries
|
||||||
_tag_delim = '%%%'
|
_tag_delim = '%%%'
|
||||||
_start_tag = 'DECKTEXT_BEGIN'
|
_page_boundary = f'{_tag_delim}PAGE_SEP{_tag_delim}'
|
||||||
_end_tag = 'DECKTEXT_END'
|
_title_regex = re.compile('<(title|TITLE)>(.*?)<\/(title|TITLE)>')
|
||||||
_start_boundary = f'{_tag_delim}{_start_tag}{_tag_delim}'
|
_link_regex = re.compile('<(a|A).*?(href|HREF)="(.+?)">(.+?)<\\/(a|A)>')
|
||||||
_end_boundary = f'{_tag_delim}{_end_tag}{_tag_delim}'
|
|
||||||
_title_start_tag = 'TITLE_BEGIN'
|
|
||||||
_title_end_tag = 'TITLE_END'
|
|
||||||
_title_start_boundary = f'{_tag_delim}{_title_start_tag}{_tag_delim}'
|
|
||||||
_title_end_boundary = f'{_tag_delim}{_title_end_tag}{_tag_delim}'
|
|
||||||
_page_sep_tag = 'PAGE_SEP'
|
|
||||||
_page_boundary = f'{_tag_delim}{_page_sep_tag}{_tag_delim}'
|
|
||||||
_link_begin_tag = 'LINK_BEGIN'
|
_link_begin_tag = 'LINK_BEGIN'
|
||||||
_link_end_tag = 'LINK_END'
|
_link_end_tag = 'LINK_END'
|
||||||
|
_start_boundary = f'{_tag_delim}DECKTEXT_BEGIN{_tag_delim}'
|
||||||
|
_end_boundary = f'{_tag_delim}DECKTEXT_END{_tag_delim}'
|
||||||
_link_regex_src = f'{_tag_delim}{_link_begin_tag}:(.+?){_tag_delim}(.+?){_tag_delim}{_link_end_tag}{_tag_delim}'
|
_link_regex_src = f'{_tag_delim}{_link_begin_tag}:(.+?){_tag_delim}(.+?){_tag_delim}{_link_end_tag}{_tag_delim}'
|
||||||
|
_link_repl = f'{_tag_delim}{_link_begin_tag}:\\1{_tag_delim}\\2{_tag_delim}{_link_end_tag}{_tag_delim}'
|
||||||
_link_regex = re.compile(_link_regex_src)
|
_link_regex = re.compile(_link_regex_src)
|
||||||
|
|
||||||
# internal tag mapping
|
# HTML sanitization and rendering
|
||||||
_tagmap = {
|
|
||||||
_start_tag: ['<!\\-\\-\\ decktext_begin\\ \\-\\->'],
|
|
||||||
_end_tag: ['<!\\-\\-\\ decktext_end\\ \\-\\->'],
|
|
||||||
_title_start_tag: ['<title>'],
|
|
||||||
_title_end_tag: ['</title>'],
|
|
||||||
_page_sep_tag: ['<hr>', '<hr/>', '<hr\\ />'],
|
|
||||||
f'{_link_begin_tag}:\\1': ['<a\\ href="(.+?)">'],
|
|
||||||
_link_end_tag: ['</a>'],
|
|
||||||
'BOLD_BEGIN': ['<b>', '<strong>'],
|
|
||||||
'BOLD_END': ['</b>', '</strong>'],
|
|
||||||
'ITALIC_BEGIN': ['<i>', '<em>'],
|
|
||||||
'ITALIC_END': ['</i>', '</em>'],
|
|
||||||
'CODE_BEGIN': ['<tt>', '<code>', '<pre>', '<xmp>'],
|
|
||||||
'CODE_END': ['</tt>', '</code>', '</pre>', '</xmp>'],
|
|
||||||
'STRIKE_BEGIN': ['<s>', '<del>'],
|
|
||||||
'STRIKE_END': ['</s>', '</del>'],
|
|
||||||
'LI_BEGIN': ['<li>'],
|
|
||||||
'LI_END': ['</li>'],
|
|
||||||
'QUOTE_BEGIN': ['<q>', '<blockquote>'],
|
|
||||||
'QUOTE_END': ['</q>', '</blockquote>'],
|
|
||||||
'IGNORE': ['<.+?>']
|
|
||||||
}
|
|
||||||
|
|
||||||
# compile regexes for this mapping
|
def _replace_img_with_alt(match):
|
||||||
_ctagmap = []
|
tag_content = match.group(0)
|
||||||
for tagkey in _tagmap:
|
# Search for the alt attribute specifically within the matched img tag
|
||||||
finaltag = f'{_tag_delim}{tagkey}{_tag_delim}'
|
alt_match = re.search('alt=["\'](.*?)["\']', tag_content)
|
||||||
for tagitem in _tagmap[tagkey]:
|
if alt_match:
|
||||||
if tagkey != 'IGNORE':
|
return f'[IMG: {alt_match.group(1)}]'
|
||||||
_ctagmap.append((re.compile(tagitem.lower()), finaltag))
|
return '' # Return empty string if no alt is found
|
||||||
_ctagmap.append((re.compile(tagitem.upper()), finaltag))
|
|
||||||
# IGNORE tag needs to be added last
|
|
||||||
if 'IGNORE' in _tagmap:
|
|
||||||
for tagitem in _tagmap['IGNORE']:
|
|
||||||
_ctagmap.append((re.compile(tagitem), ''))
|
|
||||||
|
|
||||||
# output format replacements (tags and main HTML entities)
|
# (regex, repl) tuple list: order matters!
|
||||||
|
_render_regex_src = [
|
||||||
|
('<(head|HEAD).*?>.*?<\\/(head|HEAD)>', ''),
|
||||||
|
('<(script|SCRIPT).*?>.*?<\\/(script|SCRIPT)>', ''),
|
||||||
|
('<(style|STYLE).*?>.*?<\\/(style|STYLE)>', ''),
|
||||||
|
('<(audio|AUDIO).*?>.*?<\\/(audio|AUDIO)>', ''),
|
||||||
|
('<(video|VIDEO).*?>.*?<\\/(video|VIDEO)>', ''),
|
||||||
|
('<(object|OBJECT).*?>.*?<\\/(object|OBJECT)>', ''),
|
||||||
|
('<(canvas|CANVAS).*?>.*?<\\/(canvas|CANVAS)>', ''),
|
||||||
|
('<!-- (decktext_begin|DECKTEXT_BEGIN) -->', _start_boundary),
|
||||||
|
('<!-- (decktext_end|DECKTEXT_END) -->', _end_boundary),
|
||||||
|
('<img.*?>', _replace_img_with_alt),
|
||||||
|
('<a\\ .*?href="(.+?)".*?>(.+?)<\\/a>', _link_repl),
|
||||||
|
('<(hr|HR).*?>', f'\n{_page_boundary}\n'),
|
||||||
|
('<(b|B|strong|STRONG).*?>', '**'),
|
||||||
|
('<\\/(b|B|strong|STRONG)>', '**'),
|
||||||
|
('<(i|I|em|EM).*?>', '_'),
|
||||||
|
('<\\/(i|I|em|EM)>', '_'),
|
||||||
|
('<(tt|TT|code|CODE).*?>', '`'),
|
||||||
|
('<\\/(tt|TT|code|CODE)>', '`'),
|
||||||
|
('<(s|S|del|DEL).*?>', '~~'),
|
||||||
|
('<\\/(s|S|del|DEL)>', '~~'),
|
||||||
|
('<(pre|PRE|xmp|XMP).*?>', '\n```\n'),
|
||||||
|
('<\\/(pre|PRE|xmp|XMP)>', '\n```\n'),
|
||||||
|
('<(blockquote|BLOCKQUOTE).*?>', '\n>> '),
|
||||||
|
('<\\/(blockquote|BLOCKQUOTE)>', '\n'),
|
||||||
|
('<(li|LI).*?>', '* '),
|
||||||
|
('<\\/(li|LI)>', '\n'),
|
||||||
|
('<.+?>', ''),
|
||||||
|
('<', '<'),
|
||||||
|
('>', '>'),
|
||||||
|
('&(ldquo|rdquo|quot);', '"'),
|
||||||
|
('&(rsquo|lsquo|apos);', "'"),
|
||||||
|
('&', '&')
|
||||||
|
]
|
||||||
|
|
||||||
_render_tags = {
|
_rendering_regexes = []
|
||||||
f'{_tag_delim}BOLD_BEGIN{_tag_delim}': '**',
|
for r in _render_regex_src:
|
||||||
f'{_tag_delim}BOLD_END{_tag_delim}': '**',
|
_rendering_regexes.append((re.compile(r[0]), r[1]))
|
||||||
f'{_tag_delim}ITALIC_BEGIN{_tag_delim}': '_',
|
|
||||||
f'{_tag_delim}ITALIC_END{_tag_delim}': '_',
|
|
||||||
f'{_tag_delim}CODE_BEGIN{_tag_delim}': '`',
|
|
||||||
f'{_tag_delim}CODE_END{_tag_delim}': '`',
|
|
||||||
f'{_tag_delim}STRIKE_BEGIN{_tag_delim}': '~~',
|
|
||||||
f'{_tag_delim}STRIKE_END{_tag_delim}': '~~',
|
|
||||||
f'{_tag_delim}QUOTE_BEGIN{_tag_delim}': '>> ',
|
|
||||||
f'{_tag_delim}QUOTE_END{_tag_delim}': '',
|
|
||||||
f'{_tag_delim}LI_BEGIN{_tag_delim}': '* ',
|
|
||||||
f'{_tag_delim}LI_END{_tag_delim}': '',
|
|
||||||
'<': '<',
|
|
||||||
'>': '>',
|
|
||||||
'"': '"',
|
|
||||||
''': "'",
|
|
||||||
'&': '&'
|
|
||||||
}
|
|
||||||
|
|
||||||
# main class start
|
# main class start
|
||||||
class DeckText:
|
class DeckText:
|
||||||
@@ -88,6 +75,11 @@ class DeckText:
|
|||||||
if src:
|
if src:
|
||||||
self.load(src)
|
self.load(src)
|
||||||
|
|
||||||
|
def sanitize_html(self, pagetext):
|
||||||
|
for regex in _rendering_regexes:
|
||||||
|
pagetext = regex[0].sub(regex[1], pagetext)
|
||||||
|
return pagetext.strip()
|
||||||
|
|
||||||
def _render_page(self, pagetext):
|
def _render_page(self, pagetext):
|
||||||
page_links = [] # list of tuples in (url, title) format
|
page_links = [] # list of tuples in (url, title) format
|
||||||
linkpool = pagetext = pagetext.strip()
|
linkpool = pagetext = pagetext.strip()
|
||||||
@@ -103,8 +95,6 @@ class DeckText:
|
|||||||
linkpool = linkpool.replace(fullref, '')
|
linkpool = linkpool.replace(fullref, '')
|
||||||
pagetext = pagetext.replace(fullref, f'{title} ([{linknum}])')
|
pagetext = pagetext.replace(fullref, f'{title} ([{linknum}])')
|
||||||
linknum += 1
|
linknum += 1
|
||||||
for rtag in _render_tags: # render the rest of the tags
|
|
||||||
pagetext = pagetext.replace(rtag, _render_tags[rtag])
|
|
||||||
return {
|
return {
|
||||||
'content': pagetext,
|
'content': pagetext,
|
||||||
'links': page_links
|
'links': page_links
|
||||||
@@ -112,17 +102,20 @@ class DeckText:
|
|||||||
|
|
||||||
def load(self, src:str):
|
def load(self, src:str):
|
||||||
if len(src) > 0:
|
if len(src) > 0:
|
||||||
# pre-parse stage 1: replace ambiguous tags with own tagmap
|
# Stage 1: extract document title
|
||||||
for tagkey in _ctagmap:
|
|
||||||
src = tagkey[0].sub(tagkey[1], src)
|
|
||||||
# pre-parse stage 2: extract document title
|
|
||||||
try:
|
try:
|
||||||
self.title = src.split(_title_start_boundary)[1].split(_title_end_boundary)[0].strip()
|
self.title = _title_regex.search(src).group(2).strip()
|
||||||
except:
|
except:
|
||||||
self.title = ''
|
self.title = ''
|
||||||
# pre-parse stage 3: only leave the contents between the DeckText begin/end markers
|
# Stage 2: strip everything unnecessary and render almost everything
|
||||||
|
src = self.sanitize_html(src)
|
||||||
|
# Stage 3: determine if we need to go to the fallback mode
|
||||||
|
docparts = src.split(_start_boundary)
|
||||||
|
if len(docparts) < 2: # no DeckText start marker detected
|
||||||
|
docparts = ['', docparts[0]] # emulate this
|
||||||
|
# Stage 4: only leave the contents between the DeckText begin/end markers
|
||||||
fragments = []
|
fragments = []
|
||||||
for part in src.split(_start_boundary)[1:]: # skip the part before the first marker
|
for part in docparts[1:]: # skip the part before the first marker
|
||||||
frag = part # by default, we may not use an end marker although that's bad
|
frag = part # by default, we may not use an end marker although that's bad
|
||||||
try:
|
try:
|
||||||
frag = part.split(_end_boundary)[0]
|
frag = part.split(_end_boundary)[0]
|
||||||
|
|||||||
Reference in New Issue
Block a user