IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Lazarus Pascal Discussion :

Encore des problèmes d'encodage


Sujet :

Lazarus Pascal

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    196
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 196
    Points : 435
    Points
    435
    Par défaut Encore des problèmes d'encodage
    Bonjour à tous et bonne année !

    Je continue à m'amuser à convertir de vieux projets Delphi, et j'achoppe aujourd'hui sur Dicogène, un projet écrit jadis avec mon ami Yves Ouvrard. Ça prend des fichiers textes, par exemple le texte d'un livre entier, et ça devait en récupérer les mots avec des statistiques (occurrences, nombre de mots, longueur moyenne des phrases, des mots, etc...) On trouve encore le binaire sur le site de l'académie de Rouen, où la paternité m'en est généreusement accordée alors que j'ai seulement eu l'idée et corrigé quelques bugs

    http://lettres.ac-rouen.fr/cdrom/fic...g/dicogene.htm

    La conversion Delphi-Lazarus fonctionne bien, mais j'ai un problème avec l'encodage, comme d'habitude :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    procedure TForm1.BitBtn2Click(Sender: TObject);
      var i, p : integer;
          mot : string;
          car : char;
      const FTemp = 'C:\windows\temp\dicoG.tmp';
    begin
      Cessez := false;
      // initialisation des variables statistiques
      NMots := 0;
      NPhrases := 0;
      NCarAlpha := 0;
      For i := 0 to Flux.Size - 1 do
        begin
          Flux.Read (Car, 1);
          case Car of
             'a'..'z',
             'A'..'Z',
             'À'..'Ö', 'Ø'..'ö', 'ø'..'ÿ' :
               begin
                 mot := mot + car;
                 inc (NCarAlpha);
               end;
             else
               begin   //détecter fin de phrases
               if (pos (car, '.?!') > 0)
                 and (length (mot) > 1)
                 then inc (NPhrases);
               if mot > '' then  //fin de mot
                 begin
                   if Liste.Find (mot, p)
                     then Liste.incremente (p)
                     else Liste.AddObject(mot, TStat.Create) ;
                   inc (NMots);
                   mot := '';
                 end;
               end;
            end;
          application.processmessages;
          if Cessez then Break;
          Panel1.Width := trunc (i * RichEdit1.width / Flux.size);
        end;
      Flux.Free;
     
      // exclus
      with Form2 do
        if CheckExclus.Checked then
          for i := 0 to Memo1.Lines.count - 1 do
            if Liste.Find (Memo1.lines[i], p)
              then Liste.Delete (p);
     
      Liste.Sorted := False;
      Case Form2.RadioGroup1.ItemIndex of
        0 :; // alpha : ne rien faire ;
        1 :  // alpha + fréq : éditer
            for i := 0 to Liste.count - 1
              do Liste [i] := format (liste[i] + #9#9'%d', [liste.freq [i]]);
        2 : // fréq : retrier
          begin
            Liste.TrieFreq (0, liste.count - 1);
            for i := 0 to Liste.count - 1
              do Liste [i] := format (liste[i] + #9#9'%d', [liste.freq [i]]);
          end;
        end;
     
      Liste.Insert(0,  #13#10'Résultats'#13#10);
      // ajout des Stats à la liste;
      with Form2 do
        begin
          if CheckMCarMot.Checked
            then Liste.Insert (
              0, Format ('Nombre moyen de caractères par mot : %.5f',
                [NCarAlpha / NMots]));
          if CheckNCarAlpha.Checked
            then Liste.insert (
              0, Format ('Nombre de caractères alphabétiques : %d',
                [NCarAlpha]));
          if CheckMotsPhrase.Checked
            then Liste.insert (
              0, Format ('Nombre moyen de mots par phrase : %.5f',
              [NMots / NPhrases]));
          if CheckNPhrases.Checked
            then Liste.insert (
              0, Format ('Nombre de Phrases : %d', [NPhrases]));
          if CheckNMots.Checked
            then Liste.insert (
              0, format ('Nombre de mots : %d', [NMots]));
          if (CheckNMots.Checked)
            or (CheckNPhrases.Checked)
            or (CheckMotsPhrase.Checked)
            or (CheckNCarAlpha.Checked)
            or (CheckMCarMot.Checked)
            then Liste.insert (0, 'Statistiques'#13#10);
        end;
    Quand le compilateur rencontre 'A'..'Z', il beugle que ça fait duplicate case label avec 'a'..'z''. OK. Et quand je remplace par 'A'..'z', il bute sur 'À'..'Ö' (Constant and CASE types do not match) ; aucun caractère accentué ni avec diacritique n'est accepté. Si je me contente de 'A'..'Z', ça compile. (Je peux écrire « Case car of 'A'..'z', #140, #156, #158..#159, #192..#214, #216..#246, #248..#255 : » en imaginant que ça travaille sur des chaînes ANSI, ça passe à la compilation, mais ça récupère les caractères accentués avec un codage incorrect (Ú pour é dans fichiers sauvegardés, points d'interrogation dans TMemo). C'est donc un problème ansi/utf8 que je ne sais pas régler, alors que la solution doit être quasi évidente pour plusieurs d'entre vous.

    Sans vous moquer trop de moi – quoique j'aie peur de le mériter –, pourriez-vous m'aider dans cette mauvaise passe ? Je joins un 7z des sources.

    Merci d'avance.
    Fichiers attachés Fichiers attachés

  2. #2
    Expert confirmé
    Avatar de Ph. B.
    Homme Profil pro
    Freelance
    Inscrit en
    Avril 2002
    Messages
    1 784
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 57
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2002
    Messages : 1 784
    Points : 5 915
    Points
    5 915
    Par défaut
    Bonjour,

    Le "caractère 'Ö'" est encodé en UTF-8 avec Lazarus, donc sur plus de 1 octet, d'où le rejet à la compilation.,
    AMHA, il faut reprendre la procédure d'analyse syntaxique en fonction de l'encodage du fichier à traiter (ANSI, OEM, UTF-8, Unicode).
    Il y a plusieurs moyens de le faire. Le lien suivant donne plusieurs éléments pour y parvenir :
    http://wiki.freepascal.org/LCL_Unico...and_characters
    Philippe.

  3. #3
    Membre éprouvé
    Profil pro
    Développeur informatique
    Inscrit en
    Janvier 2010
    Messages
    469
    Détails du profil
    Informations personnelles :
    Âge : 66
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Janvier 2010
    Messages : 469
    Points : 1 100
    Points
    1 100
    Par défaut
    Bonjour

    Je me permets de compléter la piste précédente, qui ne me semble pas suffisante pour bien comprendre ce qui se passe.

    Tout d'abord, une constante formée d'un caractère accentué n'est pas de type char, ce type ne contenant qu'un octet de 8 bits. C'est un widecar mais comme le case ne permet en Pascal que de ne tester des types très simples, on ne peut pas l'utiliser dans ce cas.

    Une solution provisoire de type bricolage serait de s'assurer que le texte lu est codé en ANSI ou autre "vieux" jeu de caractères 8 bits européen de l'ouest (WIN-1252 ou ISO-8859-1 ou ISO8859-15 par exemple), et éventuellement de le convertir ainsi lors de la lecture si ce n'est pas le cas (LazUTF8 permet de le faire facilement). Dans ce cas les traitements sur un caractère devraient pouvoir être conservés, à condition de spécifier les caractères accentués par des constantes numériques dans les CASE. Mais alors, pour bien afficher les mots lus contenant des accents il faudra les convertir en UTF8 avant, et en débogage l'affichage ne sera pas correct si on ne convertit pas. Et bien sûr ça ne marchera pas pour des langues non ouest-européennes.

    La solution prospective serait plutôt de s'assurer que le texte lu est en UTF8, et éventuellement de le convertir si ce n'est pas le cas, puis de trouver des moyens d'identifier les séparateurs de mots. Il faut alors bien penser que les caractères peuvent faire 1 ou 2 octets et comparer avec les méthodes de l'unité LazUTF8. Il va falloir mettre des suites de caractères du fichier dans une ou plusieurs String, sans couper en deux un caractère de plus de 8 bits, et chercher les séparateurs de ces string (sans le case).

    Si tu as des difficultés avec un fichier de donnée particulier, il serait utile de le joindre.
    Cordialement,
    Tintinux

    Initiateur de Gestinux, une comptabilité gestion open-source, pour Linux, Windows et Mac OS.
    Une version stable et une autre en développement, avec Lazarus : vous pouvez aider à la tester, la traduire et à la développer.

  4. #4
    Membre averti
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    196
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 196
    Points : 435
    Points
    435
    Par défaut
    Bonjour,

    Grand merci à tous deux pour ces réponses éclairantes de gens éclairés.

    Le texte (servant déjà aux tests en 2000), est codé en ANSI, et je me suis même un moment égaré trop en arrière dans l'archéologie en le croyant d'abord en ASCII. Quand je teste dans le CASE avec des valeurs numériques correspondants aux caractères ANSI,

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Case Car of
          #65..#90, #97..#122, #140, #156, #158..#159, #192..#214, #216..#246, #248..#255 :
            begin
    le travail est fait correctement mais les mots mal transcrits dans le fichier sauvegardé et des caractères remplacés par des ? dans le TMemo.

    En compilant sous Linux, les dysfonctionnements sont différents : les listes enregistrées dans le fichier temporaire (que j'ai déplacé en /tmp/ au lieu de C:\windows\temp\) et celles qu'on sauvegarde avec le bouton s'affichent correctement (dans gedit). Mais normalement, le TMemo devrait s'afficher en se déroulant en fonction du nombre de lines et il reste fermé comme s'il ne recevait pas de texte. Au début j'ai pensé que c'était dû à mon oubli de relocaliser le fichier temporaire, mais je n'ai pas plus de résultats après.

    Je ne suis pas très enthousiasmé par un passage en UTF8 avec des longueurs de caractères variables pour un logiciel dont je n'ai à vrai dire plus beaucoup d'usage.

    Le traitement des chaînes UTF8 me paraît une galère épouvantable. Je n'ai jamais approuvé aucune réforme de l'orthographe, parce que ce sont toutes des bricolages d'amateurs, mais j'adhérerais à celle, radicale, qui supprimerait tous les accents, en les remplaçant éventuellement par des lettres comme dans le français du Moyen Âge, temps béni où il n'y avait pas d'orthographe pourvu que les mots fussent transcrits lisiblement quand je pense que les inventeurs de nouvelles langues (espéranto, romanisation moderne du turc...) ont négligé les limitations de l'écriture mécanique (machines à écrire, qui existaient déjà pourtant) et numérique, en introduisant, les malheureux, des tas de diacritiques qui pourrissent le code, je me dis qu'ils ont rudement manqué de flair...

  5. #5
    Membre éprouvé
    Profil pro
    Développeur informatique
    Inscrit en
    Janvier 2010
    Messages
    469
    Détails du profil
    Informations personnelles :
    Âge : 66
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Janvier 2010
    Messages : 469
    Points : 1 100
    Points
    1 100
    Par défaut
    le travail est fait correctement mais les mots mal transcrits
    Dans ma 1ère solution provisoire, comme je le dis, il faut pour afficher correctement les chaînes dans le TMemo, les convertir avec la fonction AnsiToUtf8 avant de les ajouter.

    Par contre, quand tu insères dans la Liste des stats, les textes spécifiés comme constantes du programme ("Nombre moyen de caractères") sont de l'UTF8 et seront enregistrés comme tels. Si le reste du fichier est bien en UTF8, grâce à la la conversion indiquée ci-dessus, il ne devrait pas y avoir de souci. Sinon il aura un mélange de jeux de caractères et là les éditeurs de texte risquent d'avoir du mal...

    On peut le remplir directement, sans passer par une Liste ni un fichier temporaire, avec :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    RichEdit1.Lines.AddObject ( AnsiToUtf8(UnMotExtraitDuFichierANSI),   TStat.Create );
    RichEdit1.Lines.Insert ( 0, 'un texte accentué déjà en UTF8');
    Et d'ailleurs je ne vois pas l'utilité d'un TRichEdit, vu qu'il ne contient que du texte brut. Un TMemo sera plus léger.

    Bon courage !
    Cordialement,
    Tintinux

    Initiateur de Gestinux, une comptabilité gestion open-source, pour Linux, Windows et Mac OS.
    Une version stable et une autre en développement, avec Lazarus : vous pouvez aider à la tester, la traduire et à la développer.

  6. #6
    Membre chevronné

    Homme Profil pro
    au repos
    Inscrit en
    Février 2014
    Messages
    429
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : au repos

    Informations forums :
    Inscription : Février 2014
    Messages : 429
    Points : 1 884
    Points
    1 884
    Par défaut
    Bonsoir,

    Le traitement des chaînes UTF8 me paraît une galère épouvantable
    Venant de Delphi 7 (string en ansi), j'ai aussi galéré (et ce n'est pas fini !). Des tas de tests faits. Est-ce que la propriété FileName d'un TOpenDialog est en utf8 ? ...
    Si je tenais l'inventeur de l'utf8... qui, sous prétexte de gagner de l'espace mémoire et disque, a imaginé un caractère sur 1,2,3 ou 4 octets !!!

    Bon, voici un code qui va peut-être t'aider. Il vaut ce qu'il vaut, ce n'est pas très joli comme programmation, mais cela marche.
    Au lieu d'utiliser un Stream, j'utilise un TStringList pour ouvrir le fichier texte.
    Attention : ce code ne vaut que pour l'encodage Latin. Les caractères <= 127 sont identiques aux caractères ansi. Pour les caractères > 127, le premier octet a la valeur $C3, ce qui indique qu'il faut lire l'octet suivant. Ainsi le caractère "é" = $C3 $A9.
    Voir : http://www.utf8-chartable.de

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    procedure Lire;
    var
      SL: TStringList;
      i, j: integer;
      S: string;
      Mot: string;
    begin
      SL:= TStringList.Create;
      try
         SL.LoadFromFile('C:\Test.txt'); // codé en ansi
         Mot:= '';
         for j:= 0 to SL.Count - 1 do
         begin
            S:= SL[j];
            S:= AnsiToUtf8(S); // conversion en UTF8
            i:= 1;
            while i <= Length(S) do
            begin
               if (Byte(S[i]) = $C3) and (i < Length(S)) then // il faut lire l'octet suivant
               begin
                   if Byte(S[i+1]) in [$80..$96, $98..$B6, $B8..$BF] then
                        Mot:= Mot + S[i] + S[i+1];
                   inc(i);
               end
               else
                  if S[i] in [#65..#90, #97..#122] then Mot:= Mot + S[i];
               Inc(I);
            end;
         end;
      finally
         SL.Free;
      end;
     // afficher le mot
    end;
    Idéalement, il faudrait vérifier si S est réellement en Ansi avant de faire la conversion. Il y a une fonction pour cela, mais je ne m'en souviens plus.

    Cordialement
    Thierry

  7. #7
    Membre éprouvé
    Profil pro
    Développeur informatique
    Inscrit en
    Janvier 2010
    Messages
    469
    Détails du profil
    Informations personnelles :
    Âge : 66
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Janvier 2010
    Messages : 469
    Points : 1 100
    Points
    1 100
    Par défaut
    Citation Envoyé par ThWilliam
    j'ai aussi galéré (et ce n'est pas fini !)
    Même si "ça fonctionne" il est complètement inutile de traiter par octet une chaîne de caractère une fois convertie en UTF8.
    Une fois la conversion faite, les éléments de la chaîne sont des widechars, et ils peuvent et devraient être comparés à d'autres widechars ou chaîne.
    Si besoin on utilise les fonctions de l'unité LazUtf8 décrites dans le lien indiqué par PhB. Utf8Length au lieu de Length, par exemple.


    c'est quand même plus simple et plus lisible d'écrire :
    que :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if Byte(S[i]) = $C3) and (i < Length(S)) and Byte(S[i+1]) = $A9
    Le traitement par octet ne se justifie que dans la lecture telle que proposée initialement, avec un Stream, car on ne sait pas si on lit le 1er octet d'un caractère multiple, ou bien un caractère simple.
    C'est effectivement plus simple de lire le fichier avec une TStringList, si on fait l'hypothèse qu'aucune ligne ne fait plus de 2 Go, ce qui est quand même raisonnable.
    Et si on suppose que le fichier fait au total moins de 2Go (une fois converti), on peut même faire encore plus simple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Liste.LoadFromFile( utf8tosys(NomDeFichierSaisiEnUTF8) );
    Liste.Text := AnsiToUtf8( Liste.Text );
    Quand au codage du nom de fichier : TOpenDialog retourne de l'UTF8 comme tout contrôle visuel. Il faut appeler LoadFromFile avec utf8tosys(OpenDialog1.FileName) parce que LoadFromFile fait partie de de FPC (dont Lazarus dépend) qui ne s'occupe pas du jeu de caractères (il ne peut dépendre de l'unité LazUtf8 de Lazarus).
    Cordialement,
    Tintinux

    Initiateur de Gestinux, une comptabilité gestion open-source, pour Linux, Windows et Mac OS.
    Une version stable et une autre en développement, avec Lazarus : vous pouvez aider à la tester, la traduire et à la développer.

Discussions similaires

  1. Encore des problèmes avec le BDE
    Par Flint dans le forum C++Builder
    Réponses: 19
    Dernier message: 31/12/2007, 23h26
  2. Encore des problèmes de tailles de div liées
    Par gibet_b dans le forum Mise en page CSS
    Réponses: 14
    Dernier message: 04/07/2007, 08h46
  3. Réponses: 4
    Dernier message: 05/10/2006, 13h10
  4. Encore des problèmes de variables
    Par mat99 dans le forum Langage
    Réponses: 1
    Dernier message: 18/11/2005, 10h26
  5. Réponses: 8
    Dernier message: 10/08/2004, 11h49

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo