【问题标题】:How to correctly specify colname for tables?如何正确指定表的 colname?
【发布时间】:2012-01-13 05:54:59
【问题描述】:

我的源 xml 看起来像:

<TABLE>
  <ROW>
    <CELL ROWSPAN="3"> Test </CELL>
    <CELL ROWSPAN="2"> Test </CELL>
    <CELL ROWSPAN="1"> Test </CELL>
    <CELL ROWSPAN="3"> Test </CELL>
    <CELL ROWSPAN="1"> Test </CELL>
  </ROW>
  <ROW>
    <CELL ROWSPAN="1"> Test </CELL>
    <CELL ROWSPAN="1"> Test </CELL>
  </ROW>
</TABLE>

正确转换后的输出应如下所示:

<tbody>
  <row>
    <entry colname="1"> Test </entry>
    <entry colname="2"> Test </entry>
    <entry colname="3"> Test </entry>
    <entry colname="4"> Test </entry>
    <entry colname="5"> Test </entry>
 </row>
 <row>
   <entry colname="3"> Test </entry>
   <entry colname="5"> Test </entry>
 </row>
</tbody>

如您所见,棘手的部分是第二行元素。由于第一行有多个单元格占据多行,因此会影响第二行的列名,这就是为什么第二行的第一个条目以列名“3”而不是“1”开头的原因。我不知道这里怎么画表格,但是如果你把它画在纸上,你会很容易理解的。

目前,我有以下 xsl 可以部分捕获这一点(我省略了其他信息,因为我只有 @colname 有问题)

<xsl:template match="CELL">
   <xsl:if test="../preceding-sibling::ROW[1]/CELL[1]/@ROWSPAN > 1">
     <xsl:attribute name="colname" select="position()+count(../preceding-sibling::ROW[1]/CELL[@ROWSPAN>1])"/>
    ..
   </xsl:if>
</xsl:template>

这不会很好,因为它会将具有多行跨度的所有 CELL 包括在其计数中,因此结果将如下所示:

  <row>
    <entry colname="4"> Test </entry>
    <entry colname="5"> Test </entry>
  </row> 

虽然第一个条目实际上应该从 3 开始。

我发现描述这个问题有点困难,但我会尽力而为,如果需要进一步的信息,请在下面发表评论。

【问题讨论】:

  • 我从未使用过rowspan,也不知道它应该是什么意思。 XSLT 和 XPath 专家不应该知道任何特定的 XML 词汇,即使它恰好是 XHTML。请用rowspan 的含义定义编辑问题。
  • 这看起来很棘手,考虑到输出可能取决于任意数量的先前ROW 元素。我有一个想法在脑海中浮现,使用递归模板依次处理每一行,从前一行传入一个“占用”行的列表,但需要一些时间来充实。
  • 快速提问:您是否也喜欢使用COLSPAN 标签?如果是这样,事情就会变得非常复杂。
  • @Flynn1179 没有使用 COLSPAN,我使用了 namest 和 namend。

标签: xml xslt xpath html-table


【解决方案1】:

这个解决方案相当复杂,我有一种唠叨的感觉,可能有更好的方法,但它似乎有效:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="TABLE">
    <tbody>
      <xsl:apply-templates select="ROW[1]" />
    </tbody>
  </xsl:template>

  <xsl:template match="ROW">
    <xsl:param name="occupiedcols" />
    <row>
      <xsl:apply-templates select="CELL[1]">
        <xsl:with-param name="occupiedcols" select="$occupiedcols" />
      </xsl:apply-templates>
    </row>
    <xsl:apply-templates select="following-sibling::ROW[1]">
      <xsl:with-param name="occupiedcols">
        <xsl:apply-templates select="CELL[1]" mode="getoccupied">
          <xsl:with-param name="occupiedcols" select="$occupiedcols" />
        </xsl:apply-templates>
        <xsl:text></xsl:text>
      </xsl:with-param>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="CELL">
    <xsl:param name="occupiedcols" />
    <xsl:param name="col" select="1" />
    <xsl:variable name="thiscol" select="$col + string-length(substring-before(substring($occupiedcols,$col,255),'0'))" />
    <xsl:element name="entry">
      <xsl:attribute name="colname">
        <xsl:value-of select="$thiscol" />
      </xsl:attribute>
    </xsl:element>
    <xsl:apply-templates select="following-sibling::CELL[1]">
      <xsl:with-param name="occupiedcols" select="$occupiedcols"/>
      <xsl:with-param name="col" select="$thiscol + 1" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="CELL" mode="getoccupied">
    <xsl:param name="occupiedcols" />
    <xsl:param name="col" select="1" />
    <xsl:variable name="thiscol" select="$col + string-length(substring-before(substring($occupiedcols,$col,255),'0'))" />
    <xsl:choose>
      <xsl:when test="contains(substring($occupiedcols,$col,255),'0')">
        <xsl:value-of select="translate(substring-before(substring($occupiedcols,$col,255),'0'),'0123456789','-012345678')" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="translate(substring($occupiedcols,$col,255),'123456789','012345678')" />
      </xsl:otherwise>
    </xsl:choose>
    <xsl:value-of select="@ROWSPAN - 1" />
    <xsl:if test="not(following-sibling::CELL)">
      <xsl:value-of select="translate(substring($occupiedcols,$thiscol + 1, 255),'0123456789','0012345678')" />
    </xsl:if>
    <xsl:apply-templates select="following-sibling::CELL[1]" mode="getoccupied">
      <xsl:with-param name="occupiedcols" select="$occupiedcols"/>
      <xsl:with-param name="col" select="$thiscol + 1" />
    </xsl:apply-templates>
  </xsl:template>
