xsl tutorial transformar transformador test freeformater espaƱol ejemplos convertir xslt grouping

tutorial - xslt editor



XSLT Agrupamiento de 3 niveles en los atributos (4)

Bueno, de momento abandoné las llaves y la agrupación Muenchian. Apenas lo entiendo, y piratearlo no produjo los resultados deseados. Entiendo recursividad, sin embargo, fui con este enfoque recursivo que produce el resultado deseado. Lo encontré en http://www.biglist.com/lists/xsl-list/archives/200412/msg00865.html

El hilo de discusión advierte que el rendimiento sufre una gran entrada vs. el enfoque de Muenchian, y la solución a continuación es prolija y repetitiva (podría prolly refactorizarla para hacerla más pequeña y más difícil de entender ;-) pero 1) en realidad me funciona, y 2) para mi problema actual, los conjuntos de entrada son bastante pequeños, no más de una docena de nodos Parte de nivel inferior.

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- recursive grouping http://www.biglist.com/lists/xsl-list/archives/200412/msg00865.html --> <xsl:template match="//Plans"> <html> <head> <title>test grouping</title> </head> <body> <ol> <xsl:call-template name="PlanGrouping"> <xsl:with-param name="list" select="Plan"/> </xsl:call-template> </ol> </body> </html> </xsl:template> <xsl:template name="PlanGrouping"> <xsl:param name="list"/> <!-- Selecting the first Area ID as group identifier and the group itself--> <xsl:variable name="group-identifier" select="$list[1]/@AreaID"/> <xsl:variable name="group" select="$list[@AreaID = $group-identifier]"/> <!-- Do some work for the group --> <li> Area <xsl:value-of select="$group-identifier"/>: <ol> <xsl:call-template name="AreaGrouping"> <xsl:with-param name="list" select="$list[(@AreaID = $group-identifier)]"/> </xsl:call-template> </ol> </li> <!-- If there are other groups left, calls itself --> <xsl:if test="count($list)>count($group)"> <xsl:call-template name="PlanGrouping"> <xsl:with-param name="list" select="$list[not(@AreaID = $group-identifier)]"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="AreaGrouping"> <xsl:param name="list"/> <!-- Selecting the first Unit ID as group identifier and the group itself--> <xsl:variable name="group-identifier" select="$list[1]/@UnitID"/> <xsl:variable name="group" select="$list[@UnitID = $group-identifier]"/> <!-- Do some work for the group --> <li> Unit <xsl:value-of select="$group-identifier"/>: <ol> <xsl:call-template name="Parts"> <xsl:with-param name="list" select="$list[(@UnitID = $group-identifier)]"/> </xsl:call-template> </ol> </li> <!-- If there are other groups left, calls itself --> <xsl:if test="count($list)>count($group)"> <xsl:call-template name="AreaGrouping"> <xsl:with-param name="list" select="$list[not(@UnitID = $group-identifier)]"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="Parts"> <xsl:param name="list"/> <xsl:for-each select="$list/Part"> <li> Part <xsl:value-of select="@ID"/> (<xsl:value-of select="@Name"/>) </li> </xsl:for-each> </xsl:template> </xsl:stylesheet>

De acuerdo, sé que se han formulado y respondido variaciones sobre esto; Los he estado leyendo todo el día, pero todavía estoy atascado. Entonces, aquí va:

Necesito crear una lista resumida en HTML desde algún XML.

Dado este XML:

<Root><!-- yes, I know I don''t need a ''Root'' element! Legacy code... --> <Plans> <Plan AreaID="1" UnitID="83"> <Part ID="9122" Name="foo" /> <Part ID="9126" Name="bar" /> </Plan> <Plan AreaID="1" UnitID="86"> <Part ID="8650" Name="baz" /> </Plan> <Plan AreaID="2" UnitID="26"> <Part ID="215" Name="quux" /> </Plan> <Plan AreaID="1" UnitID="95"> <Part ID="7350" Name="meh" /> </Plan> </Plans> </Root>

Necesito emitir:

<ol> <li>Area 1: <ol><!-- units in Area 1 --> <li>Unit 83: <ol> <li>Part 9122 (foo)</li> <li>Part 9126 (bar)</li> </ol> </li> <li>Unit 86: <ol> <li>Part 8650 (baz)</li> </ol> <li>Unit 95: <ol> <li>Part 7350 (meh)</li> </ol> </li> </ol><!-- /units in Area 1--> </li> <li>Area 2: <ol><!-- units in Area 2 --> <li>Unit 26: <ol> <li>Part 215 (quux)</li> </ol> </li> </ol><!-- /units in Area 2--> </li> </ol>

