Files
2024-08-27 20:33:44 +02:00

123 lines
3.4 KiB
Python

from html import escape
from typing import List, Optional, Sequence, Union
from draftjs_exporter.engines.base import Attr, DOMEngine
from draftjs_exporter.types import HTML, Tag
# http://w3c.github.io/html/single-page.html#void-elements
# https://github.com/html5lib/html5lib-python/blob/0cae52b2073e3f2220db93a7650901f2200f2a13/html5lib/constants.py#L560
VOID_ELEMENTS = (
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"link",
"meta",
"param",
"source",
"track",
"wbr",
)
class Elt:
"""
A DOM element that the string engine manipulates.
This class doesn't do much, but the exporter relies on
comparing elements by reference so it's useful nonetheless.
"""
__slots__ = ("type", "attr", "children", "markup")
def __init__(self, type_: Tag, attr: Optional[Attr], markup: HTML = None):
self.type = type_
self.attr = attr
self.children: List["Elt"] = []
self.markup = markup
@staticmethod
def from_html(markup: HTML) -> "Elt":
return Elt("escaped_html", None, markup)
class DOMString(DOMEngine):
"""
String concatenation implementation of the DOM API.
"""
@staticmethod
def create_tag(type_: Tag, attr: Optional[Attr] = None) -> Elt:
return Elt(type_, attr)
@staticmethod
def parse_html(markup: HTML) -> Elt:
"""
Allows inserting arbitrary HTML into the exporter output.
Treats the HTML as if it had been escaped and was safe already.
"""
return Elt.from_html(markup)
@staticmethod
def append_child(elt: Elt, child: Elt) -> None:
# This check is necessary because the current wrapper_state implementation
# has an issue where it inserts elements multiple times.
# This must be skipped for text, which can be duplicated.
is_existing_ref = child in elt.children and isinstance(child, Elt)
if not is_existing_ref:
elt.children.append(child)
@staticmethod
def render_attrs(attr: Attr) -> str:
attrs = [f' {k}="{escape(v)}"' for k, v in attr.items()]
return "".join(attrs)
@staticmethod
def render_children(children: Sequence[Union[HTML, Elt]]) -> HTML:
return "".join(
[
DOMString.render(c)
if isinstance(c, Elt)
else escape(c, quote=False)
for c in children
]
)
@staticmethod
def render(elt: Elt) -> HTML:
type_ = elt.type
attr = DOMString.render_attrs(elt.attr) if elt.attr else ""
children = (
DOMString.render_children(elt.children) if elt.children else ""
)
if type_ == "fragment":
return children
if type_ in VOID_ELEMENTS:
return f"<{type_}{attr}/>"
if type_ == "escaped_html":
return elt.markup # type: ignore
return f"<{type_}{attr}>{children}</{type_}>"
@staticmethod
def render_debug(elt: Elt) -> HTML:
type_ = elt.type
attr = DOMString.render_attrs(elt.attr) if elt.attr else ""
children = (
DOMString.render_children(elt.children) if elt.children else ""
)
if type_ in VOID_ELEMENTS:
return f"<{type_}{attr}/>"
if type_ == "escaped_html":
return elt.markup # type: ignore
return f"<{type_}{attr}>{children}</{type_}>"