2011-01-30 7 views
5

Digamos que tengo este nodo XML:XSL: la transformación de una lista en una tabla 2-D

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

donde hay N item nodos.

Ahora me gustaría transformarlo en una tabla HTML con 4 columnas. (por ejemplo, si N = 12, hay 3 filas completas, y si N = 27, hay 7 filas, la última tiene 3 celdas)

¿Cómo podría hacer esto?

Mi llamado intestino es hacerlo de esta manera, donde {{something}} es lo que no sé cómo poner en práctica:

<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 /> 

Las piezas que no sé cómo poner en práctica, son

  • cómo hacer un predicado para probar el # de item elementos en el nodo actual
  • cómo sacar el elemento enésimo item en el nodo actual

actualización de los comentarios

Cómo rellenar la última fila con vacíos <td /> elementos para que cada fila contiene exactamente las células buscados?

+1

Buena pregunta, +1. Vea mi respuesta para la solución probablemente más corta que ni siquiera utiliza ninguna recursión explícita. :) –

+0

También se agregó una solución XSLT 2.0. :) –

Respuesta

3

Esa es mi solución de trabajo.

Como no proporcionó un resultado deseado, este en particular podría no estar completo para sus necesidades.

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

Entrada de prueba:

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

Salida:

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

hacer la nota: que puede pasar varias columnas de forma dinámica.

Requisitos adicionales y edición.

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

Se puede aplicar a la muestra anterior o para este XML concisa:

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

resultado será:

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

hacer nota:

  1. Datos codificados para agregar elementos, cuando hay menos elementos item que número de columnas.
  2. Elementos extra codificados, si el número de columnas cambiará alguna vez.

Si no va a ser cada vez menos elementos que el número de columnas, sólo se puede aplicar a los item elementos con el mismo predicado y otro diferente mode.

Y última edición. Con un bucle contado.

<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> 
+0

¿Cómo podría editarlo para que rellenara la última fila con elementos vacíos, de modo que cada fila contenga 4 celdas? –

+0

@Jason S, en un momento. – Flack

+0

@Jason S, revisa mi edición. – Flack

5

I. XSLT solución 1,0:

Aquí es probablemente una de las soluciones más cortos posibles que sobre todo no requiere recursión explícita:

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

cuando se aplica esta transformación en el siguiente documento 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> 

el, resultado correcto querido se produce:

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

Explicación:

  1. El número deseado de células por fila se especifica en el parámetro externo/global$pNumCols.

  2. plantillas se aplican sólo en esos hijos del elemento superior, cuya posición es el comienzo de una nueva fila - que se generan por la expresión $k * $pNumCols +1, donde $ k puede ser cualquier número entero.

  3. La plantilla que procesar cada elemento de la fila de arranque crea una fila (tr elemento) y dentro de ella se aplica plantillas en un modo especial para la"copy"$pNumCols empezando por sí mismo.

  4. La plantilla de búsqueda de un item en modo "copy" simplemente crea una célula (td elemento) y salidas dentro de él el valor de cadena del elemento item ser emparejado.

II. XSLT solución 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> 

aplicado en el mismo documento XML como antes, esta transformación produce el mismo resultado, correcta.

Explicación:

  1. La instrucción <xsl:for-each-group> se utiliza para seleccionar los diferentes grupos de item elementos en los que cada grupo contiene los elementos que deben estar representados en una fila.

  2. El operador estándar XPath 2.0 idiv se utiliza para este fin.

  3. La función XSLT 2.0 current-group() contiene todos los elementos que se deben presentar en la fila actual.

+0

+1. Muy elegante. – Flack

+0

+1 Buenas soluciones. –

+1

me salvó el día. Gracias amigo. – Rolf

0

Con fines de cada grupo se puede obtener una solución más elegante:

<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> 
+0

esto crea una tabla con cuatro filas, ¡no una tabla con 4 columnas! –

+0

Gracias.Reemplazado un mod b por techo (a div b) – AtnNn

+0

Hay un operador 'idiv' (división entera) en XPath/XSLT 2.0 –

1

sólo por el estilo, esto XSLT 1.0 hoja de estilo:

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

Con @ entrada de la respuesta de Flack, salida:

<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

+1. Bueno también. – Flack

Cuestiones relacionadas