Tengo funcionando la agrupación externa. Obtengo elementos de la lista de nivel superior para el Área 1 y 2. Pero no puedo obtener las secuencias de las Unidades en las Áreas. Tampoco obtengo salida ni repito el mismo valor. Ni siquiera he bajado al nivel de Parte :-(

He estado trabajando en una hoja de estilos como esta:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" <xsl:output method="html" indent="yes"/> <xsl:key name="kAreaID" match="Plan" use="@AreaID" /> <xsl:key name="kUnitID" match="Plan" use="@UnitID" /> <xsl:template match="/Root/Plans"> <html><head><title>test grouping</title></head> <body> <ol> <xsl:for-each select="./Plan[generate-id(.) = generate-id( key( ''kAreaID'', @AreaID )[1] )]" > <xsl:sort order="ascending" select="./@AreaID" /> <li>Area <xsl:value-of select="@AreaID"/>: <ol> <xsl:for-each select="key( ''kUnitID'', @UnitID )"> <li>Unit <xsl:value-of select="@UnitID"/>: <ol> <li>(Parts go here...)</li> </ol> </li> </xsl:for-each> </ol> </li> </xsl:for-each> </ol> </body> </html> </xsl:template> </xsl:stylesheet>

¡Cualquier ayuda es muy apreciada!


Esto hace lo que quiere pero con recursión, no agrupación. Lo siento, todavía estoy aprendiendo cómo usar la agrupación:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" indent="yes"/> <xsl:key name="kAreaID" match="Plan" use="@AreaID" /> <xsl:key name="kUnitID" match="Plan" use="@UnitID" /> <xsl:template match="/Root/Plans"> <html> <head> <title>test grouping</title> </head> <body> <ol> <xsl:for-each select="./Plan[generate-id(.) = generate-id( key( ''kAreaID'', @AreaID )[1] )]" > <xsl:sort order="ascending" select="./@AreaID" /> <xsl:variable name="curArea" select="@AreaID"/> <li> Area <xsl:value-of select="$curArea"/>: <ol> <xsl:for-each select="ancestor::Root/Plans/Plan[@AreaID = $curArea]"> <xsl:variable name="curUnit" select="@UnitID"/> <li> Unit <xsl:value-of select="$curUnit"/>: <ol> <xsl:for-each select="ancestor::Root/Plans/Plan[@AreaID = $curArea and @UnitID = $curUnit]/Part"> <li> Part <xsl:value-of select="concat(@ID, '' ('', @Name, '')'')"/> </li> </xsl:for-each> </ol> </li> </xsl:for-each> </ol> </li> </xsl:for-each> </ol> </body> </html> </xsl:template> </xsl:stylesheet>


No creo que deba usar la clave kUnitID en absoluto. En su lugar, reemplace la siguiente línea ...

<xsl:for-each select="key( ''kUnitID'', @UnitID )">

..con esta línea en su lugar, que debe recorrer todas las partes que coincidan con el ID de área actual

<xsl:for-each select="key( ''kAreaID'', @AreaID )">

Y dentro de este ciclo, para su código (Parts go here ...) , puede simplemente pasar por encima de las partes

<xsl:for-each select="Part"> <li>Part (<xsl:value-of select="@ID" />)</li> </xsl:for-each>


Aquí está la solución de agrupación Muenchian que está buscando.

Yendo desde el XML original que me proporcionó, pensé que agrupar por AreaID sería suficiente, pero resulta que también se necesita una segunda agrupación por UnitID.

Aquí está mi solución XSLT 1.0 modificada. No es mucho más complejo que la solución original:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <xsl:key name="kPlanByArea" match="Plan" use="@AreaID" /> <xsl:key name="kPlanByAreaAndUnit" match="Plan" use="concat(@AreaID, '','', @UnitID)" /> <xsl:template match="/"> <xsl:apply-templates select="Root/Plans" /> </xsl:template> <!-- main template --> <xsl:template match="Plans"> <ol> <!-- group by ''{@AreaID}'' (note the template mode!) --> <xsl:apply-templates mode="area-group" select=" Plan[ generate-id() = generate-id( key(''kPlanByArea'', @AreaID)[1] ) ] "> <xsl:sort select="@AreaID" data-type="number" /> </xsl:apply-templates> </ol> </xsl:template> <!-- template to output each ''{@AreaID}'' group --> <xsl:template match="Plan" mode="area-group"> <li> <xsl:value-of select="concat(''Area '', @AreaID)" /> <ol> <!-- group by ''{@AreaID},{@UnitID}'' --> <xsl:apply-templates mode="unit-group" select=" key(''kPlanByArea'', @AreaID)[ generate-id() = generate-id( key(''kPlanByAreaAndUnit'', concat(@AreaID, '','', @UnitID))[1] ) ] "> <xsl:sort select="@UnitID" data-type="number" /> </xsl:apply-templates> </ol> </li> </xsl:template> <!-- template to output each ''{@AreaID},{@UnitID}'' group --> <xsl:template match="Plan" mode="unit-group"> <li> <xsl:value-of select="concat(''Unit '', @UnitID)" /> <ol> <xsl:apply-templates select=" key(''kPlanByAreaAndUnit'', concat(@AreaID, '','', @UnitID))/Part "> <xsl:sort select="@UnitID" data-type="number" /> </xsl:apply-templates> </ol> </li> </xsl:template> <!-- template to output Parts into a list --> <xsl:template match="Part"> <li> <xsl:value-of select="concat(''Part '', @ID, '' ('', @Name ,'')'')" /> </li> </xsl:template> </xsl:stylesheet>

Como su XML no se encuentra, agregué un UnitID para agrupar en:

<Plan AreaID="1" UnitID="86"> <Part ID="8651" Name="zzz" /> </Plan>

Y aquí está el resultado:

<ol> <li>Area 1 <ol> <li>Unit 83 <ol> <li>Part 9122 (foo)</li> <li>Part 9126 (bar)</li> </ol> </li> <li>Unit 86 <ol> <li>Part 8650 (baz)</li> <li>Part 8651 (zzz)</li> </ol> </li> <li>Unit 95 <ol> <li>Part 7350 (meh)</li> </ol> </li> </ol> </li> <li>Area 2 <ol> <li>Unit 26 <ol> <li>Part 215 (quux)</li> </ol> </li> </ol> </li> </ol>

Como parece que tiene problemas con la tecla XSL, aquí mi intento de explicación:

Una <xsl:key> es absolutamente equivalente a la matriz asociativa (map, hash, como quiera que la llame) conocida en muchos lenguajes de programación. Esta:

<xsl:key name="kPlanByAreaAndUnit" match="Plan" use="concat(@AreaID, '','', @UnitID)" />

genera una estructura de datos que podría expresarse en JavaScript de esta manera:

var kPlanByAreaAndUnit = { "1,83": [''array of all <Plan> nodes with @AreaID="1" and @UnitID="83"''], "1,86": [''array of all <Plan> nodes with @AreaID="1" and @UnitID="86"''], /* ... */ "1,95": [''array of all <Plan> nodes with @AreaID="1" and @UnitID="95"''] };

La función para acceder a la estructura de datos se llama key() . Entonces, esta expresión XPath:

key(''kPlanByAreaAndUnit'', concat(@AreaID, '','', @UnitID))

es el equivalente lógico de (en JavaScript, de nuevo):

kPlanByAreaAndUnit[this.AreaID + '','' + this.UnitID];

devolver una matriz (un conjunto de nodos, más correctamente) de todos los nodos que coincidan con la cadena clave dada (la clave siempre es una cadena). Este conjunto de nodos se puede usar como cualquier otro conjunto de nodos en XSLT, es decir, como los que recupera a través de XPath "tradicional". Esto significa que puede aplicarle condiciones (predicados):

<!-- first node only... --> key(''kPlanByAreaAndUnit'', concat(@AreaID, '','', @UnitID))[1] <!-- nodes that have <Part> children only... --> key(''kPlanByAreaAndUnit'', concat(@AreaID, '','', @UnitID))[Part]

o utilícelo como base para la navegación XPath:

<!-- the actual <Part> children of matched nodes... --> key(''kPlanByAreaAndUnit'', concat(@AreaID, '','', @UnitID))/Part

y así. Esto también significa que podemos usarlo como una expresión "select" para <xsl:apply-templates> , y podemos usarlo como base para agrupar. Lo que nos lleva al núcleo de la hoja de estilo anterior (si has entendido bien esta cuestión, también has entendido el resto de la solución):

key(''kPlanByArea'', @AreaID)[ generate-id() = generate-id( key(''kPlanByAreaAndUnit'', concat(@AreaID, '','', @UnitID))[1] ) ]

En JavaScript nuevamente, esto podría expresarse como:

// the result will be a node-set, so we prepare an array var selectedNodes = []; // "key(''kPlanByArea'', @AreaID)" var nodeSet = kPlanByArea[this.AreaID]; // "[...]" - the [] actually triggers a loop that applies // the predicate expression to all nodes in the set, so we do: for (var i = 0; i < nodeSet.length; i++) { // use the current node for any calculations var c = nodeSet[i]; if ( // if the current node === the *first* node in kPlanByAreaAndUnit... generateId(c) == generateId(kPlanByAreaAndUnit[c.AreaID + '','' + c.UnitID][0]) ) { // ...include it in the resulting selection selectedNodes.push(c) } }

Una vez realizada la expresión, solo se seleccionan los nodos que son los primeros respectivos con una combinación dada de "ID de área, ID de unidad", efectivamente los hemos agrupado en su combinación "ID de área, ID de unidad".

La aplicación de una plantilla a este conjunto de nodos hace que cada combinación aparezca solo una vez. Mi <xsl:template match="Plan" mode="unit-group"> luego recupera la lista completa nuevamente para lograr un resultado completo para cada grupo.

Espero que el uso de JavaScript para explicar el concepto haya sido una buena idea.