【问题标题】:xsl: transforming a list into a 2-D tablexsl:将列表转换为二维表
【发布时间】:2011-01-30 22:41:14
【问题描述】:

假设我有这个 XML 节点:

<items>
    <item>...<item>
    <item>...<item>
    <item>...<item>
    <item>...<item>
    <item>...<item>
    ...
</items>

其中有 N 个item 节点。

现在我想把它转换成一个有 4 列的 HTML 表格。 (例如,如果 N=12,则有 3 行完整的行,如果 N=27,则有 7 行,最后有 3 个单元格)

我该怎么做呢?

我的直觉是这样做,{{something}} 是我不知道如何实现的:

<xsl:template match="items">
   <table>
      <xsl:call-template name="partition-items">
         <xsl:with-param name="skip" select="0" />
      </xsl:call-template>
   </table>
</xsl:template> 

<xsl:template name="partition-items">
    <xsl:param name="skip" />
    {{ if # of items in current node > $skip,
          output a row, 
          and call partition-items($skip+4)
    }}
<xsl:template />

我不知道如何实现的部分是

  • 如何创建一个谓词来测试当前节点中的item 个元素
  • 如何获取当前节点的第N个item元素

来自 cmets 的更新

如何用空填充最后一行 &lt;td /&gt; 元素使每一行 是否包含所需的单元格?

【问题讨论】:

  • 好问题,+1。有关甚至不使用任何显式递归的可能最短的解决方案,请参阅我的答案。 :)
  • 还添加了 XSLT 2.0 解决方案。 :)

标签: xml xslt


【解决方案1】:

我。 XSLT 1.0 解决方案:

这可能是最短的解决方案之一,尤其是不需要显式递归

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:param name="pNumCols" select="4"/>

 <xsl:template match="/*">
  <table>
   <xsl:apply-templates select="*[position() mod $pNumCols =1]"/>
  </table>
 </xsl:template>

 <xsl:template match="item">
  <tr>
    <xsl:apply-templates mode="copy" select=
    ". | following-sibling::*[not(position() >= $pNumCols)]"/>
  </tr>
 </xsl:template>

 <xsl:template match="item" mode="copy">
  <td><xsl:value-of select="."/></td>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于以下 XML 文档时

<items>
    <item>1</item>
    <item>2</item>
    <item>3</item>
    <item>4</item>
    <item>5</item>
    <item>6</item>
    <item>7</item>
    <item>8</item>
    <item>9</item>
    <item>10</item>
    <item>11</item>
    <item>12</item>
    <item>13</item>
    <item>14</item>
    <item>15</item>
    <item>16</item>
    <item>17</item>
    <item>18</item>
    <item>19</item>
    <item>20</item>
    <item>21</item>
    <item>22</item>
    <item>23</item>
    <item>24</item>
    <item>25</item>
    <item>26</item>
    <item>27</item>
</items>

产生想要的正确结果

<table>
   <tr>
      <td>1</td>
      <td>2</td>
      <td>3</td>
      <td>4</td>
   </tr>
   <tr>
      <td>5</td>
      <td>6</td>
      <td>7</td>
      <td>8</td>
   </tr>
   <tr>
      <td>9</td>
      <td>10</td>
      <td>11</td>
      <td>12</td>
   </tr>
   <tr>
      <td>13</td>
      <td>14</td>
      <td>15</td>
      <td>16</td>
   </tr>
   <tr>
      <td>17</td>
      <td>18</td>
      <td>19</td>
      <td>20</td>
   </tr>
   <tr>
      <td>21</td>
      <td>22</td>
      <td>23</td>
      <td>24</td>
   </tr>
   <tr>
      <td>25</td>
      <td>26</td>
      <td>27</td>
   </tr>
</table>

说明

  1. 每行所需的单元格数在外部/全局参数中指定$pNumCols

  2. 模板仅应用于顶部元素的此类子元素,其位置是新行的开始 -- 它们由表达式$k * $pNumCols +1 生成,其中$k 可以可以是任意整数。

  3. 处理每个行起始项的模板会创建一行(tr 元素),并在其中以特殊模式 "copy"$pNumCols 应用模板,以自己。

  4. 在模式"copy" 中匹配item 的模板只需创建一个单元格(td 元素) 并在其中输出匹配的item 元素的字符串值.

二。 XSLT 2.0 解决方案

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:param name="pNumCols" select="4"/>

    <xsl:template match="items">
        <table>
            <xsl:for-each-group select="item"
            group-by="(position()-1) idiv $pNumCols">
                <tr>
                    <xsl:for-each select="current-group()">
                        <td>
                            <xsl:apply-templates/>
                        </td>
                    </xsl:for-each>
                </tr>
            </xsl:for-each-group>
        </table>
    </xsl:template>
</xsl:stylesheet>

与以前一样应用于相同的 XML 文档,此转换会产生相同的正确结果。

说明

  1. &lt;xsl:for-each-group&gt; 指令用于选择不同组的item 元素,其中每组包含必须在一行中表示的元素。

  2. 标准 XPath 2.0 运算符 idiv 用于此目的

  3. XSLT 2.0 函数current-group() 包含当前行中必须显示的所有项目

