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:
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.002 segundos, usando
GeSHi 1.0.8.4
con:
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.
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.003 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:
Using perl Syntax Highlighting
print $refhash{tasks
}[0
]{task
}[1
]{id
}; # t1Coloreado 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).