用XSL将一个包含两列的表格拆分,其中每个逻辑列由多列组成

前端开发 2026-07-10

我想把一些遗留输出带入现代,将一个HTML表格转换成XML格式。

遗留输出按每行两列列出(product、price、order_number、amount),因此实际上它是这样的:

(product_n, price_n, order_number_n, amount_n, product_m, price_m, order_number_m, amount_m)

处理表格拆分的XSL部分是

<xsl:template match="tr">
  <item>
    <xsl:apply-templates select="th | td" mode="map-cols" />
  </item>
  <xsl:text>&#10;</xsl:text>
</xsl:template>

<xsl:template match="td" mode="map-cols">
  <!-- input uses "two column (with four columns each) rows -->
  <xsl:variable name="rcol" select="(position() - 1) mod 4" />
  <xsl:choose>
    <xsl:when test="$rcol = 0"><!-- product -->
      <xsl:if test="position() = 5">
        &lt;/item>&lt;item>
      </xsl:if>
      <name>
        <xsl:value-of select="normalize-space()" />
      </name>
    </xsl:when>
    ...
  </xsl:choose>
</xsl:template>

问题在于 &lt;/item>&lt;item> 被原样输出;在那之前我用过 </item><item>,但那会导致解析错误:

解析器错误:开启和结束标签不匹配

显然我想在第5 列处结束当前的 item,并在第5 列处开始一个新的 item

怎么做? 我正在使用 libxslt-tools-1.1.34-150400.3.13.1.x86_64 的SLES15。

输入示例

应要求,我在勾勒输入数据,但其实是一团糟。

事实上我发现表格在 amount_nproduct_m 之间还多了一个空的“分隔列”,但为了与原始描述保持一致,我已经把它移除了。

所以如你所警告的,这里给出一个示例表格行:

<html>
<head>
<title>Example</title>
</head>
<body>
<!-- more stuff -->
<table frame='void' rules='all' cellspacing=1 cellpadding=0.9 style='BORDER-RIGHT: 1px solid; BORDER-TOP: 1px solid; BORDER-LEFT: 1px solid; BORDER-BOTTOM: 1px solid; BORDER-COLLAPSE: collapse'>
<tr>
<td width=210px><span style='font-size:8.0pt;mso-bidi-font-size:12.0pt'>&nbsp;Ba...</span></td>
<td width=30px align=right><span style='font-size:8.0pt;mso-bidi-font-size:12.0pt'>3,90&nbsp;</span></td>
<td width=30px><span style='font-size:8.0pt;mso-bidi-font-size:12.0pt'>&nbsp;1111</span></td>
<td width=34px><span style='font-size:8.0pt;mso-bidi-font-size:12.0pt'>&nbsp;</span></td>
<td width=210px><span style='font-size:8.0pt;mso-bidi-font-size:12.0pt'>&nbsp;Mi...</span></td>
<td width=30px align=right><span style='font-size:8.0pt;mso-bidi-font-size:12.0pt'>9,80&nbsp;</span></td>
<td width=30px><span style='font-size:8.0pt;mso-bidi-font-size:12.0pt'>&nbsp;2222</span></td>
<td width=34px><span style='font-size:8.0pt;mso-bidi-font-size:12.0pt'>&nbsp;</span></td>
</tr>
</table>
<!-- more stuff -->
</body>
</html>

(原始数据值b 已经被混淆,因为我不允许分享那些)

输出示例

...
<item>
  <product>Ba...</product>
  <price>3,90</price>
  <ordernr>1111</ordernr>
</item>
<item>
  <product>Mi...</product>
  <price>9,80</price>
  <ordernr>2222</ordernr>
</item>
...

(另一个挑战是按例如 product 对项进行排序;原始表格已经排序,因此第一行中第二个项的起始项紧跟在最后一行的第一个项之后)

解决方案

下面给出一个简化示例,供你作为指南。

输入(在同一行的两个项之间包含一个“分隔列”):

<html>
  <body>
    <table>
      <tr>
        <td>Product A</td>
        <td>3,90</td>
        <td>1111</td>
        <td>4</td>
        <td></td>
        <td>Product B</td>
        <td>9,80</td>
        <td>2222</td>
        <td>10</td>
      </tr>
      <tr>
        <td>Product C</td>
        <td>5,30</td>
        <td>3333</td>
        <td>3</td>
        <td></td>
        <td>Product D</td>
        <td>10,40</td>
        <td>4444</td>
        <td>16</td>
      </tr>
    </table>
  </body>
</html>

XSLT 1.0

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/html">
    <output>
        <xsl:for-each select="body/table/tr/td[position() = 1 or position() = 6] ">
            <item>
                <product>
                    <xsl:value-of select="."/>
                </product>  
                <price>
                    <xsl:value-of select="following-sibling::td[1]"/>
                </price>    
                <order_number>
                    <xsl:value-of select="following-sibling::td[2]"/>
                </order_number> 
                <amount>
                    <xsl:value-of select="following-sibling::td[3]"/>
                </amount>   
            </item>
        </xsl:for-each>
    </output>
</xsl:template>

</xsl:stylesheet>

结果

<?xml version="1.0" encoding="UTF-8"?>
<output>
  <item>
    <product>Product A</product>
    <price>3,90</price>
    <order_number>1111</order_number>
    <amount>4</amount>
  </item>
  <item>
    <product>Product B</product>
    <price>9,80</price>
    <order_number>2222</order_number>
    <amount>10</amount>
  </item>
  <item>
    <product>Product C</product>
    <price>5,30</price>
    <order_number>3333</order_number>
    <amount>3</amount>
  </item>
  <item>
    <product>Product D</product>
    <price>10,40</price>
    <order_number>4444</order_number>
    <amount>16</amount>
  </item>
</output>

附加说明:

原始表格已经排序,因此第一行中第二个项的起始项紧随最后一行的第一个项之后

你可以只做很小的调整就保留原始顺序:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/html">
    <output>
        <xsl:call-template name="process">
            <xsl:with-param name="cells" select="body/table/tr/td[1]"/>
        </xsl:call-template>
        <xsl:call-template name="process">
            <xsl:with-param name="cells" select="body/table/tr/td[6]"/>
        </xsl:call-template>
    </output>
</xsl:template>

<xsl:template name="process">
    <xsl:param name="cells"/>
    <xsl:for-each select="$cells">
        <item>
            <product>
                <xsl:value-of select="."/>
            </product>  
            <price>
                <xsl:value-of select="following-sibling::td[1]"/>
            </price>    
            <order_number>
                <xsl:value-of select="following-sibling::td[2]"/>
            </order_number> 
            <amount>
                <xsl:value-of select="following-sibling::td[3]"/>
            </amount>   
        </item>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章