Projekt

Allgemein

Profil

Fehler #348

DatevExport kommt mit bestimmten Zeichen im Buchungstext nicht klar

Von Werner Hahn vor fast 7 Jahren hinzugefügt. Vor fast 6 Jahren aktualisiert.

Status:
Erledigt
Priorität:
Normal
Zugewiesen an:
-
Zielversion:
-
Beginn:
15.02.2018
Abgabedatum:
% erledigt:

0%

Geschätzter Aufwand:

Beschreibung

Bei Dialogbuchen ist es mir beim € Zeichen aufgefallen
Bei Rechnungen mit Kundennamen z.B. Kulašić

Zugehörige Revisionen

Revision 324726ac (diff)
Von Jan Büren vor fast 6 Jahren hinzugefügt

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

Historie

#1

Von Jan Büren vor fast 7 Jahren aktualisiert

  • Status wurde von Neu zu Abgewiesen geändert
  • Zielversion 3.6 wurde 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.

#2

Von Werner Hahn vor mehr als 6 Jahren aktualisiert

Hat sich doch als Fehler erwiesen, getestet mit Jan auf den CLTs

#3

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

#4

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.

#5

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?

#6

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

#7

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.

#8

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
  1. Oben:
    use List::Util qw(first);
    use POSIX qw(setlocale);
  1. 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
  1. 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;
  1. 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);`

#9

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);

#10

Von Moritz Bunkus vor fast 6 Jahren aktualisiert

(vergeblicher Postversuch vergeblich versucht zu entfernen)

#11

Von Jan Büren vor fast 6 Jahren aktualisiert

  • Status wurde von In Bearbeitung zu Erledigt geändert

mit #324726acd30b8992854a2d5

Auch abrufbar als: Atom PDF