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>"), }
然后实现转换函数,步骤如下:
- 收集所有样式的「开启事件」和「闭合事件」
- 收集所有实体的「开启事件」和「闭合事件」
- 合并所有事件并按位置排序
- 遍历文本位置,依次处理事件和文本内容
- 维护样式栈,确保闭合标签顺序正确
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
相关产品推荐
相关产品推荐

