diff --git a/deck/text.py b/deck/text.py index 5264548..f6a2542 100644 --- a/deck/text.py +++ b/deck/text.py @@ -6,79 +6,66 @@ import re # internal tag delimiters and boundaries _tag_delim = '%%%' -_start_tag = 'DECKTEXT_BEGIN' -_end_tag = 'DECKTEXT_END' -_start_boundary = f'{_tag_delim}{_start_tag}{_tag_delim}' -_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}' +_page_boundary = f'{_tag_delim}PAGE_SEP{_tag_delim}' +_title_regex = re.compile('<(title|TITLE)>(.*?)<\/(title|TITLE)>') +_link_regex = re.compile('<(a|A).*?(href|HREF)="(.+?)">(.+?)<\\/(a|A)>') _link_begin_tag = 'LINK_BEGIN' _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_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) -# internal tag mapping -_tagmap = { - _start_tag: [''], - _end_tag: [''], - _title_start_tag: [''], - _title_end_tag: [''], - _page_sep_tag: ['
', '
', ''], - f'{_link_begin_tag}:\\1': [''], - _link_end_tag: [''], - 'BOLD_BEGIN': ['', ''], - 'BOLD_END': ['', ''], - 'ITALIC_BEGIN': ['', ''], - 'ITALIC_END': ['', ''], - 'CODE_BEGIN': ['', '', '
', ''],
-  'CODE_END': ['</tt>', '</code>', '</pre>', ''],
-  'STRIKE_BEGIN': ['', ''],
-  'STRIKE_END': ['', ''],
-  'LI_BEGIN': ['
  • '], - 'LI_END': ['
  • '], - 'QUOTE_BEGIN': ['', '
    '], - 'QUOTE_END': ['', '
    '], - 'IGNORE': ['<.+?>'] -} +# HTML sanitization and rendering -# compile regexes for this mapping -_ctagmap = [] -for tagkey in _tagmap: - finaltag = f'{_tag_delim}{tagkey}{_tag_delim}' - for tagitem in _tagmap[tagkey]: - if tagkey != 'IGNORE': - _ctagmap.append((re.compile(tagitem.lower()), finaltag)) - _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), '')) +def _replace_img_with_alt(match): + tag_content = match.group(0) + # Search for the alt attribute specifically within the matched img tag + alt_match = re.search('alt=["\'](.*?)["\']', tag_content) + if alt_match: + return f'[IMG: {alt_match.group(1)}]' + return '' # Return empty string if no alt is found -# 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)>', ''), + ('', _start_boundary), + ('', _end_boundary), + ('', _replace_img_with_alt), + ('(.+?)<\\/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 = { - f'{_tag_delim}BOLD_BEGIN{_tag_delim}': '**', - f'{_tag_delim}BOLD_END{_tag_delim}': '**', - 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}': '', - '<': '<', - '>': '>', - '"': '"', - ''': "'", - '&': '&' -} +_rendering_regexes = [] +for r in _render_regex_src: + _rendering_regexes.append((re.compile(r[0]), r[1])) # main class start class DeckText: @@ -88,6 +75,11 @@ class DeckText: if 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): page_links = [] # list of tuples in (url, title) format linkpool = pagetext = pagetext.strip() @@ -103,8 +95,6 @@ class DeckText: linkpool = linkpool.replace(fullref, '') pagetext = pagetext.replace(fullref, f'{title} ([{linknum}])') linknum += 1 - for rtag in _render_tags: # render the rest of the tags - pagetext = pagetext.replace(rtag, _render_tags[rtag]) return { 'content': pagetext, 'links': page_links @@ -112,17 +102,20 @@ class DeckText: def load(self, src:str): if len(src) > 0: - # pre-parse stage 1: replace ambiguous tags with own tagmap - for tagkey in _ctagmap: - src = tagkey[0].sub(tagkey[1], src) - # pre-parse stage 2: extract document title + # Stage 1: extract document title 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: 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 = [] - 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 try: frag = part.split(_end_boundary)[0]