2011-09-14 15 views
7

Tengo el siguiente código XMLgrupo de múltiples atributos de XML con XSLT

<smses> 
    <sms address="87654321" type="1" body="Some text" readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" /> 
    <sms address="87654321" type="2" body="Some text" readable_date="3/09/2011 2:36:41 PM" contact_name="Person1" /> 
    <sms address="87654321" type="1" body="Some text" readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" /> 
    <sms address="123" type="2" body="Some text" readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" /> 
    <sms address="123" type="1" body="Some text" readable_date="3/09/2011 10:57:52 AM" contact_name="Person2" /> 
    <sms address="123" type="2" body="Some text" readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" /> 
    <sms address="12345678" type="1" body="Some text" readable_date="3/09/2011 11:21:16 AM" contact_name="Person3" /> 
    <sms address="12345678" type="2" body="Some text" readable_date="3/09/2011 11:37:21 AM" contact_name="Person3" /> 

    <sms address="12345" type="2" body="Some text" readable_date="28/01/2011 7:24:50 PM" contact_name="(Unknown)" /> 
    <sms address="233" type="1" body="Some text" readable_date="30/12/2010 1:13:41 PM" contact_name="(Unknown)" /> 
</smses> 

Estoy tratando de obtener una ouput como esto (por ejemplo, XML)

<sms contact_name="person1"> 
    <message type="1">{@body}</message> 
    <message type="2">{@body}</message> 
    <message type="1">{@body}</message> 
</sms> 
<sms contact_name="person2"> 
    <message type="2">{@body}</message> 
    <message type="1">{@body}</message> 
</sms> 
<sms contact_name="person3"> 
    <message type="2">{@body}</message> 
    <message type="1">{@body}</message> 
</sms> 
<sms contact_name="(Unknown)"> 
    <message type="2">{@body}</message> 
    <message type="1">{@body}</message> 
</sms> 
<sms contact_name="(Unknown)"> 
    <message type="2">{@body}</message> 
</sms> 

por ejemplo, html

<div> 
    <h1>Person: @contact_name (@address)</h1> 
    <p>message @type: @body</p> 
</div> 

he logrado hacer esto con el siguiente código XSLT (disculpen el código de abajo no refleja el html en su totalidad, la salida es el resultado deseado!)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="xml" indent="yes" /> 
    <xsl:key name="txt" match="sms" use="@contact_name" /> 
    <xsl:template match="smses"> 
     <xsl:apply-templates select="sms[generate-id(.)=generate-id(key('txt', @contact_name)[1])]"> 
      <xsl:sort select="@address" order="ascending" /> 
     </xsl:apply-templates> 
    </xsl:template> 
    <xsl:template match="sms"> 
     <h4><xsl:value-of select="@contact_name" /></h4> 
      <xsl:for-each select="key('txt', @contact_name)"> 
        <br /> 
        <xsl:value-of select="@body" /> 
      </xsl:for-each> 
    </xsl:template> 

</xsl:stylesheet> 

El problema que tener es, o más bien la pregunta que estoy haciendo. Tengo un elemento sms con un atributo @contact_name que es "(desconocido)" pero el @address es único entre ambos elementos, es decir, no se deben agrupar porque el mensaje sms provino de un número/persona diferente (aunque el nombre del contacto) es lo mismo, es irrelevante). Debería intentar reordenar/cambiar los datos XML o si hay alguna forma de que XSLT reconozca el grupo para unknown debe verificar si el @address es diferente si el @contact_name es el mismo.

Editar:

no dijera (o más bien olvidado) que, si bien hay algunos mensajes SMS con la misma y única @contact_name@address también hay casos en los que algunos de los campos tienen @address ligera discrepancia en los que no lo hacen tener el código de país en frente del número, por ejemplo

<sms contact_name="jared" address="12345" /> 
<sms contact_name="jared" address="+64112345" /> 

Pero que están destinados a ser agrupados debido a que son de la misma persona/número.

Editar:

En mi situación sólo habría discrepancias de tener 3 caracteres (por ejemplo 64) código de país y código de red 2 dígitos (por ejemplo 21). Básicamente, el resultado debería ser, si @contact_name = mismo y @address es completamente diferente es decir

<sms contact_name="jared" address="12345" /> 
<sms contact_name="jared" address="5433467" /> 

entonces deberían ser elementos separados, ya que son de diferentes personas/número (s).

si @contact_name = mismo y @address es diferente únicamente por los códigos de país y de red es decir

<sms contact_name="jared" address="02112345" /> 
<sms contact_name="jared" address="+642112345" /> 

entonces deben ser agrupados, ya que son de la misma persona/número

Editar:

códigos de país: +64 (3 caracteres)

códigos de red: 021 (3 caracteres, usua lly el último carácter cambia según la red)

Los números (@address) se guardan por <sms> como + 64-21-12345 (sin guiones) o 021-12345 (excluyendo el guión).

+0

Buena pregunta , +1. Ahora podrá aprender y aplicar la agrupación Muenchian utilizando claves compuestas. –

+0

@_Jared: Necesita explicar (mejor editando la pregunta) las reglas para prefijar con el código de país: Isit 2 dígitos solo o tres dígitos, o número de dígitos variable? En caso de que sea este último, entonces la solución debería haber sido proporcionada con una lista de todos los códigos de país posibles. –

+0

@_Dimitre - Disculpas, espero haberlo dejado más claro ahora. Estaba tan cerca de hacer que esto funcionara por mi cuenta hasta que llegue a esta barrera. ¡Agradezco mucho tu ayuda! – Jared

Respuesta

9

Esta transformación utiliza agrupación Muenchian con claves compuestas:

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

<xsl:key name="kContactByNameAddress" match="sms" 
      use="concat(@contact_name,'+',@address)"/> 

<xsl:template match= 
    "sms[generate-id() 
     = 
     generate-id(key('kContactByNameAddress', 
         concat(@contact_name,'+',@address) 
         ) 
         [1] 
        ) 
     ] 
    "> 
    <sms contact_name="{@contact_name}"> 
     <xsl:apply-templates mode="inGroup" 
     select="key('kContactByNameAddress', 
       concat(@contact_name,'+',@address) 
       )"/> 
    </sms> 
</xsl:template> 

<xsl:template match="sms" mode="inGroup"> 
     <message type="{@type}"> 
     <xsl:value-of select="@body"/> 
     </message> 
</xsl:template> 
<xsl:template match="sms"/> 
</xsl:stylesheet> 

cuando se aplica al documento XML proporcionado:

<smses> 
    <sms address="87654321" type="1" body="Some text" 
    readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" /> 
    <sms address="87654321" type="2" body="Some text" 
    readable_date="3/09/2011 2:36:41 PM" contact_name="Person1" /> 
    <sms address="87654321" type="1" body="Some text" 
    readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" /> 
    <sms address="123" type="2" body="Some text" 
    readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" /> 
    <sms address="123" type="1" body="Some text" 
    readable_date="3/09/2011 10:57:52 AM" contact_name="Person2" /> 
    <sms address="123" type="2" body="Some text" 
    readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" /> 
    <sms address="12345678" type="1" body="Some text" 
    readable_date="3/09/2011 11:21:16 AM" contact_name="Person3" /> 
    <sms address="12345678" type="2" body="Some text" 
    readable_date="3/09/2011 11:37:21 AM" contact_name="Person3" /> 
    <sms address="12345" type="2" body="Some text" 
    readable_date="28/01/2011 7:24:50 PM" contact_name="(Unknown)" /> 
    <sms address="233" type="1" body="Some text" 
    readable_date="30/12/2010 1:13:41 PM" contact_name="(Unknown)" /> 
</smses> 

el resultado deseado, correcta se produce:

<sms contact_name="Person1"> 
    <message type="1">Some text</message> 
    <message type="2">Some text</message> 
    <message type="1">Some text</message> 
