2012-10-08 44 views
5

¿Cómo funciona Muenchian grouping en detalles?Aplicación de la agrupación de Muenchian para un XML simple con XSLT

Tengo un documento XML sencilla generada a partir de una base de datos:

<CLIENTS> 
    <CLIENT> 
     <NAME>John</NAME> 
     <ACCOUNT_NUMBER>1424763562761</ACCOUNT_NUMBER> 
     <LAST_USED>2012-10-03</LAST_USED> 
     <AMOUNT>5000</AMOUNT> 

    </CLIENT> 
    <CLIENT> 
     <NAME>John</NAME> 
     <ACCOUNT_NUMBER>543667543732</ACCOUNT_NUMBER> 
     <LAST_USED>2012-10-02</LAST_USED> 
     <AMOUNT>10000</AMOUNT> 
    </CLIENT> 
     ... 

me gustaría al grupo por el nodo de nombre. ¿Cómo puedo la salida deseada para ser la siguiente?

<ClIENTS> 
    <CLIENT> 
     <NAME>John</NAME> 
     <ACCOUNT> 
      <ACCOUNT_NUMBER>1424763562761</ACCOUNT_NUMBER> 
      <LAST_USED>2012-10-03</LAST_USED> 
      <AMOUNT>5000</AMOUNT> 
     </ACCOUNT> 
     <ACCOUNT> 
      <ACCOUNT_NUMBER>543667543732</ACCOUNT_NUMBER> 
      <LAST_USED>2012-10-03</LAST_USED> 
      <AMOUNT>10000</AMOUNT> 
     </ACCOUNT> 
     .... 
</CLIENTS> 

Respuesta

8

Leer www.jenitennison.com/xslt/grouping/muenchian.xml, para una ayuda con el código de definir una clave

<xsl:key name="client-by-name" match="CLIENT" use="NAME"/> 

a continuación, utilizar las plantillas como

<xsl:template match="@* | node()"> 
    <xsl:copy> 
    <xsl:apply-templates select="@* | node()"/> 
    </xsl:copy> 
</xsl:template> 


<xsl:template match="CLIENTS"> 
    <xsl:copy> 
    <xsl:apply-templates select="CLIENT[generate-id() = generate-id(key('client-by-name', NAME)[1])]" mode="group"/> 
    <xsl:copy> 
</xsl:template> 

<xsl:template match="CLIENT" mode="group"> 
    <xsl:copy> 
    <xsl:copy-of select="NAME"/> 
    <xsl:apply-templates select="key('client-by-name', NAME)"/> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="CLIENT"> 
    <ACCOUNT> 
    <xsl:apply-templates select="node()[not(self::NAME)]"/> 
    </ACCOUNT> 
</xsl:template> 

[editar] Si desea utilizar XSLT 2.0 a continuación de Por supuesto que no necesita agrupar Muenchian, en su lugar usa

<xsl:template match="@* | node()"> 
    <xsl:copy> 
    <xsl:apply-templates select="@* , node()"/> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="CLIENTS"> 
    <xsl:copy> 
    <xsl:for-each-group select="CLIENT" group-by="NAME"> 
     <CLIENT> 
     <xsl:apply-templates select="NAME, current-group()"/> 
     </CLIENT> 
    </xsl:for-each-group> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="CLIENT"> 
    <ACCOUNT> 
    <xsl:apply-templates select="node() except NAME"/> 
    </ACCOUNT> 
</xsl:template> 
+0

Muchas gracias por las respuestas útiles chicos. ¿Es posible resolver el mismo problema con xslt 2.0 xls: for-each-group? En caso afirmativo, ¿puede publicar la solución? – user1728778

+0

@ user1728778 - sí, esto es posible. Por favor vea mi respuesta para una solución compacta XSLT 2.0. – ABach

4

Jeni Tennison rompe los pasos necesarios para realizar el agrupamiento Muenchian aquí:

http://www.jenitennison.com/xslt/grouping/muenchian.html

En esencia, usar XSLT para asignar una clave para el nodo, esta clave se puede repetir si hay nodos idénticos en el documento. El XSLT luego pasa por cada tecla y le permite generar los nodos con las teclas correspondientes.

Así, en la respuesta de Martin, esta línea es la creación de una clave para cada cliente en función del contenido del nodo NOMBRE (recuerda si el nombre es el mismo para varios clientes, también lo hará la tecla):

<xsl:key name="client-by-name" match="CLIENT" use="NAME"/> 

a continuación, quiere pasar por todas las llaves y encontrar la primera instancia de cada uno (de nuevo usando la respuesta de Martin)

<xsl:apply-templates select="CLIENT[generate-id() = generate-id(key('client-by-name', NAME)[1])]" mode="group"/> 

