Próbuję poprawić komunikat ostrzegawczy wydany przez Encode::decode()
. Zamiast drukować nazwę modułu i numer linii w module, chciałbym, aby wydrukował nazwę odczytanego pliku i numer linii w tym pliku, w którym znaleziono zniekształcone dane. Dla programisty wiadomość początkowa może być przydatna, ale dla użytkownika końcowego, który nie zna Perla, prawdopodobnie nie ma ona żadnego znaczenia. Użytkownik końcowy raczej chciałby wiedzieć, który plik daje problem.
Najpierw próbowałem rozwiązać ten problem, używając do tego obsługi $SIG{__WARN__}
(co prawdopodobnie nie jest dobrym pomysłem), ale otrzymuję błąd segfault. Prawdopodobnie głupi błąd, ale nie mogłem zrozumieć to:
#! /usr/bin/env perl
use feature qw(say);
use strict;
use warnings;
use Encode();
binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';
my $fn = 'test.txt';
write_test_file($fn);
# Try to improve the Encode::FB_WARN fallback warning message :
#
# utf8 "\xE5" does not map to Unicode at <module_name> line xx
#
# Rather we would like the warning to print the filename and the line number:
#
# utf8 "\xE5" does not map to Unicode at line xx of file <filename>.
my $str = '';
open (my $fh, "<:encoding(utf-8)", $fn) or die "Could not open file '$fn': $!";
{
local $SIG{__WARN__} = sub { my_warn_handler($fn, $_[0]) };
$str = do { local $/; <$fh> };
}
close $fh;
say "Read string: '$str'";
sub my_warn_handler {
my ($fn, $msg) = @_;
if ($msg =~ /\Qdoes not map to Unicode\E/) {
recover_line_number_and_char_pos($fn, $msg);
}
else {
warn $msg;
}
}
sub recover_line_number_and_char_pos {
my ($fn, $err_msg) = @_;
chomp $err_msg;
$err_msg =~ s/(line \d+)\.$/$1/; # Remove period at end of sentence.
open ($fh, "<:raw", $fn) or die "Could not open file '$fn': $!";
my $raw_data = do { local $/; <$fh> };
close $fh;
my $str = Encode::decode('utf-8', $raw_data, Encode::FB_QUIET);
my ($header, $last_line) = $str =~ /^(.*\n)([^\n]*)$/s;
my $line_no = $str =~ tr/\n//;
++$line_no;
my $pos = (length $last_line) + 1;
warn "$err_msg, in file '$fn' (line: $line_no, pos: $pos)\n";
}
sub write_test_file {
my ($fn) = @_;
my $bytes = "Hello\nA\x{E5}\x{61}"; # 2 lines ending in iso 8859-1: åa
open (my $fh, '>:raw', $fn) or die "Could not open file '$fn': $!";
print $fh $bytes;
close $fh;
}
wyjściowa:
utf8 "\xE5" does not map to Unicode at ./p.pl line 27
, in file 'test.txt' (line: 2, pos: 2)
Segmentation fault (core dumped)
Może my_warn_handler przechodzi w nieskończonej rekurencji. Spróbuj wstawić 'local $ SIG {__ WARN __};' wewnątrz my_warn_handler, aby przywrócić domyślne zachowanie? – Waxrat
@Waxrat Zgodnie z [documentation] (http://perldoc.perl.org/functions/warn.html), to nie powinno się zdarzyć: * "Większość programów obsługi musi więc ustawić wyświetlanie ostrzeżeń, które nie są przygotowane do Zajmij się tym, wywołując ostrzeżenie ponownie w programie obsługi Uwaga: jest to całkiem bezpieczne i nie spowoduje nieskończonej pętli, ponieważ \ __ WARN \ __ haki nie są wywoływane od wewnątrz. "* –
Pomyśl, że znalazłem błąd: Zapomniałem zadeklaruj '$ fh' jako leksykalny w treserze. Otwiera ponownie leksykalny '$ fh' zdefiniowany w otaczającym zakresie, a później zamyka ten uchwyt. To prawdopodobnie powoduje pewne zamieszanie w 'Encode :: decode()' .. –