# API reference

## HTML exporter

Combine entity, wrapper, and style state to render Draft.js content.

This is the main entry point for converting a Draft.js content state into an HTML string.

Source code in `draftjs_exporter/html.py`

```python
class HTML:
    """Combine entity, wrapper, and style state to render Draft.js content.

    This is the main entry point for converting a Draft.js content state
    into an HTML string.
    """

    __slots__ = (
        "composite_decorators",
        "entity_options",
        "block_options",
        "style_options",
        "_engine",
    )

    def __init__(self, config: ExporterConfig | None = None) -> None:
        """Initialize the exporter with the given configuration.

        Parameters:
            config: Exporter options. Missing values use the default block
                and style maps and the string DOM engine.
        """
        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))

        self._engine = config.get("engine", DOM.STRING)

    def render(self, content_state: ContentState | None = None) -> str:
        """Render the given Draft.js content state as HTML."""
        with DOM.engine(self._engine):
            dom = DOM._dom()

            if content_state is None:
                content_state = {}

            blocks = content_state.get("blocks", [])
            wrapper_state = WrapperState(self.block_options, blocks, dom)
            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.get("depth", 0)
                elt = self.render_block(block, entity_map, wrapper_state, dom)

                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,
        dom: type[DOMEngine],
    ) -> Element:
        """Render a single block to an element.

        Parameters:
            block: The Draft.js block to render.
            entity_map: Map of entity keys to entity definitions.
            wrapper_state: Stateful wrapper that handles nesting of list items.
            dom: Active DOM engine used to create elements.

        Returns:
            The rendered block element.
        """
        text = block.get("text", "")
        has_styles = bool(block.get("inlineStyleRanges"))
        has_entities = bool(block.get("entityRanges"))
        has_decorators = should_render_decorators(self.composite_decorators, 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

            use_continuation = not (
                style_state is None or style_state.uses_components(block)
            )

            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,
                        dom,
                    )
                else:
                    decorated_node = text

                entity_active = (
                    bool(entity_state.has_entity())
                    or entity_state.completed_entity is not None
                )

                if style_state is not None and use_continuation and not entity_active:
                    innermost = style_state.start_segment(
                        block, wrapper_state.blocks, content
                    )
                    if decorated_node not in (None, ""):
                        dom.append_child(innermost, decorated_node)
                else:
                    if style_state is not None and use_continuation:
                        style_state.flush()

                    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, dom
                    )

                    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,
                text,
                block,
                wrapper_state.blocks,
                dom,
            )
        else:
            content = text

        return wrapper_state.element_for(block, content)

    def build_command_groups(self, block: Block) -> list[tuple[str, list[Command]]]:
        """Group block modification commands by start index.

        Each group is paired with the slice of text the commands apply to.

        Parameters:
            block: The Draft.js block whose commands are grouped.

        Returns:
            Tuples of ``(text slice, commands applied to the slice)``.
        """
        text = block.get("text", "")

        commands = self.build_commands(block)
        sliced = []

        start = 0
        command_count = len(commands)

        # Manual groupby on command index. build_commands sorts by index, so
        # consecutive commands with the same index form a group. Avoids
        # itertools.groupby and the extra list allocations it would create.
        while start < command_count:
            start_index = commands[start].index
            end = start + 1

            while end < command_count and commands[end].index == start_index:
                end += 1

            if end < command_count:
                stop_index = commands[end].index
                sliced.append((text[start_index:stop_index], commands[start:end]))
            else:
                sliced.append(("", commands[start:end]))

            start = end

        return sliced

    def build_commands(self, block: Block) -> list[Command]:
        """Build all manipulation commands for a block.

        The returned commands include a start and stop text marker plus one
        command for each inline style and entity range.

        Parameters:
            block: The Draft.js block to build commands from.

        Returns:
            The ordered list of manipulation commands.
        """
        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.get("text", "")))]
        )
```

#### `__init__(config=None)`

Initialize the exporter with the given configuration.

Parameters:

| Name     | Type             | Description | Default                                                                                          |
| -------- | ---------------- | ----------- | ------------------------------------------------------------------------------------------------ |
| `config` | \`ExporterConfig | None\`      | Exporter options. Missing values use the default block and style maps and the string DOM engine. |

Source code in `draftjs_exporter/html.py`

```python
def __init__(self, config: ExporterConfig | None = None) -> None:
    """Initialize the exporter with the given configuration.

    Parameters:
        config: Exporter options. Missing values use the default block
            and style maps and the string DOM engine.
    """
    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))

    self._engine = config.get("engine", DOM.STRING)
