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

如何在XML文件指定位置插入<s>标签并保留原有格式?

解决TEI XML跨多行文本添加标签的方案

核心思路

直接提取纯文本分词会丢失原XML结构(如<lb>标签),正确的做法是保留所有原节点的顺序与结构,通过占位符标记特殊节点后再做句子分割,最后将原节点重新映射到对应的句子标签中:

  • 遍历目标文本容器(如<ab>、<p>),收集文本节点与<lb>节点序列
  • 用自定义占位符(如[[LB:n]])替换<lb>节点,拼接成带标记的完整文本
  • 对标记文本做句子分割,再将分割结果映射回原节点集合,包裹到<s>标签内
  • 全程保留原XML的空白、换行与节点属性

具体实现(基于lxml)

ElementTree对节点文本的处理不够灵活,推荐用lxml库,它能更好地保留原XML格式与节点结构。

步骤1:安装依赖

pip install lxml nltk

步骤2:完整代码

from lxml import etree
import nltk
nltk.download('punkt')
from nltk.tokenize import sent_tokenize

def add_sentence_tags(xml_path, output_path, text_container_xpath='//ab'):
    # 初始化XML解析器,严格保留空白与原格式
    parser = etree.XMLParser(remove_blank_text=False, preserve_whitespace=True)
    tree = etree.parse(xml_path, parser)
    
    # 遍历所有目标文本容器(可根据TEI结构调整XPath)
    for container in tree.xpath(text_container_xpath):
        nodes = []
        # 处理容器开头的文本节点(若存在)
        if container.text and container.text.strip():
            nodes.append(container.text)
        # 收集容器内的所有子节点
        nodes.extend(list(container))
        
        current_text = []
        current_nodes = []
        processed_elements = []
        
        for node in nodes:
            if isinstance(node, str):
                # 文本节点直接加入缓存
                current_text.append(node)
                current_nodes.append(node)
            elif node.tag == 'lb':
                # 用占位符标记<lb>节点,记录其xml:id
                lb_id = node.get('xml:id', '')
                current_text.append(f'[[LB:{lb_id}]]')
                current_nodes.append(node)
            else:
                # 遇到非文本/非lb节点,先处理当前缓存的内容
                if current_text:
                    full_text = ''.join(current_text)
                    sentences = sent_tokenize(full_text)
                    ptr = 0
                    
                    for sent in sentences:
                        sent_nodes = []
                        remaining_sent = sent
                        
                        while ptr < len(current_nodes) and remaining_sent:
                            n = current_nodes[ptr]
                            if isinstance(n, str):
                                # 匹配文本节点与句子片段
                                if remaining_sent.startswith(n[:len(remaining_sent)]):
                                    # 句子边界在当前文本节点内,截断文本
                                    sent_nodes.append(n[:len(remaining_sent)])
                                    remaining_text = n[len(remaining_sent):]
                                    if remaining_text:
                                        current_nodes[ptr] = remaining_text
                                    else:
                                        ptr += 1
                                    remaining_sent = ''
                                else:
                                    # 整个文本节点属于当前句子
                                    sent_nodes.append(n)
                                    remaining_sent = remaining_sent[len(n):]
                                    ptr += 1
                            else:
                                # 还原<lb>节点,移除占位符
                                sent_nodes.append(n)
                                remaining_sent = remaining_sent.replace(f'[[LB:{n.get("xml:id", "")}]]', '')
                                ptr += 1
                        
                        # 创建<s>标签并添加节点
                        s_elem = etree.Element('s')
                        for sn in sent_nodes:
                            if isinstance(sn, str):
                                if not s_elem.text:
                                    s_elem.text = sn
                                else:
                                    # 处理文本节点的tail
                                    last_child = s_elem[-1] if len(s_elem) > 0 else None
                                    if last_child:
                                        last_child.tail = (last_child.tail or '') + sn
                                    else:
                                        s_elem.text += sn
                            else:
                                s_elem.append(sn)
                        processed_elements.append(s_elem)
                    
                    # 重置缓存
                    current_text = []
                    current_nodes = []
                
                # 将非文本/非lb节点直接加入结果
                processed_elements.append(node)
        
        # 处理最后一批缓存内容
        if current_text:
            full_text = ''.join(current_text)
            sentences = sent_tokenize(full_text)
            ptr = 0
            
            for sent in sentences:
                sent_nodes = []
                remaining_sent = sent
                
                while ptr < len(current_nodes) and remaining_sent:
                    n = current_nodes[ptr]
                    if isinstance(n, str):
                        if remaining_sent.startswith(n[:len(remaining_sent)]):
                            sent_nodes.append(n[:len(remaining_sent)])
                            remaining_text = n[len(remaining_sent):]
                            if remaining_text:
                                current_nodes[ptr] = remaining_text
                            else:
                                ptr += 1
                            remaining_sent = ''
                        else:
                            sent_nodes.append(n)
                            remaining_sent = remaining_sent[len(n):]
                            ptr += 1
                    else:
                        sent_nodes.append(n)
                        remaining_sent = remaining_sent.replace(f'[[LB:{n.get("xml:id", "")}]]', '')
                        ptr += 1
                
                s_elem = etree.Element('s')
                for sn in sent_nodes:
                    if isinstance(sn, str):
                        if not s_elem.text:
                            s_elem.text = sn
                        else:
                            last_child = s_elem[-1] if len(s_elem) > 0 else None
                            if last_child:
                                last_child.tail = (last_child.tail or '') + sn
                            else:
                                s_elem.text += sn
                    else:
                        s_elem.append(sn)
                processed_elements.append(s_elem)
        
        # 清空原容器,替换为处理后的内容
        container.clear()
        for elem in processed_elements:
            container.append(elem)
    
    # 保存处理后的XML,保留原编码与格式
    tree.write(output_path, encoding='utf-8', pretty_print=True, xml_declaration=True)

# 调用示例:根据你的TEI结构调整text_container_xpath
add_sentence_tags('input_tei.xml', 'output_tei.xml', text_container_xpath='//div[@type="text"]/p')

关键注意事项

  • 调整XPath:必须根据你的TEI文件结构修改text_container_xpath,定位到实际包含文本的容器元素(如<p>、<ab>)
  • 分词工具适配:NLTK的sent_tokenize适合通用文本,若处理古文本或专业领域TEI,可替换为spaCy的专业模型(如en_core_web_sm)或自定义分词规则
  • 测试验证:先处理小片段XML,检查<s>标签是否正确包裹跨多行句子,<lb>节点的位置与属性是否完全保留

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 12:50:25