如何通过XSLT正确筛选修改TOKEN节点,保留其余节点结构
XSLT转换问题:为指定TOKEN添加属性并新增空TOKEN节点
输入XML文档
<?xml version="1.0" encoding="utf-8"?> <DOCUMENT> <SECTION> <PARAGRAPH TRACK="4"> <SENTENCE NAME="PRIMARY" COUNT="4"> <TOKEN BEGIN="9" END="11" SENTENCE_BEGIN="0" SENTENCE_END="156"/> <TOKEN BEGIN="32" END="37" SENTENCE_BEGIN="0" SENTENCE_END="156"/> <TOKEN BEGIN="167" END="169" SENTENCE_BEGIN="158" SENTENCE_END="316"/> <TOKEN BEGIN="210" END="215" SENTENCE_BEGIN="158" SENTENCE_END="316"/> </SENTENCE> <SENTENCE NAME="SECONDARY" COUNT="2"> <TOKEN BEGIN="139" END="141" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="A" DOUBLE="YES"/> <TOKEN BEGIN="143" END="145" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="B"/> </SENTENCE> <SENTENCE NAME="SECONDARY" COUNT="1"> <TOKEN BEGIN="17" END="19" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="C" DOUBLE="YES"/> </SENTENCE> </PARAGRAPH> </SECTION> </DOCUMENT>
期望转换结果
<?xml version="1.0" encoding="utf-8"?> <DOCUMENT> <SECTION> <PARAGRAPH TRACK="4"> <SENTENCE NAME="PRIMARY" COUNT="4"> <TOKEN BEGIN="9" END="11" SENTENCE_BEGIN="0" SENTENCE_END="156"/> <TOKEN BEGIN="32" END="37" SENTENCE_BEGIN="0" SENTENCE_END="156"/> <TOKEN BEGIN="167" END="169" SENTENCE_BEGIN="158" SENTENCE_END="316"/> <TOKEN BEGIN="210" END="215" SENTENCE_BEGIN="158" SENTENCE_END="316"/> </SENTENCE> <SENTENCE NAME="SECONDARY" COUNT="2"> <TOKEN BEGIN="139" END="141" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="A" DOUBLE="YES"/> <TOKEN BEGIN="143" END="145" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="B"/> </SENTENCE> <SENTENCE NAME="SECONDARY" COUNT="1"> <TOKEN BEGIN="17" END="19" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="C" DOUBLE="YES" NEW="YES"/> <TOKEN/> </SENTENCE> </PARAGRAPH> </SECTION> </DOCUMENT>
当前错误的XSLT代码
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> <xsl:key name="primary_tokens" match="SENTENCE[@NAME='PRIMARY']/TOKEN" use="concat(@SENTENCE_BEGIN,'|',@SENTENCE_END)"/> <xsl:template match="/*"> <xsl:for-each select=".//TOKEN[@DOUBLE='YES'][key('primary_tokens',concat(@SENTENCE_BEGIN,'|',@SENTENCE_END))]"> <xsl:if test="key('primary_tokens',concat(@SENTENCE_BEGIN,'|',@SENTENCE_END))[@BEGIN > current()/@BEGIN]"> <xsl:copy> <xsl:attribute name="NEW">YES</xsl:attribute> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:if> </xsl:for-each> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
错误输出结果
<?xml version="1.0" encoding="utf-16"?> <TOKEN NEW="YES" BEGIN="17" END="19" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="C" DOUBLE="YES"/> <DOCUMENT> <SECTION> <PARAGRAPH TRACK="4"> <SENTENCE NAME="PRIMARY" COUNT="4"> <TOKEN BEGIN="9" END="11" SENTENCE_BEGIN="0" SENTENCE_END="156"/> <TOKEN BEGIN="32" END="37" SENTENCE_BEGIN="0" SENTENCE_END="156"/> <TOKEN BEGIN="167" END="169" SENTENCE_BEGIN="158" SENTENCE_END="316"/> <TOKEN BEGIN="210" END="215" SENTENCE_BEGIN="158" SENTENCE_END="316"/> </SENTENCE> <SENTENCE NAME="SECONDARY" COUNT="2"> <TOKEN BEGIN="139" END="141" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="A" DOUBLE="YES"/> <TOKEN BEGIN="143" END="145" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="B"/> </SENTENCE> <SENTENCE NAME="SECONDARY" COUNT="1"> <TOKEN BEGIN="17" END="19" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="C" DOUBLE="YES"/> </SENTENCE> </PARAGRAPH> </SECTION> </DOCUMENT>
正确的XSLT解决方案
你之前的错误核心在于匹配根节点/*的模板里,先输出了选中的TOKEN,再复制整个文档,导致TOKEN被放到了文档最开头。我们基于XSLT的身份模板(默认复制所有节点),针对需要修改的节点单独编写模板即可解决问题:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output indent="yes" encoding="utf-8"/> <xsl:strip-space elements="*"/> <!-- 身份模板:默认复制所有节点和属性,保证大部分内容原样保留 --> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> <!-- 定义键,用于匹配PRIMARY句子中同范围的TOKEN --> <xsl:key name="primary_tokens" match="SENTENCE[@NAME='PRIMARY']/TOKEN" use="concat(@SENTENCE_BEGIN,'|',@SENTENCE_END)"/> <!-- 为符合条件的TOKEN添加NEW="YES"属性 --> <xsl:template match="TOKEN[@DOUBLE='YES'] [key('primary_tokens', concat(@SENTENCE_BEGIN,'|',@SENTENCE_END)) [@BEGIN > current()/@BEGIN]]"> <xsl:copy> <!-- 先添加NEW属性 --> <xsl:attribute name="NEW">YES</xsl:attribute> <!-- 再复制原有的所有属性 --> <xsl:apply-templates select="@*"/> </xsl:copy> </xsl:template> <!-- 为指定的SECONDARY句子添加空TOKEN节点 --> <xsl:template match="SENTENCE[@NAME='SECONDARY' and @COUNT='1']"> <xsl:copy> <!-- 复制句子的所有属性 --> <xsl:apply-templates select="@*"/> <!-- 复制句子下的所有子节点(即符合条件的TOKEN) --> <xsl:apply-templates select="node()"/> <!-- 添加新的空TOKEN节点 --> <TOKEN/> </xsl:copy> </xsl:template> </xsl:stylesheet>
方案说明:
- 身份模板:保留默认的节点复制行为,确保无需修改的节点直接原样输出。
- TOKEN匹配模板:精准定位符合条件的TOKEN(
@DOUBLE='YES'且同范围的PRIMARY句子中有BEGIN值更大的TOKEN),在原位置复制时新增NEW="YES"属性,不会打乱节点顺序。 - SENTENCE匹配模板:针对
NAME='SECONDARY'且COUNT='1'的句子,复制完原有子节点后,追加一个空的<TOKEN/>节点,满足需求。
运行这个XSLT就能得到你期望的转换结果了。
内容的提问来源于stack exchange,提问作者wild card
相关产品推荐
相关产品推荐

