通用XSLT脚本适配缺失元素:XML转HTML表格列对齐修复
问题描述
我有一个用于生成HTML表格的XSLT脚本,生成的表格会用于邮件发送(因Outlook渲染需求包含适配斑马纹的代码)。示例XML数据源如下:
<Data> <Features> <Product caption="Product" /> <ItemName caption="Item Name" /> <Category caption="Category" /> <Material caption="Material" /> <HeightCM caption="Height" /> <AssignedTo caption="Category Manager" /> </Features> <Product> <Value>001</Value> <ItemName>Product 1</ItemName> <Category>Electic</Category> <Material>Steel</Material> <HeightCM>15</HeightCM> <AssignedTo>James</AssignedTo> </Product> <Product> <Value>002</Value> <ItemName>Product 2</ItemName> <HeightCM>12</HeightCM> </Product> <Product> <Value>003</Value> <ItemName>Product 3</ItemName> <Category>Sale</Category> <AssignedTo>Jane</AssignedTo> </Product> </Data>
该XML中,Features元素定义表头行,Product元素包含产品属性值,但并非所有Product都包含全部Features子元素对应的内容(如示例中Product 1具备所有属性,Product 2和3缺失部分属性)。
原脚本在所有Product包含全部Features数据时运行正常,但仅为存在的数据生成表格单元格,导致生成的HTML表格出现列错位问题。要求在保持脚本通用性(仅保证<Data>、<Features>、<Product>元素固定)的前提下,修改脚本以适配Product缺失Feature时所需的空表格单元格。
原XSLT脚本如下:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html" indent="yes"/> <xsl:template match="/*"> <html> <head> <style> [Removed for legibility reasons] </style> </head> <body> <table> <thead> <tr> <xsl:apply-templates select="Features/*" mode="th"/> </tr> </thead> <tbody> <xsl:apply-templates select="*"/> </tbody> </table> </body> </html> </xsl:template> <xsl:template match="/*/*/*" mode="th"> <th> <xsl:value-of select="@caption"/> </th> </xsl:template> <xsl:template match="/*/*"> <tr> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="position() mod 2 = 0">even</xsl:when> <xsl:when test="position() mod 2 = 1">odd</xsl:when> </xsl:choose> </xsl:attribute> <xsl:apply-templates select="*"/> </tr> </xsl:template> <xsl:template match="/*/*/*"> <td> <xsl:value-of select="."/> </td> </xsl:template> </xsl:stylesheet>
解决方案
核心思路是:遍历Features定义的所有表头字段,对每个Product按表头顺序逐个查找对应的子元素——存在则输出值,不存在则输出空单元格,保证每一行的单元格数量和表头列数完全匹配,避免错位。
修改后的XSLT脚本如下:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html" indent="yes"/> <!-- 定义全局变量存储Features中的所有字段名称 --> <xsl:variable name="featureNames" select="/Data/Features/*/local-name()"/> <xsl:template match="/*"> <html> <head> <style> [Removed for legibility reasons] </style> </head> <body> <table> <thead> <tr> <xsl:apply-templates select="Features/*" mode="th"/> </tr> </thead> <tbody> <!-- 仅处理Product元素,排除Features --> <xsl:apply-templates select="Product"/> </tbody> </table> </body> </html> </xsl:template> <xsl:template match="/*/*/*" mode="th"> <th> <xsl:value-of select="@caption"/> </th> </xsl:template> <xsl:template match="Product"> <tr> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="position() mod 2 = 0">even</xsl:when> <xsl:when test="position() mod 2 = 1">odd</xsl:when> </xsl:choose> </xsl:attribute> <!-- 遍历所有feature名称,逐个生成单元格 --> <xsl:for-each select="$featureNames"> <td> <!-- 当前feature名称 --> <xsl:variable name="currentFeature" select="."/> <!-- 查找当前Product中对应的子元素,存在则取值,否则为空 --> <xsl:value-of select="../*[local-name() = $currentFeature]"/> </td> </xsl:for-each> </tr> </xsl:template> </xsl:stylesheet>
修改说明
- 全局变量存储字段名:新增
featureNames变量,提取Features下所有子元素的名称,作为后续遍历的基准。 - 限定Product元素处理:原脚本中
<xsl:apply-templates select="*"/>会处理所有子元素(包括Features),修改为<xsl:apply-templates select="Product"/>,避免误处理Features。 - 按表头顺序生成单元格:在Product的模板中,遍历
featureNames变量,对每个字段查找当前Product下对应的子元素——存在则输出值,不存在则输出空<td>,确保每行单元格数量与表头一致。 - 保留原有斑马纹逻辑:原有的奇偶行class判断逻辑完全保留,适配Outlook的渲染需求。
内容的提问来源于stack exchange,提问作者Stephen
相关产品推荐
相关产品推荐

