Initial commit
This commit is contained in:
191
env/lib/python3.10/site-packages/draftjs_exporter/html.py
vendored
Normal file
191
env/lib/python3.10/site-packages/draftjs_exporter/html.py
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
from itertools import groupby
|
||||
from operator import attrgetter
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from draftjs_exporter.command import Command
|
||||
from draftjs_exporter.composite_decorators import (
|
||||
render_decorators,
|
||||
should_render_decorators,
|
||||
)
|
||||
from draftjs_exporter.defaults import BLOCK_MAP, STYLE_MAP
|
||||
from draftjs_exporter.dom import DOM
|
||||
from draftjs_exporter.entity_state import EntityState
|
||||
from draftjs_exporter.options import Options
|
||||
from draftjs_exporter.style_state import StyleState
|
||||
from draftjs_exporter.types import (
|
||||
Block,
|
||||
Config,
|
||||
ContentState,
|
||||
Element,
|
||||
EntityMap,
|
||||
)
|
||||
from draftjs_exporter.wrapper_state import WrapperState
|
||||
|
||||
|
||||
class HTML:
|
||||
"""
|
||||
Entry point of the exporter. Combines entity, wrapper and style state
|
||||
to generate the right HTML nodes.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"composite_decorators",
|
||||
"entity_options",
|
||||
"block_options",
|
||||
"style_options",
|
||||
)
|
||||
|
||||
def __init__(self, config: Optional[Config] = None) -> None:
|
||||
if config is None:
|
||||
config = {}
|
||||
|
||||
self.composite_decorators = config.get("composite_decorators", [])
|
||||
|
||||
self.entity_options = Options.map_entities(
|
||||
config.get("entity_decorators", {})
|
||||
)
|
||||
self.block_options = Options.map_blocks(
|
||||
config.get("block_map", BLOCK_MAP)
|
||||
)
|
||||
self.style_options = Options.map_styles(
|
||||
config.get("style_map", STYLE_MAP)
|
||||
)
|
||||
|
||||
DOM.use(config.get("engine", DOM.STRING))
|
||||
|
||||
def render(self, content_state: Optional[ContentState] = None) -> str:
|
||||
"""
|
||||
Starts the export process on a given piece of content state.
|
||||
"""
|
||||
if content_state is None:
|
||||
content_state = {}
|
||||
|
||||
blocks = content_state.get("blocks", [])
|
||||
wrapper_state = WrapperState(self.block_options, blocks)
|
||||
document = DOM.create_element()
|
||||
entity_map = content_state.get("entityMap", {})
|
||||
min_depth = 0
|
||||
|
||||
for block in blocks:
|
||||
# Assume a depth of 0 if it's not specified, like Draft.js would.
|
||||
depth = block["depth"] if "depth" in block else 0
|
||||
elt = self.render_block(block, entity_map, wrapper_state)
|
||||
|
||||
if depth > min_depth:
|
||||
min_depth = depth
|
||||
|
||||
# At level 0, append the element to the document.
|
||||
if depth == 0:
|
||||
DOM.append_child(document, elt)
|
||||
|
||||
# If there is no block at depth 0, we need to add the wrapper that contains the whole tree to the document.
|
||||
if min_depth > 0 and wrapper_state.stack.length() != 0:
|
||||
DOM.append_child(document, wrapper_state.stack.tail().elt)
|
||||
|
||||
return DOM.render(document)
|
||||
|
||||
def render_block(
|
||||
self, block: Block, entity_map: EntityMap, wrapper_state: WrapperState
|
||||
) -> Element:
|
||||
has_styles = "inlineStyleRanges" in block and block["inlineStyleRanges"]
|
||||
has_entities = "entityRanges" in block and block["entityRanges"]
|
||||
has_decorators = should_render_decorators(
|
||||
self.composite_decorators, block["text"]
|
||||
)
|
||||
|
||||
if has_styles or has_entities:
|
||||
content = DOM.create_element()
|
||||
entity_state = EntityState(self.entity_options, entity_map)
|
||||
style_state = StyleState(self.style_options) if has_styles else None
|
||||
|
||||
for (text, commands) in self.build_command_groups(block):
|
||||
for command in commands:
|
||||
entity_state.apply(command)
|
||||
if style_state:
|
||||
style_state.apply(command)
|
||||
|
||||
# Decorators are not rendered inside entities.
|
||||
if has_decorators and entity_state.has_no_entity():
|
||||
decorated_node = render_decorators(
|
||||
self.composite_decorators,
|
||||
text,
|
||||
block,
|
||||
wrapper_state.blocks,
|
||||
)
|
||||
else:
|
||||
decorated_node = text
|
||||
|
||||
if style_state:
|
||||
styled_node = style_state.render_styles(
|
||||
decorated_node, block, wrapper_state.blocks
|
||||
)
|
||||
else:
|
||||
styled_node = decorated_node
|
||||
entity_node = entity_state.render_entities(
|
||||
styled_node, block, wrapper_state.blocks
|
||||
)
|
||||
|
||||
if entity_node is not None:
|
||||
DOM.append_child(content, entity_node)
|
||||
|
||||
# Check whether there actually are two different nodes, confirming we are not inserting an upcoming entity.
|
||||
if (
|
||||
styled_node != entity_node
|
||||
and entity_state.has_no_entity()
|
||||
):
|
||||
DOM.append_child(content, styled_node)
|
||||
# Fast track for blocks which do not contain styles nor entities, which is very common.
|
||||
elif has_decorators:
|
||||
content = render_decorators(
|
||||
self.composite_decorators,
|
||||
block["text"],
|
||||
block,
|
||||
wrapper_state.blocks,
|
||||
)
|
||||
else:
|
||||
content = block["text"]
|
||||
|
||||
return wrapper_state.element_for(block, content)
|
||||
|
||||
def build_command_groups(
|
||||
self, block: Block
|
||||
) -> List[Tuple[str, List[Command]]]:
|
||||
"""
|
||||
Creates block modification commands, grouped by start index,
|
||||
with the text to apply them on.
|
||||
"""
|
||||
text = block["text"]
|
||||
|
||||
commands = self.build_commands(block)
|
||||
grouped = groupby(commands, attrgetter("index"))
|
||||
listed = list(groupby(commands, attrgetter("index")))
|
||||
sliced = []
|
||||
|
||||
i = 0
|
||||
for start_index, comms in grouped:
|
||||
if i < len(listed) - 1:
|
||||
stop_index = listed[i + 1][0]
|
||||
sliced.append((text[start_index:stop_index], list(comms)))
|
||||
else:
|
||||
sliced.append(("", list(comms)))
|
||||
i += 1
|
||||
|
||||
return sliced
|
||||
|
||||
def build_commands(self, block: Block) -> List[Command]:
|
||||
"""
|
||||
Build all of the manipulation commands for a given block.
|
||||
- One pair to set the text.
|
||||
- Multiple pairs for styles.
|
||||
- Multiple pairs for entities.
|
||||
"""
|
||||
style_commands = Command.from_style_ranges(block)
|
||||
entity_commands = Command.from_entity_ranges(block)
|
||||
styles_and_entities = style_commands + entity_commands
|
||||
styles_and_entities.sort(key=attrgetter("index"))
|
||||
|
||||
return (
|
||||
[Command("start_text", 0)]
|
||||
+ styles_and_entities
|
||||
+ [Command("stop_text", len(block["text"]))]
|
||||
)
|
||||
Reference in New Issue
Block a user