</sms> 
<sms contact_name="Person2"> 
    <message type="2">Some text</message> 
    <message type="1">Some text</message> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="Person3"> 
    <message type="1">Some text</message> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="(Unknown)"> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="(Unknown)"> 
    <message type="1">Some text</message> 
</sms> 

Actualización: El OP ha editado su pregunta y ha publicado nuevos requisitos para que el atributo address pueda o no comience con un código de país. Dos direcciones, una con código contry y la otra sin código de país son "iguales" si la subcadena después del código de país es igual a la otra dirección. En este caso, los dos elementos se deben agrupar.

Aquí está la solución (sería trivial para escribir en XSLT 2.0, pero en XSLT 1.0 de hacerlo en una sola pasada es bastante complicado. Solución Amultipass es más fácil, pero sería generalmente requieren la extensión xxx:node-set() función y sería por lo tanto pierden la portabilidad):

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

<xsl:key name="kContactByNameAddress" match="sms" 
    use="concat(@contact_name,'+', 
       concat(substring(@address, 
           4 div starts-with(@address,'+')), 
        substring(@address, 
           1 div not(starts-with(@address,'+')) 
          ) 
        ) 
      )"/> 

<xsl:template match= 
    "sms[generate-id() 
     = 
     generate-id(key('kContactByNameAddress', 
         concat(@contact_name,'+', 
           concat(substring(@address, 
               4 div starts-with(@address,'+')), 
             substring(@address, 
               1 div not(starts-with(@address,'+')) 
               ) 
             ) 
           ) 
         ) 
         [1] 
        ) 
     ] 
    "> 
    <sms contact_name="{@contact_name}"> 
     <xsl:apply-templates mode="inGroup" 
     select="key('kContactByNameAddress', 
       concat(@contact_name,'+', 
         concat(substring(@address, 
             4 div starts-with(@address,'+')), 
           substring(@address, 
             1 div not(starts-with(@address,'+')) 
             ) 
           ) 
         ) 
       ) 
     "/> 
    </sms> 
</xsl:template> 

<xsl:template match="sms" mode="inGroup"> 
     <message type="{@type}"> 
     <xsl:value-of select="@body"/> 
     </message> 
</xsl:template> 
<xsl:template match="sms"/> 
</xsl:stylesheet> 

Cuando se aplica esta transformación en el siguiente documento XML (la anterior + añadió tres sms elementos con contact_name="Jared", dos de los cuales tienen direcciones "idénticas", según las nuevas reglas publicadas):

<smses> 
    <sms address="87654321" type="1" body="Some text" 
     readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" /> 
    <sms address="87654321" type="2" body="Some text" 
     readable_date="3/09/2011 2:36:41 PM" contact_name="Person1" /> 
    <sms address="87654321" type="1" body="Some text" 
     readable_date="3/09/2011 2:16:52 PM" contact_name="Person1" /> 
    <sms address="123" type="2" body="Some text" 
     readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" /> 
    <sms address="123" type="1" body="Some text" 
     readable_date="3/09/2011 10:57:52 AM" contact_name="Person2" /> 
    <sms address="123" type="2" body="Some text" 
     readable_date="3/09/2011 10:56:24 AM" contact_name="Person2" /> 
    <sms address="12345678" type="1" body="Some text" 
     readable_date="3/09/2011 11:21:16 AM" contact_name="Person3" /> 
    <sms contact_name="jared" address="12345" type="2" body="Some text"/> 
    <sms contact_name="jared" address="56789" type="1" body="Some text"/> 
    <sms contact_name="jared" address="+6412345" type="2" body="Some text"/> 
    <sms address="12345678" type="2" body="Some text" 
     readable_date="3/09/2011 11:37:21 AM" contact_name="Person3" /> 
    <sms address="12345" type="2" body="Some text" 
     readable_date="28/01/2011 7:24:50 PM" contact_name="(Unknown)" /> 
    <sms address="233" type="1" body="Some text" 
     readable_date="30/12/2010 1:13:41 PM" contact_name="(Unknown)" /> 
