• Publicidad

Expresión para extraer nodos XML

Así que programas sin strict y las expresiones regulares son otro modo de hablar. Aquí encontrarás respuestas de nivel avanzado, no recomendable para los débiles de corazón.

Expresión para extraer nodos XML

Notapor Kaik » 2007-01-24 14:33 @648

Hola a todos:
He comenzado a trabajar en este mundo de expresiones regulares.
A modo de ejercicio estoy haciendo un parser de XML. Sé que hay módulos como XML:DOM, etc, pero la idea es entender cómo se hace.
Llegue a lo siguiente:

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
sub getElementsByTagName($) {
    my $_nodeName=shift;
    my $_attributes = '[a-zA-Z0-9\/=_,."\s]';
    my $_innerNode='.*';
    my $_endNode="+((\/>)|(>$_innerNode<\/$_nodeName>))";
    my $_i=0;
    my @_aResul;
    while ($_xml =~s/<$_nodeName $_attributes$_endNode/NODE$_i/i) {
        push @_aResul,$&;
        $i++
    }
    return @_aResul;
}
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4


Ahora bien, si el nodo tiene nodos hijos no me los toma.
¿Qué cambios debería hacer en la expresión de $_innerNode? o si hay otra forma más óptima.

Desde ya muchas gracias
Kaik
Perlero nuevo
Perlero nuevo
 
Mensajes: 11
Registrado: 2007-01-24 14:21 @639
Ubicación: Buenos Aires

Publicidad

Notapor monoswim » 2007-01-24 16:20 @722

Deberías de evaluar si el valor del nodo no es otro nodo, en ese caso que se vuelva a llamar al función a si misma...Pasándole el parámetro que corresponda...

Era a eso que te referías ?

Saludos
MonoSwim
Perl Programming Language
Avatar de Usuario
monoswim
Perlero nuevo
Perlero nuevo
 
Mensajes: 452
Registrado: 2003-11-18 16:13 @717
Ubicación: Buenos Aires

Notapor Kaik » 2007-01-24 16:34 @732

Gracias, por contestar monoswin.
No básicamente es que cuando el Nodo XML tiene sub nodos, la expresión del ejemplo no la extrae, la omite, en el sub la variable $_xml tiene todo un archivo XML, la función recibiría como parámetro en nombre un tagName y debería devolver el array con todos los nodos.
Si el XML tiene la estructura
Sintáxis: [ Descargar ] [ Ocultar ]
Using xml Syntax Highlighting
<a/>
<a></a>
<a>
   <b/>
</a>
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4


mi función no recupera el último, debería perfeccionar el patrón contenido en $_innerNode.
Kaik
Perlero nuevo
Perlero nuevo
 
Mensajes: 11
Registrado: 2007-01-24 14:21 @639
Ubicación: Buenos Aires

Notapor explorer » 2007-01-24 20:50 @910

¿Te has dado cuenta de que las variables $_i e $i son distintas?
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Notapor Kaik » 2007-01-24 21:26 @934

Si, explorer gracias.
Kaik
Perlero nuevo
Perlero nuevo
 
Mensajes: 11
Registrado: 2007-01-24 14:21 @639
Ubicación: Buenos Aires

Notapor explorer » 2007-01-24 21:33 @939

Cambia $_innerNode por '.*?' .
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Notapor Kaik » 2007-01-24 22:16 @969

Lo he probado, pero no me va. De paso, ¿para qué es el "?" al final?
Pareciera que los saltos de líneas, tab o alguna otra cosa no lo permite.
El XML lo levanto de un archivo. ¿Sería conveniente que quite antes esos caracteres?
Kaik
Perlero nuevo
Perlero nuevo
 
Mensajes: 11
Registrado: 2007-01-24 14:21 @639
Ubicación: Buenos Aires

Notapor explorer » 2007-01-24 22:33 @981

La expresión regular '.*' es por defecto 'glotona' (se come todo lo que encuentra hasta el final). Con '?' le indicamos que se pare en la primera coincidencia que encuentre.

En tu caso, le indicamos que '.*' se pare en la primera ocasión en la que se encuentre dentro de un par de '> ... <', no en '> ... < ... >... <'.

