no tiene conocimiento de ninguna solución empaquetada, pero algo no muy flexible es bastante simple de hacer suponiendo que usted puede hacer dos pasadas sobre el archivo: (Lo que sigue es parte Perlish ejemplo pseudocódigo)
- Supuesto: los datos pueden contener espacios y no está citado ala CSV si hay un espacio - si este no es el caso, sólo tiene que utilizar
Text::CSV(_XS)
.
- Supuesto: no se utilizan pestañas para formatear.
- La lógica define un "separador de columna" como cualquier conjunto consecutivo de filas verticales pobladas al 100% con espacios.
- Si por accidente cada fila tiene un espacio que forma parte de los datos con caracteres M de compensación, la lógica considerará el desplazamiento M como un separador de columna, ya que no puede saber nada mejor. La ÚNICA forma en que puede saber mejor es si necesita separación de columnas para que tenga al menos X espacios donde X> 1 - vea el segundo fragmento de código para eso.
Código de ejemplo:
my $INFER_FROM_N_LINES = 10; # Infer columns from this # of lines
# 0 means from entire file
my $lines_scanned = 0;
my @non_spaces=[];
# First pass - find which character columns in the file have all spaces and which don't
my $fh = open(...) or die;
while (<$fh>) {
last if $INFER_FROM_N_LINES && $lines_scanned++ == $INFER_FROM_N_LINES;
chomp;
my $line = $_;
my @chars = split(//, $line);
for (my $i = 0; $i < @chars; $i++) { # Probably can be done prettier via map?
$non_spaces[$i] = 1 if $chars[$i] ne " ";
}
}
close $fh or die;
# Find columns, defined as consecutive "non-spaces" slices.
my @starts, @ends; # Index at which columns start and end
my $state = " "; # Not inside a column
for (my $i = 0; $i < @non_spaces; $i++) {
next if $state eq " " && !$non_spaces[$i];
next if $state eq "c" && $non_spaces[$i];
if ($state eq " ") { # && $non_spaces[$i] of course => start column
$state = "c";
push @starts, $i;
} else { # meaning $state eq "c" && !$non_spaces[$i] => end column
$state = " ";
push @ends, $i-1;
}
}
if ($state eq "c") { # Last char is NOT a space - produce the last column end
push @ends, $#non_spaces;
}
# Now split lines
my $fh = open(...) or die;
my @rows =();
while (<$fh>) {
my @columns =();
push @rows, \@columns;
chomp;
my $line = $_;
for (my $col_num = 0; $col_num < @starts; $col_num++) {
$columns[$col_num] = substr($_, $starts[$col_num], $ends[$col_num]-$starts[$col_num]+1);
}
}
close $fh or die;
Ahora, si requiere la separación columna para que sea por lo menos espacios X, donde X> 1, también es factible, pero el analizador de lugares de columna tiene que ser un poco más complejo:
# Find columns, defined as consecutive "non-spaces" slices separated by at least 3 spaces.
my $min_col_separator_is_X_spaces = 3;
my @starts, @ends; # Index at which columns start and end
my $state = "S"; # inside a separator
NEXT_CHAR: for (my $i = 0; $i < @non_spaces; $i++) {
if ($state eq "S") { # done with last column, inside a separator
if ($non_spaces[$i]) { # start a new column
$state = "c";
push @starts, $i;
}
next;
}
if ($state eq "c") { # Processing a column
if (!$non_spaces[$i]) { # First space after non-space
# Could be beginning of separator? check next X chars!
for (my $j = $i+1; $j < @non_spaces
|| $j < $i+$min_col_separator_is_X_spaces; $j++) {
if ($non_spaces[$j]) {
$i = $j++; # No need to re-scan again
next NEXT_CHAR; # OUTER loop
}
# If we reach here, next X chars are spaces! Column ended!
push @ends, $i-1;
$state = "S";
$i = $i + $min_col_separator_is_X_spaces;
}
}
next;
}
}
Sírvanse proporcionar y el ejemplo. – DVK
Proporcioné una solución, pero producirá SEIS columnas. ¿Está haciendo una suposición de que el separador de columna DEBE ser> 1 espacio? – DVK
No, pero podemos suponer que sé las cadenas del encabezado de columna y que los datos de la columna están alineados correctamente debajo de los encabezados. – Thilo