xsl:将列表转换为2-D表

时间:2011-01-30 22:41:15

标签: xml xslt

假设我有这个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元素

从评论中更新

  

如何用空填充最后一行   <td />元素使每行   包含完全想要的细胞?

4 个答案:

答案 0 :(得分:5)

予。 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. 模板仅应用于top元素的子元素,其位置是新行的开头 - 它们由表达式$k * $pNumCols +1生成,其中$ k可以是任何整数。

  3. 处理每个行起始项的模板会创建一行(tr元素),并在其中为{的特殊模式"copy" 应用模板{1}}从自身开始。

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

  5. <强> II。 XSLT 2.0解决方案:

    item

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

    <强>解释

    1. 使用<xsl:for-each-group>指令来选择<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> 元素的不同组,其中每个组包含必须在一行中表示的元素。

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

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

答案 1 :(得分:4)

这是我的工作解决方案

由于您未提供所需的输出,因此根据您的需要,此特定输出可能不完整。

<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. 额外的硬编码元素,如果列数会发生变化。
  3. 如果元素数量少于列数,则只需应用具有相同谓词和不同item的{​​{1}}元素。

    上次修改。使用计数循环。

    mode

答案 2 :(得分:1)

仅限于样式,这个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>

答案 3 :(得分:0)

使用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>