2010-11-17 18 views
8

no estoy seguro si esto es posible sin tener que pasar por varios pasos, pero voy a preguntar de todos modos (mi XSL es un poco oxidado)XSLT para saltar nodos ya "visitados"

tengo un documento XML, el cual contiene nodos de la siguiente manera: (El archivo real contiene una gran cantidad de variables de estructura, que interdependencias, ninguno de los cuales son circulares)

<structures> 
<structure id="STRUCT_A"> 
    <field idref="STRUCT_B" name="b"/> 
    <field idref="STRUCT_C" name="c"/> 
    <field idref="FIELD_D" name="d"/> 
</structure> 

<structure id="STRUCT_B"> 
    <field idref="STRUCT_C" name="c"/> 
    <field idref="FIELD_E" name="e"/> 
</structure> 

<structure id="STRUCT_C"> 
    <field idref="FIELD_E" name="e"/> 
    <field idref="FIELD_F" name="f"/> 
    <field idref="FIELD_G" name="g"/> 
</structure> 
</structures> 

lo que quiero hacer es generar un poco de texto (en este caso C++ struct s), y el requisito obvio es la orden de f los struct s, por lo que mi salida ideal sería

struct STRUCT_C 
{ 
    FIELD_E e; 
    FIELD_F f; 
    FIELD_G g; 
}; 

struct STRUCT_B 
{ 
    STRUCT_C c; 
    FIELD_E e; 
}; 

struct STRUCT_A 
{ 
    STRUCT_B b; 
    STRUCT_C c; 
    FIELD_D d; 
}; 

Sé que podría utilizar hacia adelante las declaraciones y que significaría que el orden no es importante, sin embargo, el problema es que no habrá "procesamiento" código en línea en las estructuras, y requerirían la definición real para estar presente.

Hasta ahora puedo detectar a ver si un structure tiene ninguna dependencia, con el siguiente segmento de XSL:

<xsl:for-each select="descendant::*/@idref"> 
    <xsl:variable name="name" select="."/> 
    <xsl:apply-templates select="//structure[@id = $name]" mode="struct.dep"/> 
</xsl:for-each> 

(esto ocurre dentro de un <xsl:template match="structure">)

Ahora, en teoría, podría entonces siga esta "cadena" de dependencia y genere el struct s para cada entrada primero, luego la que estoy actualmente, sin embargo, como se puede imaginar, esto genera muchas copias de la misma estructura, que es un dolor ...

¿Hay alguna forma de evitar las copias? Básicamente, una vez que se ha visitado una estructura, y si volvemos a visitarla, no nos molestamos en mostrar el código para ello ... No necesito el xslt completo para hacerlo (¡a menos que sea trivial!), Pero simplemente cualquier idea sobre enfoques ...

Si no lo hay, yo podría, en teoría, envolver el struct con un guardia #ifdef/#define/#endif para que el compilador sólo utiliza la primera definición, sin embargo, esto es muy desagradable! :(

(NOTES: XSLT 1.0, xsltproc en Linux: Usando libxml 20623, 10115 y libxslt libexslt 812)

+0

excelente pregunta, 1. Vea mi respuesta para una solución completa y breve. :) –

Respuesta

7

Esta transformación:

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

<xsl:variable name="vLeafs" select="/*/structure[not(field/@idref = /*/structure/@id)]"/> 

<xsl:template match="/*"> 
    <xsl:apply-templates select="$vLeafs[1]"> 
    <xsl:with-param name="pVisited" select="'|'"/> 
    </xsl:apply-templates> 

</xsl:template> 

<xsl:template match="structure"> 
    <xsl:param name="pVisited"/> 

struct <xsl:value-of select="@id"/> 
{<xsl:text/> 
    <xsl:apply-templates/> 
}; 
    <xsl:variable name="vnewVisited" 
     select="concat($pVisited, @id, '|')"/> 
    <xsl:apply-templates select= 
    "../structure[not(contains($vnewVisited, concat('|', @id, '|'))) 
       and 
       not(field/@idref 
          [not(contains($vnewVisited, concat('|', ., '|'))) 
          and 
          . = ../../../structure/@id 
          ] 
        ) 
       ] [1] 
    "> 
    <xsl:with-param name="pVisited" select="$vnewVisited"/> 
    </xsl:apply-templates> 
