• Publicidad

Recorrido de cadenas y separación de campos

¿Apenas comienzas con Perl? En este foro podrás encontrar y hacer preguntas básicas de Perl con respuestas aptas a tu nivel.

Recorrido de cadenas y separación de campos

Notapor Josmanue » 2006-07-20 04:18 @221

Hola a todos!

A ver, os cuento el problema a ver si alguien me puede echar una mano de acuerdo?

Mi programa recibe una cadena de caracteres como esta:

Código: Seleccionar todo
#xUIDxxxxxFECxxxxxxTIExxxxxxVALxLATxxxx,xxxxSLAxLONxxxx,xxxxSLOxALTxxxxxVELxxx...........


es mas larga pero todo así. Esta cadena la guardo por ejemplo en "$cadena".
Pues bien lo que tengo que hacer es recorrer esta cadena e ir separando los campos para mandarlos a tablas de una base de datos MySQL, es decir de "#x" ese caracter "x" iria al campo "fuente" de la BD, de "UIDxxxxx" los caracteres "xxxxx" irian al campo "uid" de la BD, y así sucesivamente. Los campos iran siempre en ese orden pero podría faltar alguno, por ejemplo, "VALx" podria no aparecer en la cadena.

¿Alguna idea de cómo puedo hacerlo?
Yo estaba pensando en la funcion "substr($cadena,pos,x);" que extrae de $cadena x elementos a partir de la posición pos, pero claro, como hay campos que pueden no aparecer, no se muy bien como calcular la posicion. Aaaaaa! estoy un poco liado.

Por favor ¿alguna idea para hacerlo? con esta funcion o de cualquier otra forma.
Y por otra parte ¿que es mejor sacar un campo y mandarlo a la Base de Datos y después otro.... o mejor sacar todos los campos, guardarlos todos en variables y despues mandarlos todos a la Base de datos???

Gracias por todo de antemano.
Josmanue
Perlero nuevo
Perlero nuevo
 
Mensajes: 76
Registrado: 2006-06-09 04:33 @231

Publicidad

Notapor Josmanue » 2006-07-20 07:12 @341

Hola!

Las comas esas las recibe tal cual esta escrito y las manda todas a un campo, por ejemplo, LATxxxx,xxxx indica la Latitud en una posicion, el formato sería GGMM,mmmm donde GG son los grados, MM los minutos y mmmm las décimas de minutos, y todo ello iría al campo llamado latitud de la base de datos, pero todo junto con la coma y todo.

Espero haber aclarado este punto y muchas gracias por ayudarme!!
Josmanue
Perlero nuevo
Perlero nuevo
 
Mensajes: 76
Registrado: 2006-06-09 04:33 @231

Notapor explorer » 2006-07-20 12:57 @581

Supongamos que generamos una trama de prueba con el siguiente programa:
Código: Seleccionar todo
#!/usr/bin/perl
# Generador de trama aleatoria

my @campos = qw(
    UIDxxxxx        FECxxxxxx   TIExxxxxx   VALx
    LATxxxx,xxxx    SLAx        LONxxxx,xxxx    SLOx
    ALTxxxxx        VELxxx
);

foreach ( 1 .. 5 ) {
    # Identificador de trama
    print "#$_";
   
    # Genera una salida aleatoria de algunos campos
    $cuantos = int(rand @campos)+3;
    for ( $i=0; $i < $cuantos; $i++ ) {
        print $campos[rand @campos];
    }
}
Tenemos una trama como esta (formateada para mostrarla aquí):
Código: Seleccionar todo
#1TIExxxxxxVALxLATxxxx,xxxx#2SLOxUIDxxxxxSLOxFECxxxxxxVELxxxFECxxxxxxSLAxUIDxxxxxUIDxxxxxSLOx
#3SLOxSLOxFECxxxxxxTIExxxxxxFECxxxxxxVELxxxVELxxxFECxxxxxxLATxxxx,xxxxLONxxxx,xxxx
#4ALTxxxxxSLOxUIDxxxxxUIDxxxxxFECxxxxxxSLAxLATxxxx,xxxxTIExxxxxxALTxxxxxLONxxxx,xxxxLONxxxx,xxxxLATxxxx,xxxx
#5VELxxxLATxxxx,xxxxLONxxxx,xxxxALTxxxxxFECxxxxxxUIDxxxxxVALxLATxxxx,xxxxTIExxxxxxSLAxALTxxxxx
Entonces con un programa así se puede sacar la información:
Código: Seleccionar todo
#!/usr/bin/perl -l
{
    # Definimos el separador de registros
    # sólo en este ámbito, mientras leemos las tramas
    local $/ = '#';
    @trama = <>;
}
chomp @trama;           # Nos aseguramos que no terminan en fin de línea
print scalar @trama;    # Número de tramas leidas

# Campos a buscar
@campos = qw( UID FEC TIE VAL LAT SLA LON SLO ALT VEL );
$campos = join('|', @campos);

