optimize decktext algo a bit

This commit is contained in:
Luxferre
2025-12-31 13:17:39 +02:00
parent 08c36ad0b0
commit 66000bdd3a
+67 -74
View File
@@ -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'),
('<.+?>', ''),
('&lt;', '<'),
('&gt;', '>'),
('&(ldquo|rdquo|quot);', '"'),
('&(rsquo|lsquo|apos);', "'"),
('&amp;', '&')
]
_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}': '',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&apos;': "'",
'&amp;': '&'
}
# 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]