En cuanto a los saltos de línea, sí, es recomendable quitarlos o usar la opción /m (línea múltiple) en la expresión regular.
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Notapor Kaik » 2007-01-24 22:54 @996

el XML tiene este formato:
Sintáxis: [ Descargar ] [ Ocultar ]
Using xml Syntax Highlighting
<tasks>
    <task id="t0"/>
    <task id="t1">
         <events>
        <event></event>
      </events>
        </task>
    <task id="t2" />
    <task id="t3" ></task>
  </tasks>
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4


El código quedó
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
sub getElementsByTagName($){
my $_nodeName=shift;
my $_attributes = '[a-zA-Z0-9\/=_,."\s]+';
my $_innerNode='.*?';
my $_endNode="((\/>)|(>$_innerNode<\/$_nodeName>))";
my $_i=0;
my @_aResul;
my ($_node,$_n0);
$_xml =~s/(\r)//g;

       while ($_xml =~s/<$_nodeName$_attributes$_endNode/NODE$_i/m){
                $_node=$&;
                push @_aResul,$_node;
                $_i++
        }
 return @_aResul;
};
 
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4

recupera todos menos
Sintáxis: [ Descargar ] [ Ocultar ]
Using xml Syntax Highlighting
    <task id="t1">
         <events>
                     <event></event>
                </events>
    </task>
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4


¿Cómo quito los tab solamente? ¿O qué me recomiendas?
Kaik
Perlero nuevo
Perlero nuevo
 
Mensajes: 11
Registrado: 2007-01-24 14:21 @639
Ubicación: Buenos Aires

Notapor explorer » 2007-01-25 01:58 @123

Puedes quitar los tabs con la expresión '\s+' que equivale a quitar todo lo que sea 'espacio en blanco', tabuladores incluidos.

Realmente, es peligroso quitar los tabuladores, porque pueden formar parte importante del contenido.

Esta es una posible solución:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#!/usr/bin/perl -l

open XML,"<kk.xml";
$_xml = join '', <XML>;
close XML;

sub getElementsByTagName($) {
    my $_nodeName=shift;
    my $_attributes = '\w+="[\w\s]+?"';
    my $_innerNode='.*?';
    my $_endNode="(( *\/>)|( *>$_innerNode<\/$_nodeName>))";
    my $_i=0;
    my @_aResul;
    my ($_node,$_n0);
    #$_xml =~s/\r//mg;
    #print $_xml;
    while ($_xml =~s/<$_nodeName( +$_attributes)*?$_endNode/NODE$_i/sm){
        push @_aResul,$&;
        $_i++
    }
    #print $_xml;
    return @_aResul;
}

@nodos = getElementsByTagName('task');

print join "\n============\n",@nodos;
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
con:
Sintáxis: [ Descargar ] [ Ocultar ]
Using xml Syntax Highlighting
<tasks>
    <task id="t0"/>
    <task id="t1">
         <events>
        <event></event>
      </events>
        </task>
    <task id="t2" />
    <task id="t3" ></task>
</tasks>
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4
sale:
Código: Seleccionar todo
<task id="t0"/>
============
<task id="t1">
         <events>
        <event></event>
      </events>
        </task>
============
<task id="t2" />
============
<task id="t3" ></task>

No deja de ser una chapucilla, pero al menos, funciona.

Los cambios hechos son:
* $_attributes cambiado a una expresión regular más fuerte. No valía con poner el listado de todos los caracteres admisibles en un atributo, más el espacio en blanco, porque entonces confundía el tag <tasks> con el <task> (se pensaba que la 's' final era un atributo). Ahora, es un conjunto de letras (\w+) seguido de un '=' y seguido de un conjunto de letras y espacios entrecomillados. Seguro que habría que repasarlo más a fondo... Observa la '?' para que se pare en la primera doble comilla que encuentre.

* En $_endNode he agregado un par de ' *' para que tenga en cuenta los casos de que el '/>' o '>' de final de tag tengan espacios delante.

* He comentado la línea que quitaba los retornos de carro. No es necesario por lo que viene después.

* La expresión regular del while está un poco cambiada. Primero, tiene puesta la opción /s que indica que en $_xml debe considerar con el comodín '.' también los avances de línea y retornos de carro. Dentro del patrón, los atributos pueden aparecer cero o más veces ('*'), precedidos por uno o más espacios (' +') y siempre parándonos antes ('?') del primer $_endNode que encontremos.