```

#### `build_command_groups(block)`

Group block modification commands by start index.

Each group is paired with the slice of text the commands apply to.

Parameters:

| Name    | Type    | Description                                    | Default    |
| ------- | ------- | ---------------------------------------------- | ---------- |
| `block` | `Block` | The Draft.js block whose commands are grouped. | *required* |

Returns:

| Type                              | Description                                            |
| --------------------------------- | ------------------------------------------------------ |
| `list[tuple[str, list[Command]]]` | Tuples of (text slice, commands applied to the slice). |

Source code in `draftjs_exporter/html.py`

```python
def build_command_groups(self, block: Block) -> list[tuple[str, list[Command]]]:
    """Group block modification commands by start index.

    Each group is paired with the slice of text the commands apply to.

    Parameters:
        block: The Draft.js block whose commands are grouped.

    Returns:
        Tuples of ``(text slice, commands applied to the slice)``.
    """
    text = block.get("text", "")

    commands = self.build_commands(block)
    sliced = []

    start = 0
    command_count = len(commands)

    # Manual groupby on command index. build_commands sorts by index, so
    # consecutive commands with the same index form a group. Avoids
    # itertools.groupby and the extra list allocations it would create.
    while start < command_count:
        start_index = commands[start].index
        end = start + 1

        while end < command_count and commands[end].index == start_index:
            end += 1

        if end < command_count:
            stop_index = commands[end].index
            sliced.append((text[start_index:stop_index], commands[start:end]))
        else:
            sliced.append(("", commands[start:end]))

        start = end

    return sliced
```

#### `build_commands(block)`

Build all manipulation commands for a block.

The returned commands include a start and stop text marker plus one command for each inline style and entity range.

Parameters:

| Name    | Type    | Description                                | Default    |
| ------- | ------- | ------------------------------------------ | ---------- |
| `block` | `Block` | The Draft.js block to build commands from. | *required* |

Returns:

| Type            | Description                                |
| --------------- | ------------------------------------------ |
| `list[Command]` | The ordered list of manipulation commands. |

Source code in `draftjs_exporter/html.py`

```python
def build_commands(self, block: Block) -> list[Command]:
    """Build all manipulation commands for a block.

    The returned commands include a start and stop text marker plus one
    command for each inline style and entity range.

    Parameters:
        block: The Draft.js block to build commands from.

    Returns:
        The ordered list of manipulation commands.
    """
    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.get("text", "")))]
    )
```

#### `render(content_state=None)`

Render the given Draft.js content state as HTML.

Source code in `draftjs_exporter/html.py`

```python
def render(self, content_state: ContentState | None = None) -> str:
    """Render the given Draft.js content state as HTML."""
    with DOM.engine(self._engine):
        dom = DOM._dom()

        if content_state is None:
            content_state = {}

        blocks = content_state.get("blocks", [])
        wrapper_state = WrapperState(self.block_options, blocks, dom)
        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.get("depth", 0)
            elt = self.render_block(block, entity_map, wrapper_state, dom)

            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)
```

#### `render_block(block, entity_map, wrapper_state, dom)`

Render a single block to an element.

Parameters:

| Name            | Type              | Description                                          | Default    |
| --------------- | ----------------- | ---------------------------------------------------- | ---------- |
| `block`         | `Block`           | The Draft.js block to render.                        | *required* |
| `entity_map`    | `EntityMap`       | Map of entity keys to entity definitions.            | *required* |
| `wrapper_state` | `WrapperState`    | Stateful wrapper that handles nesting of list items. | *required* |
| `dom`           | `type[DOMEngine]` | Active DOM engine used to create elements.           | *required* |

Returns:

| Type      | Description                 |
| --------- | --------------------------- |
| `Element` | The rendered block element. |

Source code in `draftjs_exporter/html.py`

```python
def render_block(
    self,
    block: Block,
    entity_map: EntityMap,
    wrapper_state: WrapperState,
    dom: type[DOMEngine],
) -> Element:
    """Render a single block to an element.

    Parameters:
        block: The Draft.js block to render.
        entity_map: Map of entity keys to entity definitions.
        wrapper_state: Stateful wrapper that handles nesting of list items.
        dom: Active DOM engine used to create elements.

    Returns:
        The rendered block element.
    """
    text = block.get("text", "")
    has_styles = bool(block.get("inlineStyleRanges"))
    has_entities = bool(block.get("entityRanges"))
    has_decorators = should_render_decorators(self.composite_decorators, 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

        use_continuation = not (
            style_state is None or style_state.uses_components(block)
        )

        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,
                    dom,
                )
            else:
                decorated_node = text

            entity_active = (
                bool(entity_state.has_entity())
                or entity_state.completed_entity is not None
            )

            if style_state is not None and use_continuation and not entity_active:
                innermost = style_state.start_segment(
                    block, wrapper_state.blocks, content
                )
                if decorated_node not in (None, ""):
                    dom.append_child(innermost, decorated_node)
            else:
                if style_state is not None and use_continuation:
                    style_state.flush()

                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, dom
                )

                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,
            text,
            block,
            wrapper_state.blocks,
            dom,
        )
    else:
        content = text

    return wrapper_state.element_for(block, content)
