Last active
May 30, 2019 23:17
-
-
Save kageurufu/64dbabcbf17e15fd8360caa8e44211f4 to your computer and use it in GitHub Desktop.
Ultra-minimal Python to HTML DOM renderer, using MarkupSafe for escaping
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import html | |
from markupsafe import Markup | |
class InvalidVoidTag(Exception): | |
pass | |
VOID_TAGS = { | |
"area", | |
"base", | |
"br", | |
"col", | |
"embed", | |
"hr", | |
"img", | |
"input", | |
"link", | |
"meta", | |
"param", | |
"source", | |
"track", | |
"wbr", | |
} | |
def dom(tag: str, *children) -> str: | |
""" | |
>>> dom('html') | |
Markup('<html></html>') | |
>>> dom('html', ('head', ), ('body', )) | |
Markup('<html><head></head><body></body></html>') | |
>>> dom("script", 'var value = "this is a test script, we should not strip anything but </script>"') | |
Markup('<script>var value = "this is a test script, we should not strip anything but <\\\\/script>"</script>') | |
>>> dom("style", ".test-class { background-color: gray; }") | |
Markup('<style>.test-class { background-color: gray; }</style>') | |
>>> dom("body", {"class": "test-class"}) | |
Markup('<body class="test-class"></body>') | |
>>> dom("p", "and we even prevent basic <script>xss</script> and <b>tag injection") | |
Markup('<p>and we even prevent basic <script>xss</script> and <b>tag injection</p>') | |
>>> dom('img', ('test',)) | |
Traceback (most recent call last): | |
... | |
e.InvalidVoidTag: Void tag img may not have child elements... | |
>>> dom( \ | |
"html", \ | |
( \ | |
"head", \ | |
("script", 'var value = "this is a test script, we should not strip anything but </script>"'), \ | |
("style", ".test-class { background-color: gray; }"), \ | |
), \ | |
( \ | |
"body", \ | |
{"class": "test-class"}, \ | |
( \ | |
"div", \ | |
("h1", "Heading"), \ | |
( \ | |
"article", \ | |
("p", "some content goes here!"), \ | |
("p", "some more content here"), \ | |
( \ | |
"p", \ | |
"and we even prevent basic <script>xss</script> and <b>tag injection", \ | |
("img", {"src": "https://avatars1.githubusercontent.com/u/259751"}), \ | |
), \ | |
), \ | |
), \ | |
), \ | |
) | |
Markup('<html><head><script>var value = "this is a test script, we should not strip anything but <\\\\/script>"</script><style>.test-class { background-color: gray; }</style></head><body class="test-class"><div><h1>Heading</h1><article><p>some content goes here!</p><p>some more content here</p><p>and we even prevent basic <script>xss</script> and <b>tag injection<img src="https://avatars1.githubusercontent.com/u/259751"></p></article></div></body></html>') | |
""" | |
html_attrs = [] | |
if children and isinstance(children[0], dict): | |
attrs, *children = children | |
for k, v in attrs.items(): | |
if v in (True, False): | |
html_attrs.append(f" {k}") | |
else: | |
html_attrs.append(f' {k}="{html.escape(v)}"') | |
open_tag = Markup(f"<{tag}{''.join(html_attrs)}>") | |
end_tag = Markup(f"</{tag}>") | |
if tag in VOID_TAGS: | |
if children: | |
raise InvalidVoidTag( | |
f"Void tag {tag} may not have child elements\n" | |
f"See https://www.w3.org/TR/html5/syntax.html#start-tags for more details" | |
) | |
return open_tag | |
return ( | |
open_tag | |
+ Markup("").join( | |
dom(*c) # render sub-tuples to dom | |
if isinstance(c, tuple) | |
else Markup(c.replace("</script>", "<\\/script>")) # don't escape script or style bodies | |
if tag == "script" | |
else Markup(c.replace("</style>", "<\\/style>")) | |
if tag == "style" | |
else c # Use plaintext to make markupsafe escape it | |
for c in children | |
) | |
+ end_tag | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment