class Command(object):
    """
    A Command represents an operation that has to be executed
    on a block for it to be converted into an arbitrary number
    of HTML nodes.
    """

    __slots__ = ("name", "index", "data")

    def __init__(self, name: str, index: int, data: str = "") -> None:
        self.name = name
        self.index = index
        self.data = data

    def __str__(self) -> str:
        return f"<Command {self.name} {self.index} {self.data}>"

    def __repr__(self) -> str:
        return str(self)

    @staticmethod
    def from_entity_ranges(block: Block) -> List["Command"]:
        """
        Creates a list of commands from a block’s list of entity ranges.
        Each range is converted to two commands: a start_* and a stop_*.
        """
        commands: List["Command"] = []
        for r in block["entityRanges"]:
            # Entity key is an integer in entity ranges, while a string in the entity map.
            data = str(r["key"])
            start = r["offset"]
            stop = start + r["length"]
            commands.append(Command("start_entity", start, data))
            commands.append(Command("stop_entity", stop, data))

        return commands

    @staticmethod
    def from_style_ranges(block: Block) -> List["Command"]:
        """
        Creates a list of commands from a block’s list of style ranges.
        Each range is converted to two commands: a start_* and a stop_*.
        """
        commands: List["Command"] = []
        for r in block["inlineStyleRanges"]:
            data = r["style"]
            start = r["offset"]
            stop = start + r["length"]
            commands.append(Command("start_inline_style", start, data))
            commands.append(Command("stop_inline_style", stop, data))
        return commands def render_decorators(
    decorators: CompositeDecorators,
    text: str,
    block: Block,
    blocks: Sequence[Block],
) -> Element:
    decorated_children = list(apply_decorators(decorators, text, block, blocks))

    if len(decorated_children) == 1:
        decorated_node = decorated_children[0]
    else:
        decorated_node = DOM.create_element()
        for decorated_child in decorated_children:
            DOM.append_child(decorated_node, decorated_child)

    return decorated_node def should_render_decorators(
    decorators: CompositeDecorators,
    text: str,
) -> bool:
    nb_decorators = len(decorators)

    if nb_decorators == 0:
        return False

    is_skippable_br = (
        nb_decorators == 1
        and decorators[0]["strategy"] == br_strategy
        and not (br in text)
    )

    return not is_skippable_br BLOCK_MAP = {
    BLOCK_TYPES.UNSTYLED: "p",
    BLOCK_TYPES.HEADER_ONE: "h1",
    BLOCK_TYPES.HEADER_TWO: "h2",
    BLOCK_TYPES.HEADER_THREE: "h3",
    BLOCK_TYPES.HEADER_FOUR: "h4",
    BLOCK_TYPES.HEADER_FIVE: "h5",
    BLOCK_TYPES.HEADER_SIX: "h6",
    BLOCK_TYPES.UNORDERED_LIST_ITEM: {"element": "li", "wrapper": "ul"},
    BLOCK_TYPES.ORDERED_LIST_ITEM: {"element": "li", "wrapper": "ol"},
    BLOCK_TYPES.BLOCKQUOTE: "blockquote",
    BLOCK_TYPES.PRE: "pre",
    BLOCK_TYPES.CODE: code_block,
    BLOCK_TYPES.ATOMIC: render_children,
} STYLE_MAP = {
    INLINE_STYLES.BOLD: "strong",
    INLINE_STYLES.CODE: "code",
    INLINE_STYLES.ITALIC: "em",
    INLINE_STYLES.UNDERLINE: "u",
    INLINE_STYLES.STRIKETHROUGH: "s",
    INLINE_STYLES.SUPERSCRIPT: "sup",
    INLINE_STYLES.SUBSCRIPT: "sub",
    INLINE_STYLES.MARK: "mark",
    INLINE_STYLES.QUOTATION: "q",
    INLINE_STYLES.SMALL: "small",
    INLINE_STYLES.SAMPLE: "samp",
    INLINE_STYLES.INSERT: "ins",
    INLINE_STYLES.DELETE: "del",
    INLINE_STYLES.KEYBOARD: "kbd",
} class DOM(object):
    """
    Component building API, abstracting the DOM implementation.
    """

    HTML5LIB = "draftjs_exporter.engines.html5lib.DOM_HTML5LIB"
    LXML = "draftjs_exporter.engines.lxml.DOM_LXML"
    STRING = "draftjs_exporter.engines.string.DOMString"

    dom: DOMEngine = None  # type: ignore

    @staticmethod
    def camel_to_dash(camel_cased_str: str) -> str:
        sub2 = _first_cap_re.sub(r"\1-\2", camel_cased_str)
        dashed_case_str = _all_cap_re.sub(r"\1-\2", sub2).lower()
        return dashed_case_str.replace("--", "-")

    @classmethod
    def use(cls, engine: str) -> None:
        """
        Choose which DOM implementation to use.
        """
        cls.dom = import_string(engine)

    @classmethod
    def create_element(
        cls,
        type_: RenderableType = None,
        props: Optional[Props] = None,
        *elt_children: Optional[Element],
    ) -> Element:
        """
        Signature inspired by React.createElement.
        createElement(
          string/Component type,
          [dict props],
          [children ...]
        )
        https://facebook.github.io/react/docs/top-level-api.html#react.createelement
        """
        # Create an empty document fragment.
        if not type_:
            return cls.dom.create_tag("fragment")

        if props is None:
            props = {}

        # If the first element of children is a list, we use it as the list.
        if len(elt_children) and isinstance(elt_children[0], (list, tuple)):
            children = elt_children[0]
        else:
            children = elt_children

        # The children prop is the first child if there is only one.
        props["children"] = children[0] if len(children) == 1 else children

        if callable(type_):
            # Function component, via def or lambda.
            elt = type_(props)
        else:
            # Raw tag, as a string.
            attributes = {}

            # Never render those attributes on a raw tag.
            props.pop("children", None)
            props.pop("block", None)
            props.pop("blocks", None)
            props.pop("entity", None)
            props.pop("inline_style_range", None)

            # Convert style object to style string, like the DOM would do.
            if "style" in props and isinstance(props["style"], dict):
                rules = [
                    f"{DOM.camel_to_dash(s)}: {v};"
                    for s, v in props["style"].items()
                ]
                props["style"] = "".join(rules)

            # Convert props to HTML attributes.
            for key in props:
                if props[key] is False:
                    props[key] = "false"

                if props[key] is True:
                    props[key] = "true"

                if props[key] is not None:
                    attributes[key] = str(props[key])

            elt = cls.dom.create_tag(type_, attributes)

            # Append the children inside the element.
            for child in children:
                if child not in (None, ""):
                    cls.append_child(elt, child)

        # If elt is "empty", create a fragment anyway to add children.
        if elt in (None, ""):
            elt = cls.dom.create_tag("fragment")

        return elt

    @classmethod
    def parse_html(cls, markup: HTML) -> Element:
        return cls.dom.parse_html(markup)

    @classmethod
    def append_child(cls, elt: Element, child: Element) -> Any:
        return cls.dom.append_child(elt, child)

    @classmethod
    def render(cls, elt: Element) -> HTML:
        return cls.dom.render(elt)

    @classmethod
    def render_debug(cls, elt: Element) -> HTML:
        return cls.dom.render_debug(elt) class EntityState(object):
    __slots__ = (
        "entity_options",
        "entity_map",
        "entity_stack",
        "completed_entity",
        "element_stack",
    )

    def __init__(
        self, entity_options: OptionsMap, entity_map: EntityMap
    ) -> None:
        self.entity_options = entity_options
        self.entity_map = entity_map

        self.entity_stack: List[EntityKey] = []
        self.completed_entity: Optional[EntityKey] = None
        self.element_stack: List[Element] = []

    def apply(self, command: Command) -> None:
        if command.name == "start_entity":
            self.entity_stack.append(command.data)
        elif command.name == "stop_entity":
            expected_entity = self.entity_stack[-1]

            if command.data != expected_entity:
                raise EntityException(
                    f"Expected {expected_entity}, got {command.data}"
                )

            self.completed_entity = self.entity_stack.pop()

    def has_entity(self) -> List[EntityKey]:
        return self.entity_stack

    def has_no_entity(self) -> bool:
        return not self.entity_stack

    def get_entity_details(self, entity_key: EntityKey) -> EntityDetails:
        details = self.entity_map.get(entity_key)

        if details is None:
            raise EntityException(
                f'Entity "{entity_key}" does not exist in the entityMap'
            )

        return details

    def render_entities(
        self, style_node: Element, block: Block, blocks: Sequence[Block]
    ) -> Element:
        # We have a complete (start, stop) entity to render.
        if self.completed_entity is not None:
            entity_details = self.get_entity_details(self.completed_entity)
            options = Options.get(
                self.entity_options,
                entity_details["type"],
                ENTITY_TYPES.FALLBACK,
            )
            props = entity_details["data"].copy()
            props["entity"] = {
                "type": entity_details["type"],
                "mutability": entity_details["mutability"]
                if "mutability" in entity_details
                else None,
                "block": block,
                "blocks": blocks,
                "entity_range": {"key": self.completed_entity},
            }

            if len(self.element_stack) == 1:
                children = self.element_stack[0]
            else:
                children = DOM.create_element()

                for n in self.element_stack:
                    DOM.append_child(children, n)

            self.completed_entity = None
            self.element_stack = []

            # Is there still another entity? (adjacent) if so add the current style_node for it.
            if self.has_entity():
                self.element_stack.append(style_node)

            return DOM.create_element(options.element, props, children)

        if self.has_entity():
            self.element_stack.append(style_node)
            return None

        return style_node class Options(object):
    """
    Facilitates querying configuration from a config map.
    """

    __slots__ = ("type", "element", "props", "wrapper", "wrapper_props")

    def __init__(
        self,
        type_: str,
        element: RenderableType,
        props: Optional[Props] = None,
        wrapper: RenderableType = None,
        wrapper_props: Optional[Props] = None,
    ) -> None:
        self.type = type_
        self.element = element
        self.props = props if props else {}
        self.wrapper = wrapper
        self.wrapper_props = wrapper_props

    def __str__(self) -> str:
        return f"<Options {self.type} {self.element} {self.props} {self.wrapper} {self.wrapper_props}>"

    def __repr__(self) -> str:
        return str(self)

    def __eq__(self, other: Any) -> bool:
        """
        Equality used in test code only, not to be relied on for the exporter.
        """
        return str(self) == str(other)

    def __ne__(self, other: Any) -> bool:
        return not self == other

    def __hash__(self) -> int:
        return hash(str(self))

    @staticmethod
    def create(kind_map: ConfigMap, type_: str, fallback_key: str) -> "Options":
        """
        Create an Options object from any mapping.
        """
        if type_ not in kind_map:
            if fallback_key not in kind_map:
                raise ConfigException(
                    f'"{type_}" is not in the config and has no fallback'
                )

            config = kind_map[fallback_key]
        else:
            config = kind_map[type_]

        if isinstance(config, dict):
            if "element" not in config:
                raise ConfigException(f'"{type_}" does not define an element')

            opts = Options(type_, **config)
        else:
            opts = Options(type_, config)

        return opts

    @staticmethod
    def map(kind_map: ConfigMap, fallback_key: str) -> OptionsMap:
        options = {}
        for type_ in kind_map:
            options[type_] = Options.create(kind_map, type_, fallback_key)

        return options

    @staticmethod
    def map_blocks(block_map: ConfigMap) -> OptionsMap:
        return Options.map(block_map, BLOCK_TYPES.FALLBACK)

    @staticmethod
    def map_styles(style_map: ConfigMap) -> OptionsMap:
        return Options.map(style_map, INLINE_STYLES.FALLBACK)

    @staticmethod
    def map_entities(entity_map: ConfigMap) -> OptionsMap:
        return Options.map(entity_map, ENTITY_TYPES.FALLBACK)

    @staticmethod
    def get(options: OptionsMap, type_: str, fallback_key: str) -> "Options":
        try:
            return options[type_]
        except KeyError:
            try:
                return options[fallback_key]
            except KeyError:
                raise ConfigException(
                    f'"{type_}" is not in the config and has no fallback'
                ) class StyleState(object):
    """
    Handles the creation of inline styles on elements.
    Receives inline_style commands, and generates the element's `style`
    attribute from those.
    """

    __slots__ = ("styles", "style_options")

    def __init__(self, style_options: OptionsMap) -> None:
        self.styles: List[str] = []
        self.style_options = style_options

    def apply(self, command: Command) -> None:
        if command.name == "start_inline_style":
            self.styles.append(command.data)
        elif command.name == "stop_inline_style":
            self.styles.remove(command.data)

    def is_empty(self) -> bool:
        return not self.styles

    def render_styles(
        self, decorated_node: Element, block: Block, blocks: Sequence[Block]
    ) -> Element:
        node = decorated_node
        if not self.is_empty():
            # This will mutate self.styles, but it’s going to be reset after rendering anyway.
            self.styles.sort(reverse=True)

            # Nest the tags.
            for style in self.styles:
                options = Options.get(
                    self.style_options, style, INLINE_STYLES.FALLBACK
                )
                props = dict(options.props)
                props["block"] = block
                props["blocks"] = blocks
                props["inline_style_range"] = {"style": style}
                node = DOM.create_element(options.element, props, node)

        return node HTML = str class WrapperState(object):
    """
    This class does the initial node building for the tree.
    It sets elements with the right tag, text content, and props.
    It adds a wrapper element around elements, if required.
    """

    __slots__ = ("block_options", "blocks", "stack")

    def __init__(
        self, block_options: OptionsMap, blocks: Sequence[Block]
    ) -> None:
        self.block_options = block_options
        self.blocks = blocks
        self.stack = WrapperStack()

    def __str__(self) -> str:
        return f"<WrapperState: {self.stack}>"

    def element_for(
        self, block: Block, block_content: Union[Element, Sequence[Element]]
    ) -> Element:
        type_ = block["type"] if "type" in block else "unstyled"
        depth = block["depth"] if "depth" in block else 0
        options = Options.get(self.block_options, type_, BLOCK_TYPES.FALLBACK)
        props = dict(options.props)
        props["block"] = block
        props["blocks"] = self.blocks

        # Make an element from the options specified in the block map.
        elt = DOM.create_element(options.element, props, block_content)

        parent = self.parent_for(options, depth, elt)

        return parent

    def parent_for(self, options: Options, depth: int, elt: Element) -> Element:
        if options.wrapper:
            parent = self.get_wrapper_elt(options, depth)
            DOM.append_child(parent, elt)
            self.stack.stack[-1].last_child = elt
        else:
            # Reset the stack if there is no wrapper.
            if self.stack.length() > 0:
                self.stack = WrapperStack()
            parent = elt

        return parent

    def get_wrapper_elt(self, options: Options, depth: int) -> Element:
        head = self.stack.head()
        if head.is_different(depth, options.wrapper, options.wrapper_props):
            self.update_stack(options, depth)

        # If depth is lower than the maximum, we cut the stack.
        if depth < head.depth:
            self.stack.slice(depth + 1)

        return self.stack.get(depth).elt

    def update_stack(self, options: Options, depth: int) -> None:
        if depth >= self.stack.length():
            # If the depth is gte the stack length, we need more wrappers.
            depth_levels = range(self.stack.length(), depth + 1)

            for level in depth_levels:
                new_wrapper = Wrapper(level, options)

                # Determine where to append the new wrapper.
                if self.stack.head().last_child is None:
                    # If there is no content in the current wrapper, we need
                    # to add an intermediary node.
                    props = dict(options.props)
                    props["block"] = {
                        "type": options.type,
                        "depth": depth,
                        "data": {},
                    }
                    props["blocks"] = self.blocks

                    wrapper_parent = DOM.create_element(options.element, props)
                    DOM.append_child(self.stack.head().elt, wrapper_parent)
                else:
                    # Otherwise we can append at the end of the last child.
                    wrapper_parent = self.stack.head().last_child

                DOM.append_child(wrapper_parent, new_wrapper.elt)

                self.stack.append(new_wrapper)
        else:
            # Cut the stack to where it now stops, and add new wrapper.
            self.stack.slice(depth)
            self.stack.append(Wrapper(depth, options))
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(object):
    """
    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)