```

## DOM

Provide a DOM-building API that abstracts the active engine.

Source code in `draftjs_exporter/dom.py`

```python
class DOM:
    """Provide a DOM-building API that abstracts the active engine."""

    HTML5LIB = "draftjs_exporter.engines.html5lib.DOM_HTML5LIB"
    """Identifier for the html5lib DOM engine."""
    LXML = "draftjs_exporter.engines.lxml.DOM_LXML"
    """Identifier for the lxml DOM engine."""
    MARKDOWN = "draftjs_exporter.engines.markdown.DOMMarkdown"
    """Identifier for the Markdown DOM engine."""
    STRING = "draftjs_exporter.engines.string.DOMString"
    """Identifier for the string DOM engine."""
    STRING_COMPAT = "draftjs_exporter.engines.string_compat.DOMStringCompat"
    """Identifier for the string compatibility DOM engine."""

    @staticmethod
    def camel_to_dash(camel_cased_str: str) -> str:
        """Convert a camelCase string to a dashed-case attribute name."""
        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 _dom(cls) -> type[DOMEngine]:
        engine = _engine_var.get()
        if engine is None:
            raise RuntimeError(
                "No DOM engine set. Call DOM.use() or use HTML() to configure an engine."
            )
        return engine

    @classmethod
    def use(cls, engine: str) -> None:
        """Select the DOM implementation for the current context."""
        try:
            resolved = _engine_cache[engine]
        except KeyError:
            resolved = _engine_cache[engine] = cast(
                type[DOMEngine], import_string(engine)
            )
        _engine_var.set(resolved)

    @staticmethod
    @contextmanager
    def engine(engine: str) -> Iterator[None]:
        """Temporarily set the DOM engine for the current context."""
        try:
            resolved = _engine_cache[engine]
        except KeyError:
            resolved = _engine_cache[engine] = cast(
                type[DOMEngine], import_string(engine)
            )
        token = _engine_var.set(resolved)
        try:
            yield
        finally:
            _engine_var.reset(token)

    @classmethod
    def create_element(
        cls,
        type_: RenderableType = None,
        props: Props | None = None,
        *elt_children: Element | None,
    ) -> Element:
        """Create an element or document fragment using the current engine.

        The signature mirrors React.createElement: a type, an optional props
        dictionary, and zero or more children.

        Parameters:
            type_: The tag name, component, or ``None`` for a fragment.
            props: Attributes and metadata to pass to the element.
            *elt_children: Child nodes to append to the element.

        Returns:
            The constructed element.
        """
        dom = cls._dom()
        # Create an empty document fragment.
        if not type_:
            return 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 elt_children and isinstance(elt_children[0], (list, tuple)):
            children = elt_children[0]
        else:
            children = elt_children

        children_len = len(children)

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

        if callable(type_):
            elt = cast(Component, 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 = 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 = dom.create_tag("fragment")

        return elt

    @classmethod
    def parse_html(cls, markup: HTML) -> Element:
        """Parse an HTML string into an element using the current engine."""
        return cls._dom().parse_html(markup)

    @classmethod
    def append_child(cls, elt: Element, child: Element) -> Any:
        """Append a child node to an element."""
        return cls._dom().append_child(elt, child)

    @classmethod
    def render(cls, elt: Element) -> HTML:
        """Render an element tree to its final HTML output."""
        return cls._dom().render(elt)

    @classmethod
    def render_debug(cls, elt: Element) -> HTML:
        """Render an element tree to a debug-friendly representation."""
        return cls._dom().render_debug(elt)
```

#### `HTML5LIB = 'draftjs_exporter.engines.html5lib.DOM_HTML5LIB'`

Identifier for the html5lib DOM engine.

#### `LXML = 'draftjs_exporter.engines.lxml.DOM_LXML'`

Identifier for the lxml DOM engine.

#### `MARKDOWN = 'draftjs_exporter.engines.markdown.DOMMarkdown'`

Identifier for the Markdown DOM engine.

#### `STRING = 'draftjs_exporter.engines.string.DOMString'`

Identifier for the string DOM engine.

#### `STRING_COMPAT = 'draftjs_exporter.engines.string_compat.DOMStringCompat'`

Identifier for the string compatibility DOM engine.

#### `append_child(elt, child)`

Append a child node to an element.

Source code in `draftjs_exporter/dom.py`

```python
@classmethod
def append_child(cls, elt: Element, child: Element) -> Any:
    """Append a child node to an element."""
    return cls._dom().append_child(elt, child)
```

#### `camel_to_dash(camel_cased_str)`

Convert a camelCase string to a dashed-case attribute name.

Source code in `draftjs_exporter/dom.py`

```python
@staticmethod
def camel_to_dash(camel_cased_str: str) -> str:
    """Convert a camelCase string to a dashed-case attribute name."""
    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("--", "-")
```

#### `create_element(type_=None, props=None, *elt_children)`

Create an element or document fragment using the current engine.

The signature mirrors React.createElement: a type, an optional props dictionary, and zero or more children.

Parameters:

| Name            | Type             | Description                                      | Default                                         |
| --------------- | ---------------- | ------------------------------------------------ | ----------------------------------------------- |
| `type_`         | `RenderableType` | The tag name, component, or None for a fragment. | `None`                                          |
| `props`         | \`Props          | None\`                                           | Attributes and metadata to pass to the element. |
| `*elt_children` | \`Element        | None\`                                           | Child nodes to append to the element.           |

Returns:

| Type      | Description              |
| --------- | ------------------------ |
| `Element` | The constructed element. |

Source code in `draftjs_exporter/dom.py`

```python
@classmethod
def create_element(
    cls,
    type_: RenderableType = None,
    props: Props | None = None,
    *elt_children: Element | None,
) -> Element:
    """Create an element or document fragment using the current engine.

    The signature mirrors React.createElement: a type, an optional props
    dictionary, and zero or more children.

    Parameters:
        type_: The tag name, component, or ``None`` for a fragment.
        props: Attributes and metadata to pass to the element.
        *elt_children: Child nodes to append to the element.

    Returns:
        The constructed element.
    """
    dom = cls._dom()
    # Create an empty document fragment.
    if not type_:
        return 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 elt_children and isinstance(elt_children[0], (list, tuple)):
        children = elt_children[0]
    else:
        children = elt_children

    children_len = len(children)

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

    if callable(type_):
        elt = cast(Component, 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 = 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 = dom.create_tag("fragment")

    return elt
```

#### `engine(engine)`

Temporarily set the DOM engine for the current context.

Source code in `draftjs_exporter/dom.py`

```python
@staticmethod
@contextmanager
def engine(engine: str) -> Iterator[None]:
    """Temporarily set the DOM engine for the current context."""
    try:
        resolved = _engine_cache[engine]
    except KeyError:
        resolved = _engine_cache[engine] = cast(
            type[DOMEngine], import_string(engine)
        )
    token = _engine_var.set(resolved)
    try:
        yield
    finally:
        _engine_var.reset(token)
```

#### `parse_html(markup)`

Parse an HTML string into an element using the current engine.

Source code in `draftjs_exporter/dom.py`

```python
@classmethod
def parse_html(cls, markup: HTML) -> Element:
    """Parse an HTML string into an element using the current engine."""
    return cls._dom().parse_html(markup)
```

#### `render(elt)`

Render an element tree to its final HTML output.

Source code in `draftjs_exporter/dom.py`

```python
@classmethod
def render(cls, elt: Element) -> HTML:
    """Render an element tree to its final HTML output."""
    return cls._dom().render(elt)
```

#### `render_debug(elt)`

Render an element tree to a debug-friendly representation.

Source code in `draftjs_exporter/dom.py`

```python
@classmethod
def render_debug(cls, elt: Element) -> HTML:
    """Render an element tree to a debug-friendly representation."""
    return cls._dom().render_debug(elt)
```

#### `use(engine)`

Select the DOM implementation for the current context.

Source code in `draftjs_exporter/dom.py`

```python
@classmethod
def use(cls, engine: str) -> None:
    """Select the DOM implementation for the current context."""
    try:
        resolved = _engine_cache[engine]
    except KeyError:
        resolved = _engine_cache[engine] = cast(
            type[DOMEngine], import_string(engine)
        )
    _engine_var.set(resolved)
```

## Constants

Draft.js-compatible constants used by the exporter.

Defines the block types, inline styles, and entity categories supported by Draft.js, plus a small enum helper.

#### `ENTITY_TYPES = Enum('LINK', 'DOCUMENT', 'IMAGE', 'EMBED', 'HORIZONTAL_RULE', 'FALLBACK')`

Draft.js entity categories.

#### `INLINE_STYLES = Enum('BOLD', 'CODE', 'ITALIC', 'UNDERLINE', 'STRIKETHROUGH', 'SUPERSCRIPT', 'SUBSCRIPT', 'MARK', 'QUOTATION', 'SMALL', 'SAMPLE', 'INSERT', 'DELETE', 'KEYBOARD', 'FALLBACK')`

Draft.js inline style names.

#### `BLOCK_TYPES`

Draft.js block types mapped to HTML elements.

Source code in `draftjs_exporter/constants.py`

```python
class BLOCK_TYPES:
    """Draft.js block types mapped to HTML elements."""

    UNSTYLED = "unstyled"
    HEADER_ONE = "header-one"
    HEADER_TWO = "header-two"
    HEADER_THREE = "header-three"
    HEADER_FOUR = "header-four"
    HEADER_FIVE = "header-five"
    HEADER_SIX = "header-six"
    UNORDERED_LIST_ITEM = "unordered-list-item"
    ORDERED_LIST_ITEM = "ordered-list-item"
    BLOCKQUOTE = "blockquote"
    PRE = "pre"
    CODE = "code-block"
    ATOMIC = "atomic"
    # Special type to configure handling of missing components.
    FALLBACK = "fallback"
```

#### `Enum`

Minimal enum-like container exposing a fixed set of string attributes.

Accessing a registered attribute returns its name. Accessing any other attribute raises `AttributeError`.

Source code in `draftjs_exporter/constants.py`

```python
class Enum:
    """Minimal enum-like container exposing a fixed set of string attributes.

    Accessing a registered attribute returns its name. Accessing any other
    attribute raises ``AttributeError``.
    """

    __slots__ = "elements"

    def __init__(self, *elements: str) -> None:
        """Store the allowed attribute names for the enum."""
        self.elements = tuple(elements)

    def __getattr__(self, name: str) -> str:
        """Return the attribute name if it is a registered enum element.

        Parameters:
            name: Attribute name being accessed.

        Returns:
            The requested attribute name.

        Raises:
            AttributeError: If the name is not a registered element.
        """
        if name not in self.elements:
            raise AttributeError(f"'Enum' has no attribute '{name}'")

        return name
```

##### `__getattr__(name)`

Return the attribute name if it is a registered enum element.

Parameters:

| Name   | Type  | Description                    | Default    |
| ------ | ----- | ------------------------------ | ---------- |
| `name` | `str` | Attribute name being accessed. | *required* |

Returns:

| Type  | Description                   |
| ----- | ----------------------------- |
| `str` | The requested attribute name. |

Raises:

| Type             | Description                              |
| ---------------- | ---------------------------------------- |
| `AttributeError` | If the name is not a registered element. |

Source code in `draftjs_exporter/constants.py`

```python
def __getattr__(self, name: str) -> str:
    """Return the attribute name if it is a registered enum element.

    Parameters:
        name: Attribute name being accessed.

    Returns:
        The requested attribute name.

    Raises:
        AttributeError: If the name is not a registered element.
    """
    if name not in self.elements:
        raise AttributeError(f"'Enum' has no attribute '{name}'")

    return name
```

##### `__init__(*elements)`

Store the allowed attribute names for the enum.

Source code in `draftjs_exporter/constants.py`

```python
def __init__(self, *elements: str) -> None:
    """Store the allowed attribute names for the enum."""
    self.elements = tuple(elements)
```

## Default maps

Default block and style maps for the exporter.

The mappings connect Draft.js block types and inline styles to HTML tags or component functions.

#### `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}`

Default mapping from block types to renderable tags or components.

#### `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'}`

Default mapping from inline styles to HTML tags.

#### `code_block(props)`

Render a code block inside a `pre` > `code` element tree.

Source code in `draftjs_exporter/defaults.py`

```python
def code_block(props: Props) -> Element:
    """Render a code block inside a ``pre`` > ``code`` element tree."""
    return DOM.create_element(
        "pre", {}, DOM.create_element("code", {}, props["children"])
    )
```

#### `render_children(props)`

Return the children of a component without wrapping markup.

Source code in `draftjs_exporter/defaults.py`

```python
def render_children(props: Props) -> Element:
    """Return the children of a component without wrapping markup."""
    return props["children"]
```

## Types

Shared type aliases and TypedDict structures for Draft.js data.

Use these types when annotating custom components, decorators, or configuration maps consumed by the exporter.

#### `Component = Callable[[Props], Element]`

Render a component from props and return an element.

#### `CompositeDecorators = list[Decorator]`

List composite decorators applied while rendering blocks.

#### `ConfigMap = dict[str, RenderableConfig | RenderableType]`

Map string keys to renderable configurations or values.

#### `Element = Any`

Engine-specific element produced by a renderable.

#### `EntityKey = str`

Key used to look up an entity in the entity map.

#### `EntityMap = dict[EntityKey, Entity]`

Map entity keys to their definitions.

#### `HTML = str`

Final rendered output of the exporter.

#### `Mutability = Literal['MUTABLE', 'IMMUTABLE', 'SEGMENTED']`

Draft.js entity mutability setting.

#### `Props = dict[str, Any]`

Dictionary of string attribute keys to arbitrary values.

#### `RenderableType = Component | Tag | None`

A tag name, component function, or `None` for a fragment.

#### `Tag = str`

HTML tag name.

#### `Block`

Bases: `TypedDict`

Single Draft.js block within a content state.

Source code in `draftjs_exporter/types.py`

```python
class Block(TypedDict, total=False):
    """Single Draft.js block within a content state."""

    key: str
    text: str
    type: str
    depth: int
    data: dict[str, Any]
    inlineStyleRanges: list[InlineStyleRange]
    entityRanges: list[EntityRange]
```

#### `ContentState`

Bases: `TypedDict`

Top-level Draft.js content state passed to the exporter.

Source code in `draftjs_exporter/types.py`

```python
class ContentState(TypedDict, total=False):
    """Top-level Draft.js content state passed to the exporter."""

    blocks: list[Block]
    entityMap: EntityMap
```

#### `Decorator`

Bases: `TypedDict`

Pattern and component used to decorate matching text.

Source code in `draftjs_exporter/types.py`

```python
class Decorator(TypedDict):
    """Pattern and component used to decorate matching text."""

    strategy: re.Pattern[str]
    component: RenderableType
```

#### `Entity`

Bases: `TypedDict`

Draft.js entity data referenced by entity ranges.

Source code in `draftjs_exporter/types.py`

```python
class Entity(TypedDict, total=False):
    """Draft.js entity data referenced by entity ranges."""

    type: str
    data: dict[str, Any]
    mutability: Mutability
```

#### `EntityRange`

Bases: `TypedDict`

Range of text associated with a single entity.

Source code in `draftjs_exporter/types.py`

```python
class EntityRange(TypedDict):
    """Range of text associated with a single entity."""

    offset: int
    length: int
    key: int
```

#### `InlineStyleRange`

Bases: `TypedDict`

Range of text styled by a single inline style.

Source code in `draftjs_exporter/types.py`

```python
class InlineStyleRange(TypedDict):
    """Range of text styled by a single inline style."""

    offset: int
    length: int
    style: str
```

#### `RenderableConfig`

Bases: `TypedDict`

Configuration describing how to render a single renderable.

Source code in `draftjs_exporter/types.py`

```python
class RenderableConfig(TypedDict, total=False):
    """Configuration describing how to render a single renderable."""

    # TODO Use typing.Required when dropping Python 3.10 support.
    # See https://peps.python.org/pep-0655/.
    element: RenderableType
    props: Props
    wrapper: RenderableType
    wrapper_props: Props
```

## Options

Normalize renderer configuration maps into internal option objects.

#### `Options`

Store and query normalized configuration for a single renderable type.

Source code in `draftjs_exporter/options.py`

```python
class Options:
    """Store and query normalized configuration for a single renderable type."""

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

    def __init__(
        self,
        type_: str,
        element: RenderableType,
        props: Props | None = None,
        wrapper: RenderableType = None,
        wrapper_props: Props | None = None,
    ) -> None:
        """Initialize options for a renderable type.

        Parameters:
            type_: The Draft.js type being configured.
            element: The element or component used to render this type.
            props: Default props to pass to the element.
            wrapper: Optional wrapper element used for nested blocks.
            wrapper_props: Props to pass to the wrapper element.
        """
        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 a human-readable representation of the options."""
        return f"<Options {self.type} {self.element} {self.props} {self.wrapper} {self.wrapper_props}>"

    def __repr__(self) -> str:
        """Return the same representation as ``__str__`` for debugging."""
        return str(self)

    def __eq__(self, other: Any) -> bool:
        """Compare options for equality.

        This comparison is intended for test assertions only and should not be
        relied on by the exporter at runtime.

        Parameters:
            other: The other object to compare with.

        Returns:
            True when the string representations match.
        """
        return str(self) == str(other)

    def __ne__(self, other: Any) -> bool:
        """Return whether the options are not equal to another object."""
        return not self == other

    def __hash__(self) -> int:
        """Return a hash based on the string representation."""
        return hash(str(self))

    @staticmethod
    def create(kind_map: ConfigMap, type_: str, fallback_key: str) -> "Options":
        """Create an Options object from a config map.

        Parameters:
            kind_map: The user-provided configuration map.
            type_: The type to look up in the map.
            fallback_key: The key to use when ``type_`` is missing.

        Returns:
            The normalized options.

        Raises:
            ConfigException: If the type and fallback are both missing or the config defines no element.
        """
        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_]

        # TODO Refactor to a TypeGuard when support for those improves.
        if isinstance(config, dict):
            if "element" not in config:
                raise ConfigException(f'"{type_}" does not define an element')

            # TODO Remove cast once ty support improves.
            opts = Options(type_, **cast(RenderableConfig, config))
        else:
            # TODO Remove cast once ty support improves.
            opts = Options(type_, cast(RenderableType, config))

        return opts

    @staticmethod
    def map(kind_map: ConfigMap, fallback_key: str) -> OptionsMap:
        """Create an OptionsMap from each entry in a config map.

        Parameters:
            kind_map: The user-provided configuration map.
            fallback_key: The fallback key passed to ``Options.create``.

        Returns:
            A mapping from type to normalized options.
        """
        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:
        """Create an OptionsMap from a block configuration map.

        Parameters:
            block_map: The user-provided block map.

        Returns:
            A mapping from block type to normalized options.
        """
        return Options.map(block_map, BLOCK_TYPES.FALLBACK)

    @staticmethod
    def map_styles(style_map: ConfigMap) -> OptionsMap:
        """Create an OptionsMap from a style configuration map.

        Parameters:
            style_map: The user-provided style map.

        Returns:
            A mapping from style name to normalized options.
        """
        return Options.map(style_map, INLINE_STYLES.FALLBACK)

    @staticmethod
    def map_entities(entity_map: ConfigMap) -> OptionsMap:
        """Create an OptionsMap from an entity configuration map.

        Parameters:
            entity_map: The user-provided entity decorators map.

        Returns:
            A mapping from entity type to normalized options.
        """
        return Options.map(entity_map, ENTITY_TYPES.FALLBACK)

    @staticmethod
    def get(options: OptionsMap, type_: str, fallback_key: str) -> "Options":
        """Return existing options from a map, falling back when needed.

        Parameters:
            options: An existing normalized options map.
            type_: The type to look up.
            fallback_key: The key to use when ``type_`` is missing.

        Returns:
            The matching options.

        Raises:
            ConfigException: If neither the type nor the fallback is present.
        """
        try:
            return options[type_]
        except KeyError:
            try:
                return options[fallback_key]
            except KeyError as err:
                raise ConfigException(
                    f'"{type_}" is not in the config and has no fallback'
                ) from err
```

##### `__eq__(other)`

Compare options for equality.

This comparison is intended for test assertions only and should not be relied on by the exporter at runtime.

Parameters:

| Name    | Type  | Description                       | Default    |
| ------- | ----- | --------------------------------- | ---------- |
| `other` | `Any` | The other object to compare with. | *required* |

Returns:

| Type   | Description                                 |
| ------ | ------------------------------------------- |
| `bool` | True when the string representations match. |

Source code in `draftjs_exporter/options.py`

```python
def __eq__(self, other: Any) -> bool:
    """Compare options for equality.

    This comparison is intended for test assertions only and should not be
    relied on by the exporter at runtime.

    Parameters:
        other: The other object to compare with.

    Returns:
        True when the string representations match.
    """
    return str(self) == str(other)
```

##### `__hash__()`

Return a hash based on the string representation.

Source code in `draftjs_exporter/options.py`

```python
def __hash__(self) -> int:
    """Return a hash based on the string representation."""
    return hash(str(self))
```

##### `__init__(type_, element, props=None, wrapper=None, wrapper_props=None)`

Initialize options for a renderable type.

Parameters:

| Name            | Type             | Description                                        | Default                               |
| --------------- | ---------------- | -------------------------------------------------- | ------------------------------------- |
| `type_`         | `str`            | The Draft.js type being configured.                | *required*                            |
| `element`       | `RenderableType` | The element or component used to render this type. | *required*                            |
| `props`         | \`Props          | None\`                                             | Default props to pass to the element. |
| `wrapper`       | `RenderableType` | Optional wrapper element used for nested blocks.   | `None`                                |
| `wrapper_props` | \`Props          | None\`                                             | Props to pass to the wrapper element. |

Source code in `draftjs_exporter/options.py`

```python
def __init__(
    self,
    type_: str,
    element: RenderableType,
    props: Props | None = None,
    wrapper: RenderableType = None,
    wrapper_props: Props | None = None,
) -> None:
    """Initialize options for a renderable type.

    Parameters:
        type_: The Draft.js type being configured.
        element: The element or component used to render this type.
        props: Default props to pass to the element.
        wrapper: Optional wrapper element used for nested blocks.
        wrapper_props: Props to pass to the wrapper element.
    """
    self.type = type_
    self.element = element
    self.props = props if props else {}
    self.wrapper = wrapper
    self.wrapper_props = wrapper_props
```

##### `__ne__(other)`

Return whether the options are not equal to another object.

Source code in `draftjs_exporter/options.py`

```python
def __ne__(self, other: Any) -> bool:
    """Return whether the options are not equal to another object."""
    return not self == other
```

##### `__repr__()`

Return the same representation as `__str__` for debugging.

Source code in `draftjs_exporter/options.py`

```python
def __repr__(self) -> str:
    """Return the same representation as ``__str__`` for debugging."""
    return str(self)
```

##### `__str__()`

Return a human-readable representation of the options.

Source code in `draftjs_exporter/options.py`

```python
def __str__(self) -> str:
    """Return a human-readable representation of the options."""
    return f"<Options {self.type} {self.element} {self.props} {self.wrapper} {self.wrapper_props}>"
```

##### `create(kind_map, type_, fallback_key)`

Create an Options object from a config map.

Parameters:

| Name           | Type        | Description                            | Default    |
| -------------- | ----------- | -------------------------------------- | ---------- |
| `kind_map`     | `ConfigMap` | The user-provided configuration map.   | *required* |
| `type_`        | `str`       | The type to look up in the map.        | *required* |
| `fallback_key` | `str`       | The key to use when type\_ is missing. | *required* |

Returns:

| Type      | Description             |
| --------- | ----------------------- |
| `Options` | The normalized options. |

Raises:

| Type              | Description                                                                 |
| ----------------- | --------------------------------------------------------------------------- |
| `ConfigException` | If the type and fallback are both missing or the config defines no element. |

Source code in `draftjs_exporter/options.py`

```python
@staticmethod
def create(kind_map: ConfigMap, type_: str, fallback_key: str) -> "Options":
    """Create an Options object from a config map.

    Parameters:
        kind_map: The user-provided configuration map.
        type_: The type to look up in the map.
        fallback_key: The key to use when ``type_`` is missing.

    Returns:
        The normalized options.

    Raises:
        ConfigException: If the type and fallback are both missing or the config defines no element.
    """
    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_]

    # TODO Refactor to a TypeGuard when support for those improves.
    if isinstance(config, dict):
        if "element" not in config:
            raise ConfigException(f'"{type_}" does not define an element')

        # TODO Remove cast once ty support improves.
        opts = Options(type_, **cast(RenderableConfig, config))
    else:
        # TODO Remove cast once ty support improves.
        opts = Options(type_, cast(RenderableType, config))

    return opts