foreach $i ( 1 .. $#trama ) {

    $mi_trama = $trama[$i];
    print $mi_trama;

    # Sacamos el valor del #x
    ($id) = $mi_trama =~ /^([^A-Z]+)/;
    print "$id:";

    # Recorremos todos los campos para identificarles
    while ( $mi_trama =~ /($campos)([^A-Z#]+)/g ) {
        print "$1\t$2";
    }
}

Lo que hacemos es usar el '#' como separador de registros (se supone que en ningún 'x' hay un '#').
Leemos toda la trama y la dividimos en registros según ese separador.
Luego, recorremos todos los registros, pintamos el valor de #x, y miramos qué campos lo forman.
Para saberlo, usamos una expresión regular que tiene esta forma:
/(UID|FEC|TIE|VAL|LAT|SLA|LON|SLO|ALT|VEL)([^A-Z#]+)/g

que quiere decir: busca el comienzo de un campo conocido (y lo guardas en $1), seguido de una serie de caracteres que no son letras en mayúscula o un '#'. Y eso segundo me lo guardas en $2. Y lo repetimos para todo el registro (g).

Resultado:
Código: Seleccionar todo
6
1TIExxxxxxVALxLATxxxx,xxxx#
1:
TIE     xxxxxx
VAL     x
LAT     xxxx,xxxx
2SLOxUIDxxxxxSLOxFECxxxxxxVELxxxFECxxxxxxSLAxUIDxxxxxUIDxxxxxSLOx#
2:
SLO     x
UID     xxxxx
SLO     x
FEC     xxxxxx
VEL     xxx
FEC     xxxxxx
SLA     x
UID     xxxxx
UID     xxxxx
SLO     x
3SLOxSLOxFECxxxxxxTIExxxxxxFECxxxxxxVELxxxVELxxxFECxxxxxxLATxxxx,xxxxLONxxxx,xxxx#
3:
SLO     x
SLO     x
FEC     xxxxxx
TIE     xxxxxx
FEC     xxxxxx
VEL     xxx
VEL     xxx
FEC     xxxxxx
LAT     xxxx,xxxx
LON     xxxx,xxxx
4ALTxxxxxSLOxUIDxxxxxUIDxxxxxFECxxxxxxSLAxLATxxxx,xxxxTIExxxxxxALTxxxxxLONxxxx,xxxxLONxxxx,xxxxLATxxxx,xxxx#
4:
ALT     xxxxx
SLO     x
UID     xxxxx
UID     xxxxx
FEC     xxxxxx
SLA     x
LAT     xxxx,xxxx
TIE     xxxxxx
ALT     xxxxx
LON     xxxx,xxxx
LON     xxxx,xxxx
LAT     xxxx,xxxx
5VELxxxLATxxxx,xxxxLONxxxx,xxxxALTxxxxxFECxxxxxxUIDxxxxxVALxLATxxxx,xxxxTIExxxxxxSLAxALTxxxxx
5:
VEL     xxx
LAT     xxxx,xxxx
LON     xxxx,xxxx
ALT     xxxxx
FEC     xxxxxx
UID     xxxxx
VAL     x
LAT     xxxx,xxxx
TIE     xxxxxx
SLA     x
ALT     xxxxx

Para ejecutarlo, puedes dárselo en la entrada estándar:
Código: Seleccionar todo
echo '#1TIExxxxxxVALxLATxxxx,xxxx#2SLOxUIDxxxxxSLOxFECxxxxxxVELxxxFECxxxxxxSLAxUIDxxxxxUIDxxxxxSLOx
#3SLOxSLOxFECxxxxxxTIExxxxxxFECxxxxxxVELxxxVELxxxFECxxxxxxLATxxxx,xxxxLONxxxx,xxxx
#4ALTxxxxxSLOxUIDxxxxxUIDxxxxxFECxxxxxxSLAxLATxxxx,xxxxTIExxxxxxALTxxxxxLONxxxx,xxxxLONxxxx,xxxxLATxxxx,xxxx
#5VELxxxLATxxxx,xxxxLONxxxx,xxxxALTxxxxxFECxxxxxxUIDxxxxxVALxLATxxxx,xxxxTIExxxxxxSLAxALTxxxxx' | ./trama.pl
o lo guardas en un fichero y se lo pones como argumento:
Código: Seleccionar todo
./trama.pl trama.txt
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 Josmanue » 2006-07-21 05:54 @287

El primer caracter de la cadena siempre será el caracter "#", es el que marca el inicio de la cadena por así decirlo.

Y por último y gracias por todo.

En la expresión regular que usas, presupones que los caracteres de los campos no son "#" lo cual es cierto, sólo existe un caracter # al principio de la cadena, pero presupones también (porque yo no lo había dicho, claro) que no hay mayúsculas y esto no es cierto, ya que por ejemplo el campo VALx, esa x será o una "A" o una "V" mayúsculas.
Última edición por Josmanue el 2006-08-09 11:01 @501, editado 1 vez en total
Josmanue
Perlero nuevo
Perlero nuevo
 
Mensajes: 76
Registrado: 2006-06-09 04:33 @231

Notapor explorer » 2006-07-21 06:27 @310

Josmanue escribiste:En primer lugar no entiendo muy bien para qué haces lo de la trama. El equipo emisor, sólo enviará una cadena cada vez, con una serie de campos que algunos pueden o no aparecer, pero que nunca se van a repetir, es decir, cada cadena tendra 0 ó 1 campo VEL o FEC o... pero nunca dos VEL...
El que salgan dos es a causa del uso de rand. De algo tenía que partir. Además, para el caso que nos ocupa, no importa.
Josmanue escribiste:Si se recibe otra cadena, lo hará como otro paquete diferente pasados 2 o 3 minutos, no se si me explico.
Perfectamente.
Josmanue escribiste:Además el primer caracter de la cadena siempre será el caracter "#", es el que marca el inicio de la cadena por así decirlo.

Por otra parte dices que separas los registros con:
local $/ = '#';
@trama = <>;

pero teniendo en cuenta que solo recibe una cadena, esto no sería necesario ¿no?
Pues sí, no es necesario.

Josmanue escribiste:Una duda más, la búsqueda de campos que haces:

@campos = qw( UID FEC TIE VAL LAT SLA LON SLO ALT VEL );
$campos =join('|', @campos);


a ver si lo he entendido, esto me devolvería la misma cadena pero con los campos separados con el caracter "|"?? es decir:

UIDxxxxx|FECxxxxxx|TIExxxxxx|VALx|LATxxxx,xxxx|SLAx|LONxxxx,xxxx|SLOx|ALTxxxxx|VELxxx|

devolveria eso en la variable $campos??? porque si esto es así, sería suficiente con eso no? separo la cadena en sus campos y ahora los voy mandando a la Base de Datos no? o no es tan facil?

No, lo que devuelve el join está indicado en la Cita debajo del programa: 'UID|FEC|TIE|VAL|LAT|SLA|LON|SLO|ALT|VEL'.

Josmanue escribiste:Y por último y gracias por todo.

En la expresión regular que usas, presupones que los caracteres de los campos no son "#" lo cual es cierto, sólo existe un caracter # al principio de la cadena, pero presupones también (porque yo no lo había dicho, claro) que no hay mayúsculas y esto no es cierto, ya que por ejemplo el campo VALx, esa x será o una "A" o una "V" mayúsculas.

Lo dicho, me queda un divertido fin de semana de Perl y muchas gracias por la ayuda.

Eso lo complica bastante más... el que existan mayúsculas en los campos. Si es así, el programa que te he dado no te sirve para nada. Es necesario otro distinto...
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 explorer » 2006-07-21 07:41 @362

Bueno... prueba con este:
Código: Seleccionar todo
#!/usr/bin/perl -l

# Campos a buscar
@campos = qw(
    UID.{5}
    FEC.{6}
    TIE.{6}
    VAL.
    SLA.
    LAT.{4},.{4}
    LON.{4},.{4}
    SLO.
    ALT.{5}
    VEL.{3}
);
$campos = join('|', @campos);

# Recibimos la trama por la entrada estándar
$trama = <>;
chomp $trama;

# Sacamos el valor del #x
($id) = $trama =~ /^#([^A-Z]+)/;
print "$id:";

# Recorremos todos los campos para identificarles
while ( $trama =~ /($campos)/g ) {
    print "$1";
    ($campo, $valor) = unpack( "A3 A*", $1);
    print "\t$campo\t$valor";
}

Esto es lo que hacemos:
* Definimos @campos como una lista de expresiones regulares, que son el nombre de cada campo con su correspondiente longitud de su valor.
* Definimos $campos como la unión de todo eso, separado con '|', que es el 'or' de una expresión regular.
* Leemos la trama por la entrada estándar y le quitamos el final de línea.
* Identificamos el valor que hay detrás de '#'.
* Ahora, para toda la $trama (/g), buscamos los campos que coincidan con los nuestros. El resultado queda en $1 gracias a los paréntesis de captura.
* En $1 tendremos algo de la forma UIDxxxxx. Para separar las dos partes aprovechamos el hecho de que todos los campos tienen 3 letras. Usamos unpack para desempaquetar esa información en dos partes, $campo y $valor.

Un ejemplo:
Código: Seleccionar todo
echo '#4ALTxxxxxSLOxUIDxxxxxFECxxxxxxSLAxLATxxxx,xxxxTIExxxxxxLONxxxx,xxxx' | ./filtra.pl
4:
ALTxxxxx
        ALT     xxxxx
SLOx
        SLO     x
UIDxxxxx
        UID     xxxxx
FECxxxxxx
        FEC     xxxxxx
SLAx
        SLA     x
LATxxxx,xxxx
        LAT     xxxx,xxxx
TIExxxxxx
        TIE     xxxxxx
LONxxxx,xxxx
        LON     xxxx,xxxx
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 Josmanue » 2006-07-25 06:01 @292

Esto va teniendo muy buena pinta! :D esta opcion si que me parece que va a dar resultado, muchas gracias, eso del unpack no tenía ni idea, pero tengo algunas preguntas.

Una vuelta de tuerca más:

Teniendo en cuenta el formato del campo LATxxxx,xxxx donde las x´s son: GGMM,mmmm
GG:grados
MM:minutos
mmmm:décimas de minutos

Y LONxxxxx,xxxx donde las x´s son: GGGMM,mmmm

Tengo que calcular los campos:

$Latitud_Grados = (GG + MM/60 + mmmm/600000);
y
$Longitud_Grados = (GGG + MM/60 + mmmm/600000);

¿Se te ocurre algo para hacerlo? ¿Habría que separar el campo en GG, MM y mmmm y después calcularlo no?
Última edición por Josmanue el 2006-08-09 11:21 @514, editado 1 vez en total
Josmanue
Perlero nuevo
Perlero nuevo
 
Mensajes: 76
Registrado: 2006-06-09 04:33 @231

Notapor explorer » 2006-07-25 07:03 @335

Josmanue escribiste:¿por que sacas el valor de "#x" antes de recorrer los campos? ese es un campo más que también hay que insertarlo en el campo correspondiente en la base de datos. ¿Es porque el simbolo # puede dar problemas?
No, es porque es muy distinto de la forma en la que voy a buscar el resto de campos, que sí siguen la regla de estar formados por 3 letras mayúsculas y algo más. Empiezo averiguando ese dato pero lo podría haber hecho también al final (el valor de $trama no cambia en todo el programa).

Josmanue escribiste:Bueno y la expresión regular "[^A-Z]+" la he cambiado por "[^0-2]" ya que en este campo sólo hay dos valores posibles 0 ó 2 y como sólo es un caracter no necesito el signo "+" ¿no?

A ver... si SÓLO va a aparecer el carácter '0' o el '2' detrás del '#', entonces la expresión regular es esta: "/^#(0|2)/".

Josmanue escribiste:Por otro lado, ¿¿para que en el bucle separe en $campo y $valor no es necesario que trama sea una matriz asociativa?? es decir, ¿tendría que ser %trama en lugar de $trama? ¿¿o al menos podría cambiarse a %trama?? Lo digo porque creo que sería más fácil (creo) a la hora de insertar los campos en la base de datos decirle que, por ejemplo, en el campo Fecha de la BD inserte $trama{"FEC"} que sería directamente el valor de FEC. Vamos si es que me he enterado bien de cómo funcionan las matrices asociativas.
Bueno, pero es que mi programa no hace todo :)

Efectivamente, la forma más fácil sería poniendolo en memorias asociativas, pero antes miraría cómo es la forma en que lo voy a pasar a la base de datos. Si se trata sólo de construit un 'INSERT' entonces sí.

Antes un recordatorio. $trama es donde está la trama que nos ha pasado el usuario por la entrada estándar, así que no la cambiamos. Lo que sí podemos hacer es crear una nueva variable llamada %trama donde guardaremos los campos que vamos encontrando.

Quedaría algo así:
Código: Seleccionar todo
while ( $trama =~ /($campos)/g ) {
    print "$1";
    ($campo, $valor) = unpack( "A3 A*", $1);
    print "\t$campo\t$valor";
    $trama{$campo} = $valor;
}
y luego, a la hora de crear la instrucción sql:
Código: Seleccionar todo
$sql = 'INSERT INTO tabla SET ';
$sql .= join( ', ', map { "$_ = \'$trama{$_}\'" } keys %trama );
$sql .= ';';
que convierte la trama de ejemplo en:
INSERT INTO tabla SET LON = 'xxxx,xxxx', SLA = 'x', SLO = 'x', UID = 'xxxxx', LAT = 'xxxx,xxxx', ALT = 'xxxxx', TIE = 'xxxxxx', FEC = 'xxxxxx';
Quizás es difífil de ver lo que hace la línea join-map-keys, pero voy a tratar de explicarlo:
* En %trama tenemos los valores apuntados por sus respectivos campos.
* Como no nos importa el orden de salida de los campos/valores, uso keys para sacar los nombres de los campos.
* Esos campos van 'entrando' en el map, que va 'mapeandolos' (de ahí su nombre :) ) cada uno a una cadena de caracteres. En esa cadena de caracteres, la variable $_ guarda el valor del campo que nos ha entregado 'keys'. Entonces, como tenemos el valor del campo en $_, también tenemos acceso a su valor, que está en $trama{$_}. Usamos estos dos datos para construir la cadena de caracteres.
* El resultado de 'map' es un array o lista de varias cadenas de caracteres (todas de la forma "campo = 'valor'"). Bueno, pues con el join, las juntamos todas uniéndolas con ", ".

Josmanue escribiste:Teniendo en cuenta el formato del campo LATxxxx,xxxx donde las x´s son: GGMM,mmmm
GG:grados
MM:minutos
mmmm:décimas de minutos

Y LONxxxxx,xxxx donde las x´s son: GGGMM,mmmm

Tengo que calcular los campos:

$Latitud_Grados = (GG + MM/60 + mmmm/600000);
y
$Longitud_Grados = (GGG + MM/60 + mmmm/600000);

¿Se te ocurre algo para hacerlo? ¿Habría que separar el campo en GG, MM y mmmm y después calcularlo no?
Sí, hay que separarlos. Eso se puede hacer en el bucle while:
Código: Seleccionar todo
while ( $trama =~ /($campos)/g ) {
    print "1:$1";
    ($campo, $valor) = unpack( "A3 A*", $1);
    print "\t$campo\t$valor";
    if ( $campo eq 'LON' or $campo eq 'LAT' ) {
        ($GG,$MM,$mmmm) = $valor =~ /(...?)(..),(....)/; # Los grados puedes ser dos o tres cifras
        $valor = $GG + $MM/60 + $mmmm/600_000;
    }
    $trama{$campo} = $valor;
}


Josmanue escribiste:Ah! y una cosita más en el bucle, se guarda en la variable $1 por defecto ¿no?
Por defecto, no. La variable $1 'aparece' por efecto de los paréntesis que hay en la expresión regular en el while. Lo que hace es guardar 'lo que han encontrado esos paréntesis'. Si existieran otro par de paréntesis, podría 'aparecer' la variable $2, y así.
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 Josmanue » 2006-07-26 05:55 @288

Esto ya me va funcionando estupendamente :!: Muchisimas Gracias!!!

He hecho una modificacion al tema de la Latitud_Grados y Longitud_Grados ya que si SLA = 'S' Latitud_Grados debe ser negativo y lo mismo para Longitud_Grados si el campo SLO = 'W'. Asi que despues de calcularlos he puesto:
Código: Seleccionar todo
if ($trama{$SLA} = 'S')
{
      $Latitud_Grados = -$Latitud_Grados;
}


Y he tenido que separar tu bucle if en dos diferentes, uno para la Latitud y otro para la Longitud.
Y me funciona perfectamente :lol:

Ya sólo me quedan unos cuantos campos calculados más y la parte de la insercion en la Base de Datos que aun me tienen que dar acceso y no puedo probarlo aun, pero de momento pruebo las salidas con "print" y me da lo que yo quiero.

En respuesta a tu pregunta
Oye... ¿esos datos no estarán saliendo de un GPS, verdad?


Lo envian por GPRS o por Satelite, de hecho el primer dígito "#x" esa x indica un '0' si el informe lo ha enviado por GPRS o un '2' si el informe lo ha enviado por Satelite.
Son datos que envian coches forestales de prevencion de incendios en el campo y hay que almacenar esos datos en la Base de Datos.

¿Por qué lo preguntas? ¿Hay algun problema? :shock: :shock:
Josmanue
Perlero nuevo
Perlero nuevo
 
Mensajes: 76
Registrado: 2006-06-09 04:33 @231

Notapor explorer » 2006-07-26 06:51 @327

Por nada... por sí existía algún módulo que leyese esa información de forma directa. En CPAN he visto que hay unos cuántos módulos para GPS y satélite, pero no para GPRS.

Y sobre los incendios... me hace recordar mi anterior trabajo...
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 Básico

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 27 invitados