a continuación, desea encontrar todos los clientes que coinciden con la clave para poder dar salida a su detalle (otra vez, Martins)

<xsl:apply-templates select="key('client-by-name', NAME)"/> 

Desde aquí se necesitaría otra plantilla para dar salida a los datos de cliente

3

Muenchian agrupación (como por @ respuesta de Martin) elimina la redundancia que una estrategia de agrupación más tradicional tiene, mientras que la agrupación.

Sin Muenchian Agrupación, plantillas utiliza generalmente preceding-sibling o following-sibling para determinar la primera instancia candidato de cada grupo y, a continuación requeriría una segunda consulta para buscar todos los nodos que coinciden con el grupo, como sigue:

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

    <xsl:template match="CLIENTS"> 
     <CLIENTS> 
      <!--Only find the 'first' instance of each client--> 
      <xsl:apply-templates select="CLIENT[not(NAME = preceding-sibling::CLIENT/NAME)]" mode="client"/> 
     </CLIENTS> 
    </xsl:template> 

    <xsl:template match="CLIENT" mode="client"> 
     <xsl:variable name="name" select="NAME"/> 
     <CLIENT> 
      <NAME> 
       <xsl:value-of select="$name"/> 
      </NAME> 
      <ACCOUNTS> 
       <!--Note that we now have to find the other matching clients *again* - this is the inefficiency that Muenchian grouping eliminates--> 
       <xsl:apply-templates select="/CLIENTS/CLIENT[NAME/text()=$name]" mode="account" /> 
      </ACCOUNTS> 
     </CLIENT> 
    </xsl:template> 

    <xsl:template match="CLIENT" mode="account"> 
     <ACCOUNT> 
      <!--Copy everything else except Name, which is the grouping key --> 
      <xsl:copy-of select="@* | *[not(local-name='NAME')]"/> 
     </ACCOUNT> 
    </xsl:template> 

</xsl:stylesheet> 
+2

+1 para explicar por qué Muenchian Grouping es incluso necesario. – ABach

1

En un comentario anterior (bajo la respuesta de @Martin), el OP preguntó si el elemento for-each-group de XSLT 2.0 podría usarse para resolver este problema.

Cuando este XSLT 2.0 solución:

<?xml version="1.0" encoding="utf-8"?> 
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="2.0"> 
    <xsl:output omit-xml-declaration="no" indent="yes" /> 
    <xsl:strip-space elements="*" /> 

    <xsl:template match="/*"> 
    <CLIENTS> 
     <xsl:for-each-group select="CLIENT" group-by="NAME"> 
     <CLIENT> 
      <xsl:sequence select="NAME" /> 
      <xsl:for-each select="current-group()"> 
      <ACCOUNT> 
       <xsl:sequence select="*[not(self::NAME)]" /> 
      </ACCOUNT> 
      </xsl:for-each> 
     </CLIENT> 
     </xsl:for-each-group> 
    </CLIENTS> 
    </xsl:template> 

</xsl:stylesheet> 

... se aplica a XML original de la OP:

<CLIENTS> 
    <CLIENT> 
    <NAME>John</NAME> 
    <ACCOUNT_NUMBER>1424763562761</ACCOUNT_NUMBER> 
    <LAST_USED>2012-10-03</LAST_USED> 
    <AMOUNT>5000</AMOUNT> 
    </CLIENT> 
    <CLIENT> 
    <NAME>John</NAME> 
    <ACCOUNT_NUMBER>543667543732</ACCOUNT_NUMBER> 
    <LAST_USED>2012-10-02</LAST_USED> 
    <AMOUNT>10000</AMOUNT> 
    </CLIENT> 
</CLIENTS> 

... el resultado deseado se produce:

<?xml version="1.0" encoding="utf-8"?> 
<CLIENTS> 
    <CLIENT> 
    <NAME>John</NAME> 
    <ACCOUNT> 
     <ACCOUNT_NUMBER>1424763562761</ACCOUNT_NUMBER> 
     <LAST_USED>2012-10-03</LAST_USED> 
     <AMOUNT>5000</AMOUNT> 
    </ACCOUNT> 
    <ACCOUNT> 
     <ACCOUNT_NUMBER>543667543732</ACCOUNT_NUMBER> 
     <LAST_USED>2012-10-02</LAST_USED> 
     <AMOUNT>10000</AMOUNT> 
    </ACCOUNT> 
    </CLIENT> 
</CLIENTS> 

Explicación:

Como ya has conjeturado correctamente, XSLT 2.0 introdujo el elemento for-each-group (y sus socios asociados, como current-group()) para eliminar metodologías de agrupación sorprendentes/impresionantes, pero potencialmente confusas como el Método Muenchian.

Cuestiones relacionadas