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': ['
', '', '', ''],
- '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]