</smses> 

el resultado deseado, correcta se produce:

<sms contact_name="Person1"> 
    <message type="1">Some text</message> 
    <message type="2">Some text</message> 
    <message type="1">Some text</message> 
</sms> 
<sms contact_name="Person2"> 
    <message type="2">Some text</message> 
    <message type="1">Some text</message> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="Person3"> 
    <message type="1">Some text</message> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="jared"> 
    <message type="2">Some text</message> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="jared"> 
    <message type="1">Some text</message> 
</sms> 
<sms contact_name="(Unknown)"> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="(Unknown)"> 
    <message type="1">Some text</message> 
</sms> 

Explicación detallada:

La principal dificultad en este problema surge del hecho de que no hay "si ... luego ... else "operador en XPath 1.0, sin embargo, debemos especificar una única expresión XPath en el atributo use de la instrucción xsl:key, que selecciona el atributo address (cuando no comienza con "+") o su subcadena después del código de país (si su valor de cadena comienza con "+").

Aquí estoy usando la aplicación de este pobre hombre de

if($condition) 
    then $string1 
    else $string2 

La siguiente expresión XPath, cuando se evalúa es equivalente a la anterior:

concat(substring($string1, 1 div $condition), 
     substring($string2, 1 div not($condition)) 
    ) 

Esta equivalencia se deduce del hecho que 1 div true() es lo mismo que 1 div 1 y esto es 1, mientras que 1 div false() es el igual que 1 div 0 y ese es el número (positivo) Infinity.

Además, para cualquier cadena $s, el valor de substring($s, Infinity) es solo la cadena vacía. Y, por supuesto, para cualquier cadena $s el valor de substring($s, 1) es solo la cadena $s.

II. solución de 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:strip-space elements="*"/> 

<xsl:template match="/*"> 
    <xsl:for-each-group select="sms" group-by= 
    "concat(@contact_name,'+', 
      if(starts-with(@address,'+')) 
      then substring(@address, 4) 
      else @address 
      )"> 
    <sms contact_name="{@contact_name}"> 
     <xsl:apply-templates select="current-group()"/> 
    </sms> 

    </xsl:for-each-group> 
</xsl:template> 

<xsl:template match="sms"> 
     <message type="{@type}"> 
     <xsl:value-of select="@body"/> 
     </message> 
</xsl:template> 
</xsl:stylesheet> 

cuando se aplica esta (! mucho más simple) transformación XSLT 2.0 en el mismo documento XML (arriba), la misma salida correcta se produce:

<sms contact_name="Person1"> 
    <message type="1">Some text</message> 
    <message type="2">Some text</message> 
    <message type="1">Some text</message> 
</sms> 
<sms contact_name="Person2"> 
    <message type="2">Some text</message> 
    <message type="1">Some text</message> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="Person3"> 
    <message type="1">Some text</message> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="jared"> 
    <message type="2">Some text</message> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="jared"> 
    <message type="1">Some text</message> 
</sms> 
<sms contact_name="(Unknown)"> 
    <message type="2">Some text</message> 
</sms> 
<sms contact_name="(Unknown)"> 
    <message type="1">Some text</message> 
</sms> 
+0

¡Estás muy bien informado! Muchas gracias por responder. ¡Ay, creo que he metido la pata en mis ejemplos, la mayoría de las disculpas por eso! He editado mi pregunta para actualizar esto. – Jared

+0

Amigo que fue increíble, y una explicación de lo que estaba haciendo era épica. Muchas gracias, creo que puedo tomarlo desde aquí :) – Jared

+0

@Jared: De nada. La solución puede ser significativamente más simple si hay información adicional en el problema, por ejemplo, si se sabe que todos los números de teléfono (sin el código de país) tienen la misma longitud fija. Si esto es así en su caso, por favor, confirme y estaré encantado de publicar la solución más simple. –

Cuestiones relacionadas