【讨论】:

    【解决方案2】:

    这是我的工作解决方案

    由于您没有提供所需的输出,因此该特定输出可能无法满足您的需求。

    <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:call-template name="make-columns">
                    <xsl:with-param name="nodelist" select="item"/>
                </xsl:call-template>
            </table>
        </xsl:template>
    
        <xsl:template name="make-columns">
            <xsl:param name="nodelist"/>
            <xsl:param name="columns-number" select="4"/>
    
            <tr>
                <xsl:apply-templates select="$nodelist[
                                not(position() > $columns-number)
                                ]"/>
            </tr>
    
            <!-- If some nodes are left, recursively call current
            template, passing only nodes that are left -->
            <xsl:if test="count($nodelist) > $columns-number">
                <xsl:call-template name="make-columns">
                    <xsl:with-param name="nodelist" select="$nodelist[
                                            position() > $columns-number
                                            ]"/>
                </xsl:call-template>
            </xsl:if>
        </xsl:template>
    
        <xsl:template match="item">
            <td>
                <xsl:apply-templates/>
            </td>
        </xsl:template>
    
    </xsl:stylesheet>
    

    测试输入:

    <items>
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
        <item>5</item>
        <item>6</item>
        <item>7</item>
        <item>8</item>
        <item>9</item>
        <item>10</item>
        <item>11</item>
        <item>12</item>
        <item>13</item>
        <item>14</item>
        <item>15</item>
        <item>16</item>
        <item>17</item>
        <item>18</item>
        <item>19</item>
        <item>20</item>
        <item>21</item>
        <item>22</item>
        <item>23</item>
        <item>24</item>
        <item>25</item>
        <item>26</item>
        <item>27</item>
    </items>
    

    输出:

    <table>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>3</td>
            <td>4</td>
        </tr>
        <tr>
            <td>5</td>
            <td>6</td>
            <td>7</td>
            <td>8</td>
        </tr>
        <tr>
            <td>9</td>
            <td>10</td>
            <td>11</td>
            <td>12</td>
        </tr>
        <tr>
            <td>13</td>
            <td>14</td>
            <td>15</td>
            <td>16</td>
        </tr>
        <tr>
            <td>17</td>
            <td>18</td>
            <td>19</td>
            <td>20</td>
        </tr>
        <tr>
            <td>21</td>
            <td>22</td>
            <td>23</td>
            <td>24</td>
        </tr>
        <tr>
            <td>25</td>
            <td>26</td>
            <td>27</td>
        </tr>
    </table>
    

    请注意:您可以动态传递列号。

    其他要求和修改。

    <xsl:stylesheet
        version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:my="http://localhost"
        exclude-result-prefixes="my">
        <xsl:output method="html" indent="yes"/>
    
        <my:layout>
            <td/><td/><td/><td/>
            <td/><td/><td/><td/>
            <td/><td/><td/><td/>
            <td/><td/><td/><td/>
        </my:layout>
    
        <xsl:template match="/*">
            <table>
                <xsl:call-template name="make-columns">
                    <xsl:with-param name="nodelist" select="item"/>
                </xsl:call-template>
            </table>
        </xsl:template>
    
        <xsl:template name="make-columns">
            <xsl:param name="nodelist"/>
            <xsl:param name="columns-number" select="4"/>
    
            <tr>
                <xsl:apply-templates select="$nodelist[
                                not(position() > $columns-number)
                                ]"/>
                <xsl:if test="count($nodelist) &lt; $columns-number">
                    <xsl:copy-of select="document('')/*/my:layout/td[
                        position() &lt;= $columns-number - count($nodelist)
                        ]"/>
                </xsl:if>
            </tr>
    
            <!-- If some nodes are left, recursively call current
            template, passing only nodes that are left -->
            <xsl:if test="count($nodelist) > $columns-number">
                <xsl:call-template name="make-columns">
                    <xsl:with-param name="nodelist" select="$nodelist[
                                            position() > $columns-number
                                            ]"/>
                </xsl:call-template>
            </xsl:if>
        </xsl:template>
    
        <xsl:template match="item">
            <td>
                <xsl:apply-templates/>
            </td>
        </xsl:template>
    
    </xsl:stylesheet>
    

    它可以应用于前面的示例或这个简洁的 XML:

    <items>
        <item>1</item>
    </items>
    

    结果将是:

    <table>
        <tr>
            <td>1</td>
            <td xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="http://localhost"></td>
            <td xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="http://localhost"></td>
            <td xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="http://localhost"></td>
        </tr>
    </table>
    

    请注意:

    1. item 元素少于列数时,添加元素的硬编码数据。
    2. 额外的硬编码元素,如果列数会发生变化。

    如果元素的数量永远不会少于列数,您可以只应用具有相同谓词和不同 modeitem 元素。

    最后一次编辑。带有计数循环。

    <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:call-template name="make-columns">
                    <xsl:with-param name="nodelist" select="item"/>
                </xsl:call-template>
            </table>
        </xsl:template>
    
        <xsl:template name="make-columns">
            <xsl:param name="nodelist"/>
            <xsl:param name="columns-number" select="4"/>
    
            <tr>
                <xsl:apply-templates select="$nodelist[
                                not(position() > $columns-number)
                                ]"/>
                <xsl:if test="count($nodelist) &lt; $columns-number">
                    <xsl:call-template name="empty-cells">
                        <xsl:with-param name="finish" select="$columns-number - count($nodelist)"/>
                    </xsl:call-template>
                </xsl:if>
            </tr>
    
            <!-- If some nodes are left, recursively call current
            template, passing only nodes that are left -->
            <xsl:if test="count($nodelist) > $columns-number">
                <xsl:call-template name="make-columns">
                    <xsl:with-param name="nodelist" select="$nodelist[
                                            position() > $columns-number
                                            ]"/>
                </xsl:call-template>
            </xsl:if>
        </xsl:template>
    
        <xsl:template match="item">
            <td>
                <xsl:apply-templates/>
            </td>
        </xsl:template>
    
        <xsl:template name="empty-cells">
            <xsl:param name="finish"/>
            <td/>
            <xsl:if test="not($finish = 1)">
                <xsl:call-template name="empty-cells">
                    <xsl:with-param name="finish" select="$finish - 1"/>
                </xsl:call-template>
            </xsl:if>
        </xsl:template>
    
    </xsl:stylesheet>
    

    【讨论】:

    • 你怎么能编辑它,让它用空的 元素填充最后一行,以便每行包含 4 个单元格?
    • 编辑更新中的copy-of 似乎会导致Firefox 出现错误......我想我只是要添加一个call-template 并传入count($nodelist) - $columns-number
    • @Jason S,在我的 3.6 版中运行良好。可能是命名空间 uri 解决问题。是的,您可以只使用一个小的有限循环。我会发布更新。我想这会是一个更好的方法。
    • @Flack: +1 为了付出巨大的努力,除了我认为最后一个正确的解决方案还有许多命名模板......
    • @Alejandro。 XSLT 中的封装方式并不多。虽然我同意,但我仍然认为这在很大程度上是一个风格问题。
    【解决方案3】:

    只是为了样式,这个 XSLT 1.0 样式表:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:param name="pColumns" select="4"/>
        <xsl:template match="/*">
            <table>
                <xsl:apply-templates select="*[position() mod $pColumns = 1]"/>
            </table>
        </xsl:template>
        <xsl:template match="item">
            <xsl:variable name="vItems"
                          select=".|following-sibling::*[$pColumns > position()]"/>
            <tr>
                <xsl:apply-templates select="$vItems" mode="makeCell"/>
                <xsl:call-template name="fillRow">
                    <xsl:with-param name="pItems" 
                                    select="$pColumns - count($vItems)"/>
                </xsl:call-template>
            </tr>
        </xsl:template>
        <xsl:template match="item" mode="makeCell">
            <td>
                <xsl:value-of select="."/>
            </td>
        </xsl:template>
        <xsl:template name="fillRow">
            <xsl:param name="pItems" select="0"/>
            <xsl:if test="$pItems">
                <td/>
                <xsl:call-template name="fillRow">
                    <xsl:with-param name="pItems" select="$pItems - 1"/>
                </xsl:call-template>
            </xsl:if>
        </xsl:template>
    </xsl:stylesheet>
    

    使用@Flack 的回答输入,输出:

    <table>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>3</td>
            <td>4</td>
        </tr>
        <tr>
            <td>5</td>
            <td>6</td>
            <td>7</td>
            <td>8</td>
        </tr>
        <tr>
            <td>9</td>
            <td>10</td>
            <td>11</td>
            <td>12</td>
        </tr>
        <tr>
            <td>13</td>
            <td>14</td>
            <td>15</td>
            <td>16</td>
        </tr>
        <tr>
            <td>17</td>
            <td>18</td>
            <td>19</td>
            <td>20</td>
        </tr>
        <tr>
            <td>21</td>
            <td>22</td>
            <td>23</td>
            <td>24</td>
        </tr>
        <tr>
            <td>25</td>
            <td>26</td>
            <td>27</td>
            <td />
        </tr>
    </table>
    

    【讨论】:

      【解决方案4】:

      使用 for-each-group 您可以获得更优雅的解决方案:

      <xsl:template match="items">
        <table>
          <xsl:for-each-group select="item" group-by="ceiling(position() div $column_width)">
            <tr>
              <xsl:for-each select="current-group()">
                <td>
                  <xsl:apply-templates/>
                </td>
              </xsl:for-each>
            </tr>
          </xsl:for-each-group>
        </table>
      </xsl:template>
      

      【讨论】:

      • 这将创建一个有四行的表,而不是一个有 4 列的表!
      • 谢谢。将 mod b 替换为天花板(a div b)
      • XPath/XSLT 2.0 中有一个idiv 运算符(整数除法)
      猜你喜欢
      • 2021-12-18
      • 2022-12-29
      • 2019-09-17
      • 2015-11-20
      • 2011-12-04
      • 2020-04-05
      • 2023-03-27
      • 2020-01-15
      相关资源
      最近更新 更多