</xsl:stylesheet>

它有一个已知问题:如果一个单元格跨越 9 行以上,它将中断。如果这是一个问题,实际上很容易适应。

另外,它不支持COLSPAN的任何使用。

它的工作原理是传递一串数字,详细说明每列仍有一个单元格的行数,因此在您的示例中,第二行将传递“21020”,它会根据 where 计算 colname 属性0 是。第二遍每行将数字减一,但将每个单元格的 ROWSPAN 替换为 0。

此解决方案还假定所有单元格都具有ROWSPAN 属性,即使它们仅跨越一个。如果不是这样,我可以添加一种支持默认 1 的方法。

【讨论】:

    【解决方案2】:

    我认为一个好主意是为每一列维护一个位向量,该向量包含该列中前一个单元格将扩展到的行数。为此,您必须使用递归,因为您必须修改每一行的位向量,而 XSLT 作为函数式编程语言无法修改“变量”。

    以下样式表展示了一个包含 3 列的表格的想法。

    <xsl:template match="TABLE2">
      <tbody>
        <xsl:call-template name="processRows">
          <xsl:with-param name="rows" select="ROW"/>
        </xsl:call-template>
      </tbody>
    </xsl:template>
    
    <xsl:template name="processRows">
      <xsl:param name="rows"/>
      <xsl:param name="index" select="1"/>
      <!-- Bit vector for the columns -->
      <xsl:param name="col1" select="0"/>
      <xsl:param name="col2" select="0"/>
      <xsl:param name="col3" select="0"/>
    
      <xsl:variable name="cellsBefore2">
        <xsl:choose>
          <xsl:when test="$col1 > 0">0</xsl:when>
          <xsl:otherwise>1</xsl:otherwise>
        </xsl:choose>
      </xsl:variable>
      <xsl:variable name="cellsBefore3">
        <xsl:choose>
          <xsl:when test="$col2 > 0">
            <xsl:value-of select="$cellsBefore2"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="$cellsBefore2 + 1"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:variable>
    
      <row>
        <xsl:if test="$col1 = 0">
          <entry colname="1">
            <xsl:value-of select="$rows[$index]/CELL[1]/text()"/>
          </entry>
        </xsl:if>
        <xsl:if test="$col2 = 0">
          <entry colname="2">
            <xsl:value-of select="$rows[$index]/CELL[$cellsBefore2 + 1]/text()"/>
          </entry>
        </xsl:if>
        <xsl:if test="$col3 = 0">
          <entry colname="3">
            <xsl:value-of select="$rows[$index]/CELL[$cellsBefore3 + 1]/text()"/>
          </entry>
        </xsl:if>
      </row>
      <xsl:if test="$index &lt; count($rows)">
        <xsl:call-template name="processRows">
          <xsl:with-param name="rows" select="$rows"/>
          <xsl:with-param name="index" select="$index + 1"/>
          <xsl:with-param name="col1">
            <xsl:choose>
              <xsl:when test="$col1 > 0">
                <xsl:value-of select="$col1 - 1"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:value-of select="number($rows[$index]/CELL[1]/@ROWSPAN) - 1"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:with-param>
          <xsl:with-param name="col2">
            <xsl:choose>
              <xsl:when test="$col2 > 0">
                <xsl:value-of select="$col2 - 1"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:value-of select="number($rows[$index]/CELL[$cellsBefore2 + 1]/@ROWSPAN) - 1"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:with-param>
          <xsl:with-param name="col3">
            <xsl:choose>
              <xsl:when test="$col3 > 0">
                <xsl:value-of select="$col3 - 1"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:value-of select="number($rows[$index]/CELL[$cellsBefore3 + 1]/@ROWSPAN) - 1"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:with-param>
        </xsl:call-template>
      </xsl:if>
    </xsl:template>
    

    如您所见,问题在于列数是硬编码的。如果您想拥有任意数量的列,您需要能够生成并传递任意长度的列表作为参数。这仅在 XSLT 2.0 中是可能的。但是,如果您使用的是 XSLT 1.1,则可以改为使用 EXSLT(如果它在您的环境中可用),将列表作为结果树写入变量中,并在此变量上使用 exslt:node-set() 以获得可用列表.

    【讨论】:

    • 谢谢!这有很大的帮助。你能告诉我如何将任意长度的列表作为参数传递吗?
    • 我想用另一种解决方案更容易,它通过将向量连接成一个字符串来解决问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-10
    • 1970-01-01
    • 2020-04-16
    • 2020-09-20
    相关资源
    最近更新 更多