xsl: преобразование списка в двумерную таблицу - PullRequest
6 голосов
/ 31 января 2011

Допустим, у меня есть этот узел XML:

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

, где есть N item узлов.

Теперь я хотел бы преобразовать его в таблицу HTML с 4 столбцами.(например, если 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 в текущем узле
  • как получить элемент Nth item в текущем узле

Обновление по комментариям

Как заполнить последнюю строку пустыми <td /> элементами, чтобы каждая строка содержала именно нужные ячейки?

Ответы [ 4 ]

5 голосов
/ 31 января 2011

I. Решение 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. Шаблон, соответствующий item в режиме "copy", просто создает ячейку (td элемент) и выводит внутри нее строковое значение сопоставляемого элемента item.

II. Решение 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. Инструкция <xsl:for-each-group> используется для выбора различных групп элементов item, где каждая группа содержит элементы, которые должны быть представлены в одной строке.

  2. Стандартный оператор XPath 2.0 idiv используется для этой цели.

  3. Функция XSLT 2.0 current-group() содержит все элементы, которые должны быть представлены в текущей строке .

4 голосов
/ 31 января 2011

Это мой рабочий раствор .

Поскольку вы не предоставили желаемый результат, этот конкретный может быть неполным для ваших нужд.

<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. Дополнительные жестко закодированные элементы, если количество столбцов когда-либо изменится.

Если элементов не будет меньше, чем количество столбцов, вы можете просто применить к элементам item с тем же предикатом и другим mode.

И последнее редактирование. Со счетным циклом.

<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>
1 голос
/ 31 января 2011

Только для стиля, эта таблица стилей 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>

С вводом ответа @ Флэка, вывод:

<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>
0 голосов
/ 31 января 2011

С каждой группой вы можете получить более элегантное решение:

<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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...