
本文介绍如何利用 xslt 3 和 saxon-he(通过 python 的 `saxonche` 库)对嵌套 `` 标签的 ` ` 段落中特定软件名进行精准包装,依据 json 提供的上下文和规范化名称实现语义化标注,避免正则误匹配与 dom 结构破坏。 在处理学术文献或技术文档的 XML 数据时,常需从富文本段落(如含 <ref>、<em> 等内联标签的 <p>)中识别并标注软件名称(如 “MODELLER”、“PROSA”)。难点在于:软件名可能夹杂在纯文本与子元素之间,传统字符串替换会破坏结构,而仅靠 itertext() 或 lxml 的文本遍历又难以精确定位到原始上下文中的确切位置——尤其当 JSON 注释数据提供了带偏移量(offsetStart/offsetEnd)和完整上下文句子(context)时,应优先利用该语义信息而非盲目模式匹配。 XSLT 3 提供了强大且声明式的解决方案:它原生支持 JSON 解析(parse-json() / json-doc())、高阶迭代(xsl:iterate)、隧道参数(tunnel="yes")以及正则感知的文本分析(analyze-string),完美契合本任务需求。以下是一个可直接运行的端到端方案: 此方案摒弃了易出错的手动 DOM 拆解逻辑,以声明式、可验证、可复用的 XSLT 实现精准语义标注,是处理结构化文本中实体识别任务的专业级实践。✅ 步骤一:安装依赖
pip install saxonche
✅ 步骤二:编写 XSLT 脚本(wrap-software.xsl)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<!-- 读取 JSON 文件(或内联 JSON 字符串) -->
<xsl:param name="json-data" as="xs:string">
[{"type":"software","software-type":"software","software-name":{"rawForm":"MODELLER","normalizedForm":"MODELLER","offsetStart":79,"offsetEnd":87},"context":"The reliability of the model structure was tested using the ENERGY commands of MODELLER (Sali and Blundell, 1993)."}]
</xsl:param>
<xsl:param name="json-doc" select="parse-json($json-data)"/>
<!-- 主模板:对每个 <p> 迭代所有 software 条目 -->
<xsl:template match="p">
<xsl:iterate select="$json-doc?*[?type = 'software']">
<xsl:param name="p" select="."/>
<xsl:on-completion select="$p"/>
<!-- 仅当当前 p 的文本内容包含该 software 的 context 时才处理 -->
<xsl:if test="contains(string-join($p//text(), ''), ?context)">
<xsl:variable name="transformed-p" as="element(p)">
<xsl:apply-templates select="$p" mode="process">
<xsl:with-param name="software" select="." tunnel="yes"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:next-iteration>
<xsl:with-param name="p" select="$transformed-p"/>
</xsl:next-iteration>
</xsl:if>
</xsl:iterate>
</xsl:template>
<!-- 处理模式:仅作用于文本节点 -->
<xsl:mode name="process" on-no-match="shallow-copy"/>
<xsl:template mode="process" match="p//text()">
<xsl:param name="software" tunnel="yes" as="map(*)"/>
<xsl:choose>
<!-- 判断该文本是否属于目标 context,且包含 normalizedForm -->
<xsl:when test="contains($software?context, .) and contains(., $software?software-name?normalizedForm)">
<!-- 使用 analyze-string 精确匹配(支持字面量,无需转义正则元字符) -->
<xsl:apply-templates select="analyze-string(., $software?software-name?normalizedForm)" mode="wrap"/>
</xsl:when>
<xsl:otherwise>
<xsl:next-match/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- 包装匹配结果 -->
<xsl:template mode="wrap" match="*:match">
<software>{.}</software>
</xsl:template>
<!-- 默认浅拷贝所有未匹配节点 -->
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
</xsl:stylesheet>✅ 步骤三:Python 调用执行(wrap.py)
from saxonche import PySaxonProcessor
# 加载 XML 和 XSLT
xml_content = '''<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>'''
with PySaxonProcessor() as proc:
xslt_proc = proc.new_xslt30_processor()
# 直接传入 XML 字符串和 XSLT 文件路径
executable = xslt_proc.compile_stylesheet(stylesheet_file='wrap-software.xsl')
output = executable.transform_to_string(xdm_node=proc.parse_xml(xml_text=xml_content))
print(output)⚠️ 注意事项与最佳实践










