Fehler #348
DatevExport kommt mit bestimmten Zeichen im Buchungstext nicht klar
0%
Beschreibung
Bei Dialogbuchen ist es mir beim € Zeichen aufgefallen
Bei Rechnungen mit Kundennamen z.B. Kulašić
Zugehörige Revisionen
Historie
Von Jan Büren vor fast 7 Jahren aktualisiert
- Status wurde von Neu zu Abgewiesen geändert
- Zielversion
3.6wurde gelöscht
Nein.
Der DATEV-Import kann nur CP-1250 (Windows Europäisch) verarbeiten.
Damit erst gar nicht in dieser fehlerhaften Form exportiert wird, hab ich eine strikte Prüfung hierfür angesetzt, bzw. diese Zeichen werden gefiltert.
Das wäre jetzt meine erste Vermutung.
Prüf mal, wie das € kodiert ist.
Es gibt auch Testfälle für dieses Verhalten, wenn Du da etwas verbessert willst / kannst.
Von Werner Hahn vor mehr als 6 Jahren aktualisiert
Hat sich doch als Fehler erwiesen, getestet mit Jan auf den CLTs
Von Jan Büren vor mehr als 6 Jahren aktualisiert
- Status wurde von Abgewiesen zu In Bearbeitung geändert
Hallo Werner,
ja, sorry fürs erste Abweisen, aber ich bin erstmal von internen Kodierungsfehlern ausgegangen.
Auf der anderen Seite wäre ein Feedback zu "Prüf mal wie das € kodiert ist", simpel machbar gewesen:
Das €-Zeichen ist folgendermaßen kodiert:
$ cat t € $ hexdump -C t 00000000 e2 82 ac 0a |....|
Ah, ok 'e2 82 ac' -> € in utf8 (s.a.: http://utf8-chartable.de/unicode-utf8-table.pl?start=8320&number=128&names=)
Das sollte sauber nach cp1252 konvertieren, nämlich so:
$ iconv -futf8 -tcp1252 t €
Kulašić ist jetzt der Knackpunkt, hier gibt es ein Zeichen das nicht in cp1252 vorhanden ist.
$ cat kulasic # utf8 terminal Kulašić $ iconv -futf8 -tcp1252 kulasic # win-1252 terminal Kulašiiconv: ungültige Eingabe-Sequenz an der Stelle 7
An der Stelle war ich bei Entwickeln unsicher, ob ich das generell ablehne oder dann nur teilweise kodiert übergeben soll.
Soweit die Zusammenfassung, ich hab jetzt einen Testfall für diese 4 Varianten und einen Fix.
S.a. Commit f2b3e089.
P.S.: Es gibt wieder steigmann unstable
Von Moritz Bunkus vor mehr als 6 Jahren aktualisiert
Du kannst iconv
(und damit Text::Iconv
) mit der Option translit
nutzen. Dann werden Zeichen, die im Zielzeichensatz nicht vorhanden sind, durch ähnliche Zeichen ersetzt:
[0 mbunkus@chai-latte ~] echo Kulašić | iconv -f utf-8 -t cp1252//translit | iconv -f cp1252 -t utf-8 Kulašic [0 mbunkus@chai-latte ~] perl -MText::Iconv -Mutf8 -e 'print(Text::Iconv->new("utf-8", "cp1252//translit")->convert("Kulašić"), "\n")' | iconv -f cp1252 -t utf-8 Kulašic
In kivitendo über den Wrapper SL::Iconv
.
Von Jan Büren vor etwa 6 Jahren aktualisiert
Hi,
der folgende Code-Schnipsel tut außerhalb von kivitendo das was er soll:
use Text::Iconv; use strict; open my $fh_in, '<', 'input2.txt' or die "could not open <input.txt> for reading: $!"; open my $fh_out, '>', 'output.txt' or die "could not open <output.txt> for writing: $!"; print $fh_out Text::Iconv->new("utf-8", "cp1252//translit")->convert($_) while <$fh_in>; close $fh_in; close $fh_out;
Ein strace ergibt:
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/perl/5.26/DynaLoader.pm", O_RDONLY) = 4 openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/perl5/5.26/auto/Text/Iconv/Iconv.so", O_RDONLY|O_CLOEXEC) = 4
Ein hexdump zeigt das c als 0x63 was in cp1252 dem c entspricht:
00000260 69 63 22 |ic"
In kivi funzt das nicht. Sind hier noch andere Konfigurationen gesetzt, wird ein anderes Modul geladen?
my $filename_translit = "EXTF_DATEV_kivitendo_translit" . $self->from->ymd() . '-' . $self->to->ymd() . ".csv"; open my $fh_in, '<:encoding(UTF-8)', $self->export_path . '/' . $filename or die "could not open $filename for reading: $!"; open my $fh_out, '>', $self->export_path . '/' . $filename_translit or die "could not open $filename_translit for writing: $!"; print $fh_out Text::Iconv->new("utf-8", "cp1252//translit")->convert($_) while <$fh_in>; close $fh_in; close $fh_out;
Damit wird aus dem ć ein ?:
hexcode:
00000260 69 3f 22 |i?"
Wen mich per strace an den fcgi-Prozess dranhänge, bekomme ich keine klare Antwort, welches Modul geladen wird.
Ideen?
Von Jan Büren vor etwa 6 Jahren aktualisiert
Der Testfall lässt sich noch simpler beschreiben:
kivi:
$main::lxdebug->message(0, "was hier " . Text::Iconv->new("utf-8", "cp1252//translit")->convert("Kulašić")); was hier Kulaši?
perl skript:
print Text::Iconv->new("utf-8", "cp1252//translit")->convert("Kulašić"); Kulašic
Von Bernd Bleßmann vor fast 6 Jahren aktualisiert
Der Unterschied scheint mir die Umgebung zu sein, speziell die Spracheinstellungen (LANG (LANGUAGE?/LOCALE?)):
bernd@specht-kivitendo:~/kivi$ perl -MText::Iconv -Mutf8 -e 'print(Text::Iconv->new("utf-8", "cp1252//translit")->convert("Kulašić"), "\n");' | iconv -f cp1252 -t utf-8 Kulašic bernd@specht-kivitendo:~/kivi$ LANG= perl -MText::Iconv -Mutf8 -e 'print(Text::Iconv->new("utf-8", "cp1252//translit")->convert("Kulašić"), "\n");' | iconv -f cp1252 -t utf-8 Kulaši?
Beim ersten Beispiel wird das 'c' umgewandelt, beim zweiten (mit LANG=) nicht, wie in der kivi. Was das für Implikationen für den kivi-Code hat bzw. was man da machen kann/muss, weiß ich nicht.
Von Moritz Bunkus vor fast 6 Jahren aktualisiert
Das Problem liegt in der Tat in den Locales. iconv ist eine Funktion der glibc unter Linux, und das Modul `Text::Iconv` ist nur ein dünner Wrapper darüber. Für die glibc wird beim Start des Perlinterpreters die Locales initialisiert, u.a. die für den Zeichensatz (`LC_ALL` falls gesetzt, ansonsten `LC_CTYPE`, ansonsten aus `LANG` abgeleitet). Unter (F)CGI sind die ganzen Umgebungsvariablen, die die Locale angeben, aber leer, weshalb effektiv die Locale `C` (= POSIX-Locale mit Zeichensatz ASCII) zum Einsatz kommt.
Wird nun die Konvertierungsfunktion mit einem String wie Kulašić aufgerufen, so kann iconv wohl nicht transliteraten, wenn die `LC_CTYPE=C` gilt.
Also ist die Lösung, dafür zu sorgen, dass `LC_CTYPE` auch brauchbar gesetzt ist. Dazu hat man mehrere Optionen:
1. Man sorgt dafür, dass die Umgebungsvariablen richtig gesetzt sind, bevor Perl ausgeführt wird. Wie das geht, hängt in dem Moment also vom verwendeten Webserver ab.
2. Man könnte global (z.B. im Dispatcher, weil durch den alles läuft) einmal `setlocale("LC_CTYPE", "de_DE.UTF-8");` machen.
3. Man könnte das nur in der Funktion machen, die zu CP1252 umwandeln soll. Da Locale-Einstellungen globaler State sind, sollte die Funktion nach sich aufräumen und die Locale wieder auf den vorherigen Wert zurück stellen.
Eine Funktion, die einen String in CP1252 mit Transliteration umwandelt, könnte z.B. so aussehen (tatsächlich getestet):
```perl- Oben:
use List::Util qw(first);
use POSIX qw(setlocale);
- Woanders:
sub to_cp1252_translit {
my ($text) =_;
);eval {
my $old_locale = first { setlocale('LC_ALL', $_) } qw(de_DE.UTF-8 en_US.UTF-8);
$text = Text::Iconv->new("utf-8", "cp1252//translit")->convert($text);
setlocale('LC_ALL', $old_locale);
1;
} or do {
$::lxdebug->message(LXDebug::WARN(), "to_cp1252_translit exception: " . $
return '';
};return $text;
}
```
Es wird vorausgesetzt, dass entweder `de_DE.UTF-8` oder `en_US.UTF-8` als Locale auf dem Server vorhanden ist. Welche, ist egal; es geht hier nur um den Zeichensatz, nicht um die Sprache, das Datumsformat oder so (wir setzen ja nur `LC_CTYPE` und nicht z.B. `LC_MESSAGES`).
Mein persönlicher Favorit wäre eher Variante 2, es global zu setzen, denn dann würden alle iconv-Aufrufe davon profitieren (und evtl. andere C-Funktionen). Das könnte man dann direkt in `SL::Dispatcher::new` machen, da das nur einmal pro laufender Instanz gemacht werden muss (ebenfalls getestet):
```perl- Oben ergänzen:
use POSIX qw(setlocale);
sub new {
my ($class, $interface) = @_;
my $self = bless {}, $class;
$self->{interface} = lc($interface || 'cgi');
$self->{auth_handler} = SL::Dispatcher::AuthHandler->new;
SL::ArchiveZipFixes->apply_fixes;
- Initialize character type locale to UTF-8:
foreach my $locale (qw(de_DE.UTF-8 en_US.UTF-8)) {
last if setlocale('LC_ALL', $locale);
}
return $self;
}
```
Der Konvertierungsaufruf ist dann wirlich nur noch `my $new_text = Text::Iconv->new("utf-8", "cp1252//translit")->convert($old_text);`
Von Moritz Bunkus vor fast 6 Jahren aktualisiert
Ach fuck you, vergessen, dass Redmine ja nicht Markdown nutzt. Und bearbeiten kann ich meine Kommentare auch nicht, daher einmal komplett neu.
Das Problem liegt in der Tat in den Locales. iconv ist eine Funktion der glibc unter Linux, und das Modul Text::Iconv
ist nur ein dünner Wrapper darüber. Für die glibc wird beim Start des Perlinterpreters die Locales initialisiert, u.a. die für den Zeichensatz (LC_ALL
falls gesetzt, ansonsten LC_CTYPE
, ansonsten aus LANG
abgeleitet). Unter (F)CGI sind die ganzen Umgebungsvariablen, die die Locale angeben, aber leer, weshalb effektiv die Locale C
(= POSIX-Locale mit Zeichensatz ASCII) zum Einsatz kommt.
Wird nun die Konvertierungsfunktion mit einem String wie Kulašić
aufgerufen, so kann iconv wohl nicht transliteraten, wenn die LC_CTYPE=C
gilt.
Also ist die Lösung, dafür zu sorgen, dass LC_CTYPE
auch brauchbar gesetzt ist. Dazu hat man mehrere Optionen:
1. Man sorgt dafür, dass die Umgebungsvariablen richtig gesetzt sind, bevor Perl ausgeführt wird. Wie das geht, hängt in dem Moment also vom verwendeten Webserver ab.
2. Man könnte global (z.B. im Dispatcher, weil durch den alles läuft) einmal setlocale("LC_CTYPE", "de_DE.UTF-8");
machen.
3. Man könnte das nur in der Funktion machen, die zu CP1252 umwandeln soll. Da Locale-Einstellungen globaler State sind, sollte die Funktion nach sich aufräumen und die Locale wieder auf den vorherigen Wert zurück stellen.
Eine Funktion, die einen String in CP1252 mit Transliteration umwandelt, könnte z.B. so aussehen (tatsächlich getestet):
# Oben: use List::Util qw(first); use POSIX qw(setlocale); #Woanders: sub to_cp1252_translit { my ($text) = @_; eval { my $old_locale = first { setlocale('LC_ALL', $_) } qw(de_DE.UTF-8 en_US.UTF-8); $text = Text::Iconv->new("utf-8", "cp1252//translit")->convert($text); setlocale('LC_ALL', $old_locale); 1; } or do { $::lxdebug->message(LXDebug::WARN, "to_cp1252_translit exception: " . $@); return ''; }; return $text; }
Es wird vorausgesetzt, dass entweder de_DE.UTF-8
oder en_US.UTF-8
als Locale auf dem Server vorhanden ist. Welche, ist egal; es geht hier nur um den Zeichensatz, nicht um die Sprache, das Datumsformat oder so (wir setzen ja nur LC_CTYPE
und nicht z.B. LC_MESSAGES
).
Mein persönlicher Favorit wäre eher Variante 2, es global zu setzen, denn dann würden alle iconv-Aufrufe davon profitieren (und evtl. andere C-Funktionen). Das könnte man dann direkt in SL::Dispatcher::new
machen, da das nur einmal pro laufender Instanz gemacht werden muss (ebenfalls getestet):
#Oben ergänzen: use POSIX qw(setlocale); sub new { my ($class, $interface) = @_; my $self = bless {}, $class; $self->{interface} = lc($interface || 'cgi'); $self->{auth_handler} = SL::Dispatcher::AuthHandler->new; SL::ArchiveZipFixes->apply_fixes; # Initialize character type locale to UTF-8: foreach my $locale (qw(de_DE.UTF-8 en_US.UTF-8)) { last if setlocale('LC_ALL', $locale); } return $self; }
Der Konvertierungsaufruf ist dann wirlich nur noch my $new_text = Text::Iconv->new("utf-8", "cp1252//translit")->convert($old_text);
Von Moritz Bunkus vor fast 6 Jahren aktualisiert
(vergeblicher Postversuch vergeblich versucht zu entfernen)
Von Jan Büren vor fast 6 Jahren aktualisiert
- Status wurde von In Bearbeitung zu Erledigt geändert
mit #324726acd30b8992854a2d5
Fixt #348 DatevExport kommt mit bestimmten Zeichen im Buchungstext nicht klar
In der Mandantenkonfiguration befindet sich jetzt eine Einstellung,
welche die Kodierung des DATEV-Exports steuert. DATEV erwartet CP1252.
kivitendo kann diese Kodierung so vom kivitendo Nutzer einfordern, alternativ nicht
vorhandenen Zeichen versuchen zu ersetzen oder die DATEV-Erwartung ignorieren
und UTF-8 liefern. Voreingestellt ist CP1252 mit Ersetzungen