```

##### `get(options, type_, fallback_key)`

Return existing options from a map, falling back when needed.

Parameters:

| Name           | Type         | Description                            | Default    |
| -------------- | ------------ | -------------------------------------- | ---------- |
| `options`      | `OptionsMap` | An existing normalized options map.    | *required* |
| `type_`        | `str`        | The type to look up.                   | *required* |
| `fallback_key` | `str`        | The key to use when type\_ is missing. | *required* |

Returns:

| Type      | Description           |
| --------- | --------------------- |
| `Options` | The matching options. |

Raises:

| Type              | Description                                      |
| ----------------- | ------------------------------------------------ |
| `ConfigException` | If neither the type nor the fallback is present. |

Source code in `draftjs_exporter/options.py`

```python
@staticmethod
def get(options: OptionsMap, type_: str, fallback_key: str) -> "Options":
    """Return existing options from a map, falling back when needed.

    Parameters:
        options: An existing normalized options map.
        type_: The type to look up.
        fallback_key: The key to use when ``type_`` is missing.

    Returns:
        The matching options.

    Raises:
        ConfigException: If neither the type nor the fallback is present.
    """
    try:
        return options[type_]
    except KeyError:
        try:
            return options[fallback_key]
        except KeyError as err:
            raise ConfigException(
                f'"{type_}" is not in the config and has no fallback'
            ) from err
