如何用XSLT展开HTML表格的rowspan与colspan以验证结构
XSLT实现HTML表格单元格展开与结构验证
核心实现思路
要完成表格单元格展开与结构验证,需分两步执行:
- 计算表格总列数:解析表头或第一行的单元格(包含
colspan属性),确定表格的基准列数 - 逐行处理单元格:
- 对带
colspan的单元格,直接拆分为对应数量的<td>/<th>元素 - 对带
rowspan的单元格,当前行保留原单元格外,需在后续rowspan-1行中预留对应数量的空单元格占位
- 对带
完整XSLT代码实现
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" indent="yes"/> <!-- 处理整个表格,先计算总列数再逐行处理 --> <xsl:template match="table"> <xsl:variable name="totalCols"> <xsl:call-template name="calculateTotalCols"> <xsl:with-param name="cells" select="thead/tr/th | tbody/tr[1]/td"/> </xsl:call-template> </xsl:variable> <table> <!-- 处理表头 --> <xsl:if test="thead"> <thead> <xsl:apply-templates select="thead/tr"> <xsl:with-param name="totalCols" select="$totalCols"/> <xsl:with-param name="pendingRowspans" select="/.."/> <!-- 初始无跨行待处理 --> </xsl:apply-templates> </thead> </xsl:if> <!-- 处理表体,传递跨行状态 --> <tbody> <xsl:apply-templates select="tbody/tr[1]"> <xsl:with-param name="totalCols" select="$totalCols"/> <xsl:with-param name="pendingRowspans" select="/.."/> </xsl:apply-templates> </tbody> <!-- 输出结构验证结果 --> <tfoot> <tr> <td colspan="{$totalCols}"> <xsl:choose> <xsl:when test="count(tbody/tr[count(td) != $totalCols]) = 0"> *表格结构合规:所有行单元格数量与列数一致* </xsl:when> <xsl:otherwise> *表格结构不合规:存在行单元格数量与列数不匹配* </xsl:otherwise> </xsl:choose> </td> </tr> </tfoot> </table> </xsl:template> <!-- 计算表格总列数的递归模板 --> <xsl:template name="calculateTotalCols"> <xsl:param name="cells"/> <xsl:param name="sum" select="0"/> <xsl:choose> <xsl:when test="$cells"> <xsl:call-template name="calculateTotalCols"> <xsl:with-param name="cells" select="$cells[position() > 1]"/> <xsl:with-param name="sum" select="$sum + number($cells[1]/@colspan) + (number($cells[1]/@colspan) = 0)"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$sum"/> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- 处理单行,先填充上一行遗留的跨行单元格 --> <xsl:template match="tr"> <xsl:param name="totalCols"/> <xsl:param name="pendingRowspans"/> <xsl:variable name="currentCells" select="td | th"/> <xsl:variable name="processedCells"> <!-- 填充上一行的跨行占位 --> <xsl:for-each select="$pendingRowspans"> <td class="placeholder"/> <!-- 更新跨行剩余行数 --> <xsl:if test="@remaining > 1"> <pending remaining="{@remaining - 1}"/> </xsl:if> </xsl:for-each> <!-- 处理当前行的单元格 --> <xsl:apply-templates select="$currentCells"> <xsl:with-param name="totalCols" select="$totalCols"/> </xsl:apply-templates> </xsl:variable> <!-- 补全行内不足的单元格 --> <tr> <xsl:copy-of select="$processedCells/*"/> <xsl:if test="count($processedCells/*) < $totalCols"> <xsl:call-template name="fillEmptyCells"> <xsl:with-param name="count" select="$totalCols - count($processedCells/*)"/> </xsl:call-template> </xsl:if> </tr> <!-- 收集当前行产生的新跨行待处理项 --> <xsl:variable name="newPending"> <xsl:for-each select="$currentCells[@rowspan and number(@rowspan) > 1]"> <xsl:for-each select="1 to number(@rowspan) - 1"> <pending remaining="{number(current()/@rowspan) - position()}"/> </xsl:for-each> </xsl:for-each> <!-- 保留未处理完的跨行项 --> <xsl:copy-of select="$pendingRowspans[@remaining > 1]"/> </xsl:variable> <!-- 递归处理下一行 --> <xsl:if test="following-sibling::tr"> <xsl:apply-templates select="following-sibling::tr[1]"> <xsl:with-param name="totalCols" select="$totalCols"/> <xsl:with-param name="pendingRowspans" select="$newPending/pending"/> </xsl:apply-templates> </xsl:if> </xsl:template> <!-- 拆分带colspan的单元格 --> <xsl:template match="td | th"> <xsl:param name="totalCols"/> <xsl:variable name="span" select="number(@colspan)"/> <xsl:choose> <xsl:when test="$span > 1"> <xsl:call-template name="generateCells"> <xsl:with-param name="count" select="$span"/> <xsl:with-param name="elementName" select="local-name()"/> <xsl:with-param name="content" select="node()"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:copy-of select="."/> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- 生成指定数量的单元格模板 --> <xsl:template name="generateCells"> <xsl:param name="count"/> <xsl:param name="elementName"/> <xsl:param name="content"/> <xsl:if test="$count > 0"> <xsl:element name="{$elementName}"> <xsl:if test="$count = 1"> <xsl:copy-of select="$content"/> </xsl:if> </xsl:element> <xsl:call-template name="generateCells"> <xsl:with-param name="count" select="$count - 1"/> <xsl:with-param name="elementName" select="$elementName"/> <xsl:with-param name="content" select="$content"/> </xsl:call-template> </xsl:if> </xsl:template> <!-- 填充空单元格模板 --> <xsl:template name="fillEmptyCells"> <xsl:param name="count"/> <xsl:if test="$count > 0"> <td class="empty"/> <xsl:call-template name="fillEmptyCells"> <xsl:with-param name="count" select="$count - 1"/> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet>
测试示例
目标HTML表格
<table> <thead> <tr> <th colspan="2">表头1</th> <th>表头2</th> </tr> </thead> <tbody> <tr> <td rowspan="2">跨行单元格</td> <td>普通单元格</td> <td>普通单元格</td> </tr> <tr> <td colspan="2">跨列单元格</td> </tr> </tbody> </table>
转换后期望输出
<table> <thead> <tr> <th>表头1</th> <th>表头1</th> <th>表头2</th> </tr> </thead> <tbody> <tr> <td rowspan="2">跨行单元格</td> <td>普通单元格</td> <td>普通单元格</td> </tr> <tr> <td class="placeholder"></td> <td>跨列单元格</td> <td>跨列单元格</td> </tr> </tbody> <tfoot> <tr> <td colspan="3">*表格结构合规:所有行单元格数量与列数一致*</td> </tr> </tfoot> </table>
关键说明
- 通过
pendingRowspans变量跟踪后续行需要填充的跨行单元格位置 - 拆分
colspan时仅在第一个单元格保留原内容,其余为空白占位(可按需调整内容逻辑) - 最终通过
tfoot直观输出结构验证结果,快速判断表格是否合规
内容的提问来源于stack exchange,提问作者BWin
相关产品推荐
相关产品推荐

