如何对同一XML表格应用多组XSLT 2.0转换?
问题
处理客户提供的不规范XML文档时,需要用XSLT 2.0完成两个转换:
- 移除表格中的空
<row/>元素 - 对包含表头行(含
outputclass="TBL_Heading"的<p>)的表格,将其outputclass从OriginalTableClass改为NewTableClass
单独执行两个转换均正常,但同时使用时空行无法被移除。
XML示例片段:
<table outputclass="OriginalTableClass"> <tgroup cols="2"> <tbody> <row> <entry> <p outputclass="TBL_Heading">Heading</p> </entry> <entry> <p outputclass="TBL_Heading">Heading</p> </entry> </row> <row> <entry morerows="1"> <p outputclass="TBL_Body_Text">Content</p> </entry> <entry> <p outputclass="TBL_Body_Text">Content</p> </entry> </row> <row/> <row/> </tbody> </tgroup> </table>
最初的XSLT代码(单独移除空行有效):
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:x2w="http://www.orbistechnologies.com/xml2word" exclude-result-prefixes="xs" version="2.0"> <xsl:template match="*" mode="ABC"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates mode="ABC"/> </xsl:copy> </xsl:template> <!-- 移除空行 --> <xsl:template match="tbody/row[not(child::*)]" mode="ABC"/> </xsl:stylesheet>
添加修改表格outputclass的模板后(单独有效,合并后空行无法移除):
<!-- 修改含表头表格的outputclass --> <xsl:template match="table[@outputclass='OriginalTableClass']" mode="ABC"> <xsl:copy> <xsl:element name="table"> <xsl:choose> <xsl:when test="*/descendant::p[@outputclass='TBL_Heading']"> <xsl:attribute name="outputclass">NewTableClass</xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="outputclass"> <xsl:value-of select="@outputclass"/> </xsl:attribute> </xsl:otherwise> </xsl:choose> <!-- 复制表格内部内容 --> <xsl:copy-of select="./node()"/> </xsl:element> </xsl:copy> </xsl:template>
核心问题分析
问题出在表格模板里的<xsl:copy-of select="./node()"/>——这个指令是直接复制原表格的所有子节点,完全跳过了后续的模板匹配流程。
XSLT的核心逻辑是模板匹配+递归处理:当你用<xsl:apply-templates>时,会触发对当前节点子元素的模板匹配(包括移除空行的模板);但<xsl:copy-of>只是原样复制节点,不会执行任何转换逻辑,所以空行被原封不动地保留下来,根本没机会触发移除模板。
另外,表格模板里多余的<xsl:copy>+新<table>元素嵌套,会导致输出XML出现重复的<table>标签,反而破坏结构。
修正方案
修改表格模板,把<xsl:copy-of>替换为<xsl:apply-templates mode="ABC"/>,同时简化表格元素的复制逻辑:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:x2w="http://www.orbistechnologies.com/xml2word" exclude-result-prefixes="xs" version="2.0"> <!-- 通用复制模板(身份模板) --> <xsl:template match="*" mode="ABC"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:apply-templates mode="ABC"/> </xsl:copy> </xsl:template> <!-- 移除空行模板 --> <xsl:template match="tbody/row[not(child::*)]" mode="ABC"/> <!-- 修改含表头表格的outputclass模板 --> <xsl:template match="table[@outputclass='OriginalTableClass']" mode="ABC"> <xsl:copy> <!-- 处理outputclass属性 --> <xsl:choose> <xsl:when test="*/descendant::p[@outputclass='TBL_Heading']"> <xsl:attribute name="outputclass">NewTableClass</xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:copy-of select="@outputclass"/> </xsl:otherwise> </xsl:choose> <!-- 递归处理表格内部子节点,触发空行移除模板 --> <xsl:apply-templates mode="ABC"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
关键改动说明:
- 用
<xsl:copy>直接复制原<table>元素,避免嵌套问题 - 把
<xsl:copy-of select="./node()"/>换成<xsl:apply-templates mode="ABC"/>,让表格内部的<tgroup>、<tbody>、<row>等节点都能触发对应的转换模板,包括空行移除逻辑 - 简化属性处理:用
<xsl:copy-of select="@outputclass"/>代替重新创建属性,更简洁高效
后续扩展建议
当需要添加更多转换逻辑时,遵循以下原则即可保证所有转换生效:
- 始终用
<xsl:apply-templates>代替<xsl:copy-of>处理子节点:只有这样才能让子节点触发对应的转换模板,实现链式处理 - 保留身份模板的基础地位:通用的身份模板(匹配所有元素并复制)是XSLT的基础,所有特殊模板(如修改表格、移除空行)都是对它的补充,优先级更高的特殊模板会覆盖身份模板的处理
- 明确模板优先级:如果多个模板匹配同一节点,可以通过
priority属性指定优先级(数值越高优先级越高),避免匹配歧义 - 分组管理模板:按功能把模板分组(比如表格处理、文本处理)并添加注释,方便后续维护
内容的提问来源于stack exchange,提问作者Randy H in CT
相关产品推荐
相关产品推荐