</xsl:template> 

<xsl:template match="field"> 
    <xsl:value-of select="concat('&#xA; ', @idref, ' ', @name, ';')"/> 
</xsl:template> 
</xsl:stylesheet> 

cuando se aplica en el documento XML proporcionado:

<structures> 
<structure id="STRUCT_A"> 
    <field idref="STRUCT_B" name="b"/> 
    <field idref="STRUCT_C" name="c"/> 
    <field idref="FIELD_D" name="d"/> 
</structure> 

<structure id="STRUCT_B"> 
    <field idref="STRUCT_C" name="c"/> 
    <field idref="FIELD_E" name="e"/> 
</structure> 

<structure id="STRUCT_C"> 
    <field idref="FIELD_E" name="e"/> 
    <field idref="FIELD_F" name="f"/> 
    <field idref="FIELD_G" name="g"/> 
</structure> 
</structures> 

produce los, resultado deseado correcta:

struct STRUCT_C 
{ 
    FIELD_E e; 
    FIELD_F f; 
    FIELD_G g; 
}; 


struct STRUCT_B 
{ 
    STRUCT_C c; 
    FIELD_E e; 
}; 


struct STRUCT_A 
{ 
    STRUCT_B b; 
    STRUCT_C c; 
    FIELD_D d; 
}; 

Explicación: structure elementos se procesan estrictamente uno por uno. En cualquier momento procesamos el primer elemento structure cuyo id aún no se ha registrado en el parámetro pVisited y que no tiene ningún valor field/@idref que no esté ya en el parámetro pVisited y se refiere a un elemento structure existente.

+0

+1 buen enfoque: procese desde las hojas hacia arriba, para que no tenga que modificar y pasar tanta información de estado. Todavía estoy digiriendo tu respuesta ... – LarsH

+0

+1, funciona para mí, ahora necesito asignarlo al documento xml real que tengo, ¡eso será divertido! Gracias v. ¡Muchachos! – Nim

+0

@Dimitre: +1 ¡Muy buena respuesta! –

2

Oh, esto es más complicado de lo que parecía en un principio para el 1 buena pregunta

..

Creo que la mejor manera de lograr esto en XSLT 1.0 sería pasar un parámetro de acumulación cada vez que aplique plantillas a una estructura. El parámetro (llámalo "$ visited-structures") es una lista de nombres delimitada por espacios de estructuras que ya ha procesado.

Actualización: finalmente conseguí esto. :-)

En la plantilla para procesar una estructura, compruebe si otras estructuras de las que depende no están ya enumeradas en $ visited-structures. De lo contrario, genere el código para esta estructura, y recurse en la plantilla seleccionando la siguiente estructura no visitada, añadiendo el nombre de la estructura actual al parámetro $ visited-structures. De lo contrario, no genere código para la estructura pero recurse en la plantilla seleccionando la primera estructura de dependencia, pasando el parámetro $ visited-structures sin modificar.

Aquí está el código ...

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0"> 
    <xsl:key name="structuresById" match="/*/structure" use="@id" /> 

    <xsl:template match="structures"> 
     <xsl:apply-templates select="structure[1]" > 
     <!-- a space-delimited list of id's of structures already processed, with space 
      at beginning and end. Could contain duplicates. --> 
     <xsl:with-param name="visited-structures" select="' '"/> 
     </xsl:apply-templates> 
    </xsl:template> 

    <xsl:template match="structure"> 
     <xsl:param name="visited-structures" select="' '" /> 
     <xsl:variable name="dependencies" select="key('structuresById', field/@idref) 
        [not(contains($visited-structures, @id))]"/> 
     <xsl:choose> 
     <xsl:when test="$dependencies"> 
      <xsl:apply-templates select="$dependencies[1]"> 
       <xsl:with-param name="visited-structures" select="$visited-structures"/> 
      </xsl:apply-templates>    
     </xsl:when> 
     <xsl:otherwise> 
      <!-- Now generate code for this structure ... ... --> 