Desde luego, hacer un parser es complicado, y con expresiones regulares, también, pero un poquito menos.

Lo ideal es basarse en la idea de un parser: construir las expresiones regulares en función de la gramática. La gramática de un xml es sencilla. El ejemplo que has puesto tiene 4 combinaciones de tags, todos perfectamente válidos. Lo principal es encontrar la expresión regular que puede identificar a cada uno de los tokens de la gramática que podemos encontrar en el documento.

Un ejemplo. Con el mismo xml de muestra, el siguiente programa analiza y devuelve en una estructura hash el modelo objeto del documento. Lo interesante, aparte de ser una llamada recursiva, es que las expresiones regulares se construyen de forma parecida a lo que tenías: expresiones basadas unas en otras.

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#!/usr/bin/perl -l

use Data::Dumper;

open XML,"<kk.xml";
$_xml = join '', <XML>;
close XML;


### Devuelve el modelo objeto del documento, en un hash
my $id  = qr/\w+/;                                          # Un identificador
my $val = qr/[\w\s]+/;                                      # Un valor de atributo
my $att = qr/ +$id="$val"/;                                 # Un atributo es un identificador = "valor"
my $tag = qr/<($id)((?:$att)*) *(?:>(.*?)<\/ *\1 *>|\/>)/s; # Un tag es un id. seguido de 0 o más atributos
                                                            # sin contenido (/>) o con contenido y tag de cierre
sub getDOM {
    my $xml = shift;
    my $ref = shift;
    #print "Procesando\n$xml";
    while ( $xml =~ /$tag/sg ) {
        my ($mitag,$miattr,$micontenido) = ($1,$2,$3);
        #print "$mitag:$miattr:$micontenido";
        push @{$ref->{$mitag}}, {};                     # Creamos un nodo hash por cada nuevo $mitag
                                                        # y nodo array (push) por cada tag del mismo nivel,
                                                        # almacenando un hash anónimo.
                                                        # (NO usar esta línea sin cinturón de seguridad :-)
                                                        # (por algo estamos en el foro de Experto ;-)

        if ( defined $miattr ) {                        # El atributo lo guardaremos como clave de un hash
            foreach ( split " ", $miattr ) {                # Para todos los atributos
                my ($attr,$valor) = split "=";
                $attr  =~ s/ //g;                           # Limpiamos las dos partes de caracteres indeseables
                $valor =~ s/"//g;
                $ref->{$mitag}[-1]{$attr} = $valor;         # El -1 se refiere al último nodo array creado por push
            }
        }

        if ( defined $micontenido ) {                   # Llamada recursiva si $micontenido tiene algún posible tag
            getDOM($micontenido,$ref->{$mitag}[-1])
                if $micontenido =~ /[<>]/;
        }
    }
}

getDOM($_xml,\%refhash);

print Dumper \%refhash;
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4
Salida:
Código: Seleccionar todo
$VAR1 = {
          'tasks' => [
                       {
                         'task' => [
                                     {
                                       'name' => 'Inicio',
                                       'type' => 'start',
                                       'id' => 't0',
                                       'typetask' => '99'
                                     },
                                     {
                                       'events' => [
                                                     {
                                                       'event' => [
                                                                    {}
                                                                  ]
                                                     }
                                                   ],
                                       'name' => 'Inicio',
                                       'type' => 'stop',
                                       'id' => 't1',
                                       'typetask' => '91'
                                     },
                                     {
                                       'id' => 't2'
                                     },
                                     {
                                       'id' => 't3'
                                     }
                                   ]
                       }
                     ]
        };
Y teniendo el modelo objeto del documento convertido en una estructura de datos conocida, es mucho más fácil de manejar que no el documento de texto que al final es el fichero xml...

Ejemplo:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
print $refhash{tasks}[0]{task}[1]{id}; # t1
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


Seguro que se te ocurren más formas de hacer el parseo... (un árbol sintáctico, listas enlazadas, o simplemente arrays de tags).
Última edición por explorer el 2007-01-25 16:25 @726, editado 1 vez en total
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Siguiente

Volver a Avanzado

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 1 invitado

cron