Pas au courant de toute solution packagée, mais quelque chose de pas très flexible est assez simple en supposant que vous pouvez faire deux passes sur le fichier: (ce qui suit est partiellement perlienne exemple pseudocode)
- Assomption: les données peuvent contenir des espaces et n'est pas cité ala CSV s'il y a un espace - si ce n'est pas le cas, il suffit d'utiliser
Text::CSV(_XS)
. Hypothèse: aucun onglet utilisé pour le formatage.
- La logique définit un "séparateur de colonnes" comme étant n'importe quel ensemble consécutif de lignes verticales peuplées à 100% d'espaces.
- Si par hasard chaque ligne a un espace qui fait partie des données à des caractères M décalés, la logique considèrera M comme un séparateur de colonne, car elle ne peut pas mieux connaître. La seule façon dont il peut mieux savoir est si vous avez besoin d'une séparation de colonne d'au moins X espaces où X> 1 - voir le deuxième fragment de code pour cela.
Exemple de code:
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;
Maintenant, si vous besoin d'une séparation de colonne à au moins X espaces où X> 1, il est également faisable, mais l'analyseur d'emplacements de colonne doit être un peu plus complexe:
# 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'il vous plaît fournir et exemple. – DVK
J'ai fourni une solution, mais elle produira six colonnes. Faites-vous l'hypothèse que le séparateur de colonnes DOIT être> 1 espace? – DVK
Non, mais nous pouvons supposer que je connais les chaînes d'en-tête de colonne, et que les données de la colonne sont correctement alignées sous les en-têtes. – Thilo