You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python实现Draft.js文本块转HTML遇阻,求解决方案

问题分析

原代码的核心问题在于:

  • 对每个字符遍历所有样式规则,导致文本重复输出,且样式标签的开启/闭合逻辑混乱
  • 未处理多样式叠加时的标签嵌套顺序
  • 缺少对entityRanges中LINK类型的处理逻辑
解决方案

下面是修复后的代码,同时支持inline样式和LINK实体的转换,通过事件驱动的方式处理所有样式/实体的开启和闭合,确保标签顺序正确:

首先定义样式和实体的标签映射:

from operator import itemgetter

# 内联样式标签映射
STYLE_TAGS = {
    "STRIKE": ("<strike>", "</strike>"),
    "BOLD": ("<b>", "</b>"),
    "ITALIC": ("<i>", "</i>"),
}

# 实体类型标签映射(以LINK为例)
ENTITY_TAGS = {
    "LINK": lambda data: (f'<a href="{data["url"]}">', "</a>"),
}

然后实现转换函数,步骤如下:

  1. 收集所有样式的「开启事件」和「闭合事件」
  2. 收集所有实体的「开启事件」和「闭合事件」
  3. 合并所有事件并按位置排序
  4. 遍历文本位置,依次处理事件和文本内容
  5. 维护样式栈,确保闭合标签顺序正确
def draft_block_to_html(block, entities=None):
    text = block["text"]
    inline_styles = block.get("inlineStyleRanges", [])
    entity_ranges = block.get("entityRanges", [])
    entities = entities or {}  # 实体数据,格式:{entity_key: {type: "LINK", data: {url: "..."}}}
    events = []

    # 1. 处理内联样式事件:添加开启和闭合事件
    for style in inline_styles:
        offset = style["offset"]
        end_offset = offset + style["length"]
        style_name = style["style"]
        if style_name not in STYLE_TAGS:
            continue
        # 开启事件:(位置, "open", 样式名)
        events.append((offset, "open", "style", style_name))
        # 闭合事件:(位置, "close", 样式名)
        events.append((end_offset, "close", "style", style_name))

    # 2. 处理实体事件:添加开启和闭合事件(以LINK为例)
    for entity_range in entity_ranges:
        offset = entity_range["offset"]
        end_offset = offset + entity_range["length"]
        entity_key = entity_range["key"]
        entity = entities.get(entity_key)
        if not entity or entity["type"] not in ENTITY_TAGS:
            continue
        # 开启事件:(位置, "open", "entity", 实体对象)
        events.append((offset, "open", "entity", entity))
        # 闭合事件:(位置, "close", "entity", 实体对象)
        events.append((end_offset, "close", "entity", entity))

    # 3. 排序事件:先按位置,同一位置先处理开启事件,再处理闭合事件
    def event_sort_key(event):
        pos, action, _, _ = event
        # open事件排在前面,close事件排在后面
        return (pos, 0 if action == "open" else 1)
    events.sort(key=event_sort_key)

    # 4. 遍历文本和事件,生成HTML
    result = []
    current_pos = 0
    active_styles = []  # 维护当前激活的样式栈,用于处理闭合顺序

    for event in events:
        event_pos, action, event_type, data = event
        # 先添加当前位置到事件位置之间的文本
        if event_pos > current_pos:
            result.append(text[current_pos:event_pos])
            current_pos = event_pos

        if action == "open":
            if event_type == "style":
                open_tag, _ = STYLE_TAGS[data]
                result.append(open_tag)
                active_styles.append(data)
            elif event_type == "entity":
                open_tag, _ = ENTITY_TAGS[data["type"]](data["data"])
                result.append(open_tag)
                active_styles.append(("entity", data))
        elif action == "close":
            # 闭合时从栈顶找对应的样式/实体,确保嵌套顺序正确
            if event_type == "style":
                for idx in reversed(range(len(active_styles))):
                    if active_styles[idx] == data:
                        _, close_tag = STYLE_TAGS[data]
                        result.append(close_tag)
                        del active_styles[idx]
                        break
            elif event_type == "entity":
                for idx in reversed(range(len(active_styles))):
                    if isinstance(active_styles[idx], tuple) and active_styles[idx][1] == data:
                        _, close_tag = ENTITY_TAGS[data["type"]](data["data"])
                        result.append(close_tag)
                        del active_styles[idx]
                        break

    # 添加剩余的文本
    if current_pos < len(text):
        result.append(text[current_pos:])

    # 容错:处理未闭合的标签
    for item in reversed(active_styles):
        if isinstance(item, str):
            _, close_tag = STYLE_TAGS[item]
            result.append(close_tag)
        elif isinstance(item, tuple):
            entity = item[1]
            _, close_tag = ENTITY_TAGS[entity["type"]](entity["data"])
            result.append(close_tag)

    return "".join(result)
测试示例

用你提供的Draft块测试:

test_block = {
    "data": {},
    "depth": 0,
    "entityRanges": [],
    "inlineStyleRanges": [
        {"length": 4, "offset": 4, "style": "BOLD"},
        {"length": 4, "offset": 20, "style": "BOLD"},
        {"length": 8, "offset": 8, "style": "ITALIC"},
        {"length": 12, "offset": 12, "style": "STRIKE"},
    ],
    "text": "testtesttesttesttesttest",
    "type": "unstyled",
    "key": "5kucc"
}

html = draft_block_to_html(test_block)
print(html)

输出结果(与预期一致):

test<b>test</b><i>test</i><strike><i>test</i><b>test</b>test</strike>
处理LINK实体示例

假设存在包含LINK的块:

test_block_with_link = {
    "data": {},
    "depth": 0,
    "entityRanges": [
        {"offset": 4, "length": 8, "key": "link1"}
    ],
    "inlineStyleRanges": [{"offset": 6, "length": 4, "style": "BOLD"}],
    "text": "testclickhereplease",
    "type": "unstyled",
    "key": "abc123"
}

# 实体数据
entities = {
    "link1": {
        "type": "LINK",
        "data": {"url": "https://example.com"}
    }
}

html = draft_block_to_html(test_block_with_link, entities)
print(html)

输出:

test<a href="https://example.com">clic<b>kher</b>e</a>please

内容的提问来源于stack exchange,提问作者GeorgNation

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 15:29:55