XML文档中软件提及内容的<software>标签包裹实现问询
解决XML中
标签内软件名称的
问题背景
- XML文件中目标软件名称位于
<p>标签内,且<p>标签通常包含<ref>等子标签 - 需要将指定软件名称精准包裹在
<software>标签中,简单文本替换无法满足定位需求 - 已有JSON文件记录所有软件提及的位置信息(含字符偏移量、段落编号)
- 尝试过
xml.etree、Beautiful Soup、lxml等工具,现有脚本需完善
输入输出示例
输入XML片段
<p> The reliability of the model structure was tested using the ENERGY commands of MODELLER <ref type="bibr" target="#b45">(Sali and Blundell, 1993)</ref>. The modelled structures were also validated using the program PROSA <ref type="bibr" target="#b54">(Wiederstein and Sippl, 2007)</ref>.</p>
期望输出XML片段
<p> The reliability of the model structure was tested using the ENERGY commands of <software>MODELLER</software> <ref type="bibr" target="#b45">(Sali and Blundell, 1993)</ref>. The modelled structures were also validated using the program <software>PROSA</software> <ref type="bibr" target="#b54">(Wiederstein and Sippl, 2007)</ref>.</p>
软件标注JSON示例
{ "type": "software", "software-type": "software", "software-name": { "rawForm": "MODELLER", "normalizedForm": "MODELLER", "offsetStart": 79, "offsetEnd": 87, "boundingBoxes": [ { "p": 1, "x": 495.722, "y": 320.344, "w": 53.9323, "h": 8.0517 } ] }, "context": "The reliability of the model structure was tested using the ENERGY commands of MODELLER (Sali and Blundell, 1993)." }
完善后的Python解决方案
使用lxml库处理XML节点的文本拆分与插入,利用JSON标注中的偏移量精准定位软件名称:
from lxml import etree import json def wrap_software_in_p(xml_content, software_annotations): # 解析XML,保留格式 parser = etree.XMLParser(remove_blank_text=True) root = etree.fromstring(xml_content, parser) # 按段落索引处理(JSON中p字段为段落编号,从1开始) for idx, p_elem in enumerate(root.findall('.//p'), start=1): # 筛选当前段落的所有软件标注 current_annos = [anno for anno in software_annotations if anno['software-name']['boundingBoxes'][0]['p'] == idx] if not current_annos: continue # 按偏移量降序排序,避免插入新节点影响后续偏移计算 current_annos.sort(key=lambda x: x['software-name']['offsetStart'], reverse=True) # 遍历每个标注,插入<software>标签 for anno in current_annos: software_name = anno['software-name']['rawForm'] start = anno['software-name']['offsetStart'] end = anno['software-name']['offsetEnd'] current_pos = 0 parent = p_elem # 检查段落初始文本节点 initial_text = parent.text or '' if len(initial_text) > current_pos: if start < current_pos + len(initial_text): # 拆分初始文本,插入软件标签 pre_text = initial_text[:start - current_pos] software_text = initial_text[start - current_pos:end - current_pos] post_text = initial_text[end - current_pos:] software_tag = etree.Element('software') software_tag.text = software_text parent.text = pre_text software_tag.tail = post_text parent.insert(0, software_tag) continue current_pos += len(initial_text) # 遍历段落内的子节点(如<ref>),检查子节点的tail文本 for i, child in enumerate(list(parent)): # 跳过子节点文本(示例中软件不在子节点内,如需处理可扩展) current_pos += len(child.text or '') tail_text = child.tail or '' tail_len = len(tail_text) if start < current_pos + tail_len: # 拆分tail文本,插入软件标签 pre_tail = tail_text[:start - current_pos] software_text = tail_text[start - current_pos:end - current_pos] post_tail = tail_text[end - current_pos:] software_tag = etree.Element('software') software_tag.text = software_text software_tag.tail = post_tail child.tail = pre_tail parent.insert(i+1, software_tag) break current_pos += tail_len # 返回格式化后的XML字符串 return etree.tostring(root, encoding='unicode', pretty_print=True) # 示例调用 if __name__ == '__main__': # 输入XML内容 xml_input = '''<root> <p> The reliability of the model structure was tested using the ENERGY commands of MODELLER <ref type="bibr" target="#b45">(Sali and Blundell, 1993)</ref>. The modelled structures were also validated using the program PROSA <ref type="bibr" target="#b54">(Wiederstein and Sippl, 2007)</ref>.</p> </root>''' # 软件标注数据(实际可从JSON文件读取) software_annotations = [ { "type": "software", "software-type": "software", "software-name": { "rawForm": "MODELLER", "normalizedForm": "MODELLER", "offsetStart": 79, "offsetEnd": 87, "boundingBoxes": [{"p": 1, "x": 495.722, "y": 320.344, "w": 53.9323, "h": 8.0517}] }, "context": "The reliability of the model structure was tested using the ENERGY commands of MODELLER (Sali and Blundell, 1993)." }, { "type": "software", "software-type": "software", "software-name": { "rawForm": "PROSA", "normalizedForm": "PROSA", "offsetStart": 194, "offsetEnd": 199, "boundingBoxes": [{"p": 1, "x": 495.722, "y": 320.344, "w": 53.9323, "h": 8.0517}] }, "context": "The modelled structures were also validated using the program PROSA (Wiederstein and Sippl, 2007)." } ] # 处理并输出结果 modified_xml = wrap_software_in_p(xml_input, software_annotations) print(modified_xml)
关键说明
- 采用
lxml库实现复杂节点操作,相比xml.etree更适合文本拆分与标签插入场景 - 按偏移量降序处理标注,避免插入新节点后后续偏移量失效
- 遍历段落的初始文本和子节点的tail文本,精准定位软件名称位置
- 将原文本拆分为「前缀文本+软件名称+后缀文本」,创建
<software>标签插入对应位置
内容的提问来源于stack exchange,提问作者Scalbert Samuel
相关产品推荐
相关产品推荐

