XSLT结合following-sibling与递归模板实现XML语料分句抓取
问题解决思路
不需要编写复杂的递归模板,针对你当前的XSLT 2.0版本,使用内置的相邻节点分组能力就能直接实现全文档分句,同时一次性解决现有代码的两个缺陷。
现有代码的问题根因
- 仅输出第一句内容:你当前使用的XPath表达式
//w[not(contains(./forme, '.'))][following-sibling::*[1][contains(./forme, '.')]]本质是筛选“下一个相邻节点是句末句号”的单词节点,既无法抓取长句子中离句号较远的单词,也没有对多句边界做批量遍历,实际只能匹配到每句中倒数第二个单词节点,根本覆盖不了完整句子范围。 - 缺失句末带
.的节点:你的XPath通过not(contains(./forme, '.'))直接过滤掉了所有带句号的节点,自然无法把句末的标点节点纳入结果。
适配语料结构的实现方案
你的语料结构是根节点<cordial2xml>下连续排布平级的<w>节点,分句的核心判断规则非常明确:只要<w>节点的<forme>值为.,就是当前句子的结束边界;从文档起始位置、上一个句末边界之后,到当前句末节点为止的所有连续<w>,共同组成一个完整句子。
这类连续序列切分场景,用XSLT 2.0的for-each-group做相邻分组是最简洁、学习成本最低的方案,不需要手写递归逻辑:
- 以“节点前面出现过的句末句号累计数量”作为分组键,自然把所有连续的同句子节点归到同一组
- 分组过程会自动把带
.的句末节点纳入所属句子的组内,不会遗漏 - 可以一次性遍历文档所有
<w>节点,不存在只处理第一句的问题
可直接运行的修正版XSLT代码
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" encoding="utf-8" indent="yes"/> <xsl:template match="/"> <xsl:text disable-output-escaping='yes'><!DOCTYPE html></xsl:text> <html> <head> <title>分句结果</title> </head> <body> <h1>Poème du Père Duchesne</h1> <!-- 以句末累计计数为键,对所有w节点做相邻分组 --> <xsl:for-each-group select="cordial2xml/w" group-adjacent="count(preceding-sibling::w[normalize-space(forme) = '.'])"> <!-- 每一个分组对应一个完整句子,单独包裹为段落 --> <p class="sentence"> <!-- 遍历组内所有w节点,拼接词形,同时保留词元、词性信息 --> <xsl:for-each select="current-group()"> <span class="word"> <xsl:value-of select="forme"/> <!-- NLP语料扩展输出:需要保留标注时打开下方注释 --> <!-- <small> (lemme: <xsl:value-of select="lemme"/>, categorie: <xsl:value-of select="categorie"/>) </small> --> </span> <xsl:if test="position() != last()"> <xsl:text xml:space="preserve"> </xsl:text> </xsl:if> </xsl:for-each> </p> </xsl:for-each-group> </body> </html> </xsl:template> </xsl:stylesheet>
补充说明
如果后续需要适配更复杂的句末边界规则(比如把!、?也作为句末标记),只需要修改分组键里的判断条件normalize-space(forme) = '.'为('.', '!', '?')[. = normalize-space(forme)]即可,不需要重写整体逻辑。
手写递归模板本质是手动维护游标位置、逐节点判断边界的过程,对于仅学习一周XSLT的开发者来说,调试成本高、容易出现边界判断错误,不推荐作为优先实现方案。
内容的提问来源于stack exchange,提问作者Etienne Armangau
相关产品推荐
相关产品推荐