struct <xsl:value-of select="@id"/> 
{ 
<xsl:apply-templates select="field"/>}; 
      <xsl:variable name="new-visited" select="concat(' ', @id, $visited-structures)"/> 
      <xsl:apply-templates select="/*/structure[not(contains($new-visited, @id))][1]" > 
       <xsl:with-param name="visited-structures" select="$new-visited"/> 
      </xsl:apply-templates> 
     </xsl:otherwise> 
     </xsl:choose>  
    </xsl:template> 

    <xsl:template match="field"> 
     <xsl:value-of select="concat(' ', @idref, ' ', @name, ';&#xa;')"/>  
    </xsl:template> 

</xsl:stylesheet> 

Y la salida:

<?xml version="1.0" encoding="utf-8"?> 

struct STRUCT_C 
{ 
    FIELD_E e; 
    FIELD_F f; 
    FIELD_G g; 
}; 


struct STRUCT_B 
{ 
    STRUCT_C c; 
    FIELD_E e; 
}; 


struct STRUCT_A 
{ 
    STRUCT_B b; 
    STRUCT_C c; 
    FIELD_D d; 
}; 
+0

P.S. También tiene que pasar un parámetro adicional a la plantilla para '', diciéndole si recurse o no en el siguiente hermano. Esto solo sería cierto en el nivel superior, es decir, cuando se aplica en la plantilla para procesar ''. – LarsH

+0

variable que se pasa suena como una idea, sin embargo, ¿no habrá un problema de alcance? es decir, cada nodo de 'estructura' de raíz se llama con un espacio en blanco' $ visited-structures'? Por lo tanto, todavía habrá múltiples copias de los nodos de estructura? – Nim

+0

¡Guau, parece que incluso tienes que pasar por una "continuación"! O al menos una lista de estructuras aún por procesar. – LarsH

3

Sólo por diversión, otro enfoque (nivel por nivel) y las teclas de Ussing:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="text"/> 
    <xsl:key name="kStructById" match="structure" use="@id"/> 
    <xsl:key name="kStructByIdref" match="structure" use="field/@idref"/> 
    <xsl:template match="/"> 
     <xsl:param name="pParents" select="/.."/> 
     <xsl:param name="pChilds" 
        select="structures/structure[not(key('kStructById', 
                 field/@idref))]"/> 
     <xsl:variable name="vParents" select="$pParents|$pChilds"/> 
     <xsl:variable name="vChilds" 
         select="key('kStructByIdref',$pChilds/@id) 
           [count(key('kStructById', 
              field/@idref) | 
             $vParents) = 
            count($vParents)]"/> 
     <xsl:apply-templates select="$pChilds"/> 
     <xsl:apply-templates select="current()[$vChilds]"> 
      <xsl:with-param name="pParents" select="$vParents"/> 
      <xsl:with-param name="pChilds" select="$vChilds"/> 
     </xsl:apply-templates> 
    </xsl:template> 
    <xsl:template match="structure"> 
     <xsl:value-of select="concat('struct ',@id,'&#xA;{&#xA;')"/> 
     <xsl:apply-templates/> 
     <xsl:text>};&#xA;</xsl:text> 
    </xsl:template> 
    <xsl:template match="field"> 
     <xsl:value-of select="concat('&#x9;',@idref,' ',@name,';&#xA;')"/> 
    </xsl:template> 
</xsl:stylesheet> 

Salida:

struct STRUCT_C 
{ 
    FIELD_E e; 
    FIELD_F f; 
    FIELD_G g; 
}; 
struct STRUCT_B 
{ 
    STRUCT_C c; 
    FIELD_E e; 
}; 
struct STRUCT_A 
{ 
    STRUCT_B b; 
    STRUCT_C c; 
    FIELD_D d; 
}; 
+0

Agradable. No estoy seguro de haber visto alguna vez una plantilla "/" para aplicar plantillas a "/" de forma recursiva. :-) – LarsH

+0

@LarsH: ¡Ja! Eso es lo mismo que usar referencias de plantilla. Pero en este caso es solo una forma esotérica para evitar un 'xsl: if' ... –

Cuestiones relacionadas