```

##### `map(kind_map, fallback_key)`

Create an OptionsMap from each entry in a config map.

Parameters:

| Name           | Type        | Description                                | Default    |
| -------------- | ----------- | ------------------------------------------ | ---------- |
| `kind_map`     | `ConfigMap` | The user-provided configuration map.       | *required* |
| `fallback_key` | `str`       | The fallback key passed to Options.create. | *required* |

Returns:

| Type         | Description                                |
| ------------ | ------------------------------------------ |
| `OptionsMap` | A mapping from type to normalized options. |

Source code in `draftjs_exporter/options.py`

```python
@staticmethod
def map(kind_map: ConfigMap, fallback_key: str) -> OptionsMap:
    """Create an OptionsMap from each entry in a config map.

    Parameters:
        kind_map: The user-provided configuration map.
        fallback_key: The fallback key passed to ``Options.create``.

    Returns:
        A mapping from type to normalized options.
    """
    options = {}
    for type_ in kind_map:
        options[type_] = Options.create(kind_map, type_, fallback_key)

    return options
```

##### `map_blocks(block_map)`

Create an OptionsMap from a block configuration map.

Parameters:

| Name        | Type        | Description                  | Default    |
| ----------- | ----------- | ---------------------------- | ---------- |
| `block_map` | `ConfigMap` | The user-provided block map. | *required* |

Returns:

| Type         | Description                                      |
| ------------ | ------------------------------------------------ |
| `OptionsMap` | A mapping from block type to normalized options. |

Source code in `draftjs_exporter/options.py`

```python
@staticmethod
def map_blocks(block_map: ConfigMap) -> OptionsMap:
    """Create an OptionsMap from a block configuration map.

    Parameters:
        block_map: The user-provided block map.

    Returns:
        A mapping from block type to normalized options.
    """
    return Options.map(block_map, BLOCK_TYPES.FALLBACK)
```

##### `map_entities(entity_map)`

Create an OptionsMap from an entity configuration map.

Parameters:

| Name         | Type        | Description                              | Default    |
| ------------ | ----------- | ---------------------------------------- | ---------- |
| `entity_map` | `ConfigMap` | The user-provided entity decorators map. | *required* |

Returns:

| Type         | Description                                       |
| ------------ | ------------------------------------------------- |
| `OptionsMap` | A mapping from entity type to normalized options. |

Source code in `draftjs_exporter/options.py`

```python
@staticmethod
def map_entities(entity_map: ConfigMap) -> OptionsMap:
    """Create an OptionsMap from an entity configuration map.

    Parameters:
        entity_map: The user-provided entity decorators map.

    Returns:
        A mapping from entity type to normalized options.
    """
    return Options.map(entity_map, ENTITY_TYPES.FALLBACK)
```

##### `map_styles(style_map)`

Create an OptionsMap from a style configuration map.

Parameters:

| Name        | Type        | Description                  | Default    |
| ----------- | ----------- | ---------------------------- | ---------- |
| `style_map` | `ConfigMap` | The user-provided style map. | *required* |

Returns:

| Type         | Description                                      |
| ------------ | ------------------------------------------------ |
| `OptionsMap` | A mapping from style name to normalized options. |

Source code in `draftjs_exporter/options.py`

```python
@staticmethod
def map_styles(style_map: ConfigMap) -> OptionsMap:
    """Create an OptionsMap from a style configuration map.

    Parameters:
        style_map: The user-provided style map.

    Returns:
        A mapping from style name to normalized options.
    """
    return Options.map(style_map, INLINE_STYLES.FALLBACK)
```

## Error

Custom exceptions raised by the exporter.

#### `ConfigException`

Bases: `ExporterException`

Raised when the exporter configuration is invalid or unsupported.

Source code in `draftjs_exporter/error.py`

```python
class ConfigException(ExporterException):
    """Raised when the exporter configuration is invalid or unsupported."""
```

#### `ExporterException`

Bases: `Exception`

Base exception for all exporter errors.

Source code in `draftjs_exporter/error.py`

```python
class ExporterException(Exception):
    """Base exception for all exporter errors."""
```
