Forum Programmation.c++ renommage de fichiers en masse

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes : aucune
0
22
fév.
2022

Salut à tous,
ça fait un petit moment que je bute sur un problème qui m'avait semblé simple au départ mais que j'ai l'impression d'avoir sous-estimé.

Contexte

J'appends le c++ depuis un peu plus d'un an. Pour ca j'ai entrepris d'écrire une application dans le cadre de mon travail pour analyser des données venant d'un détecteur. Le logiciel fonctionne et il est utilisé par mes collègues dans le cadre du développement de notre projet.

Pour améliorer mes connaissances j'ai aussi entrepris de réécrire mes scripts (python, bash, perl) en c++. C'est peut être pas malin mais ça me fait un exercice…

Le premier que j'ai souhaité réécrire c'est un petit script qui s'appelle formatname dont l'objectif est de mettre les noms de fichiers d'un dossier ou d'un seul fichier sous une forme simple qui pourrait peut être se résumer par [a-z0-9_.]*

Le problème

En fait je bloque sur la partie caractères étendus. Toutes ces lettres accentuées qui font la richesse de nos langues mais qui font mon malheur. Dans mon script bash j'avais utilisé une fonction écrite par Christophe Blaess et que je reproduis ci-dessous

    #
    # author    Christophe Blaess
    # link      https://www.blaess.fr/christophe/livres/scripts-shell-linux-et-unix/
    #
    function latin_to_ascii {
        local result
        result=$(echo "$1" |
        sed -e 's/[ÀÁÂÃÄÅ]/A/g' |
        sed -e 's/Æ/AE/g'       |
        sed -e 's/Ç/C/g'        |
        sed -e 's/[ÈÉÊË]/E/g'   |
        sed -e 's/[ÌÍÎÏ]/I/g'   |
        sed -e 's/Ñ/N/g'        |
        sed -e 's/[ÒÓÔÕÖØ]/O/g' |
        sed -e 's/[ÙÚÛÜ]/U/g'   |
        sed -e 's/Ý/Y/g'        |
        sed -e 's/[àáâãä]/a/g'  |
        sed -e 's/æ/ae/g'       |
        sed -e 's/ç/c/g'        |
        sed -e 's/[èéêë]/e/g'   |
        sed -e 's/[ìíîï]/i/g'   |
        sed -e 's/ñ/n/g'        |
        sed -e 's/[òóôöø]/o/g'  |
        sed -e 's/[ùúûü]/u/g'   |
        sed -e 's/ý/y/g')

        echo "$result"
    }

J'ai essayé de faire pareil en c++ en utilisant les expressions régulières mais sans succès. Mes premières recherches semblent montrer que c'est un problèmes d'encodage. Si j'ai bien compris les caractères accentués sont stockés sur 2 octets et ca fout le bazar pour ce type de conversion.

    std::regex re;
    std::cout << "input string: " << s << std::endl;
    re = "[éèëěê]";
    s = std::regex_replace (s, re, "e",std::regex_constants::format_default);
    std::cout << "output string: " << s << std::endl;

On m'a aussi conseillé d'essayer libiconv mais j'y arrive pas non plus. Pourtant ça à l'air simple mais je dois pas être doué :)

#include <cctype>
#include <cstddef>
#include <cstring>
#include <filesystem>
#include <ios>
#include <iostream>
#include <iconv.h>
#include <filesystem>
#include <cstdio>

int main (int argc, char *argv[])
{

  if (argc > 1)
  {
    for (int i = 1; i < argc; ++i)
      {

      char* origin = argv[i];
      size_t ol = strlen (origin);
      char destination[100];
      size_t dl = strlen (destination);
      memset(destination, 0, 100);

      char * pin  = origin;
      char * pout = destination;

      iconv_t conv = iconv_open ("ASCII", "UTF-8");
      int ret = iconv(conv, &pin, &ol, &pout, &dl);
      iconv_close (conv);
      fprintf(stderr,"out: %s\n",destination);

      std::cout << "longeur origin: " << ol << std::endl;
      std::cout << "longeur destination: " << strlen(destination) << std::endl;

      std::cout << "origin: " << origin << std::endl;
      std::cout <<  "destination: " << destination << std::endl;

    }
  }
  else
    std::cout << "no argument provided" << std::endl;
  return 0;
}

En tout cas, merci d'avance pour votre aide.

Olivier

  • # Le code ?

    Posté par  . Évalué à 2.

    Salut,
    Tu devrais mettre le code tel que tu l’as écrit jusque là, on pourra peut-être t’aider à adapter, debugguer.

  • # hop

    Posté par  . Évalué à 7.

    moi j'utiliserai un truc dédié :

    boost::locale::normalize()

    ça demande une dépendance à boost, mais ça prévoie plus de cas que ce que tu peux imaginer ;)

    Il ne faut pas décorner les boeufs avant d'avoir semé le vent

  • # Alternative déjà dispo

    Posté par  . Évalué à 3. Dernière modification le 22 février 2022 à 18:02.

    Bonjour,

    Je ne sais pas si c'est un exercice ou si tu n'as pas trouvé ton bonheur ailleurs, mais tu peux jeter un coup d'oeil à detox, utilitaire en ligne de commande pour retirer les caractères et espaces qui peuvent poser problème dans les scripts en général ou dans les systèmes de fichiers, je l'utilise parfois sur des milliers de fichiers et ça fait le job ;)

    (rien à voir, mais ton site perso est cassé :/ )

    • [^] # Re: Alternative déjà dispo

      Posté par  (site web personnel, Mastodon) . Évalué à 2.

      C'est bien un exercice (ou alors la personne se fait plaisir dans son apprentissage de C++) …et il semble que ça fait appel à iconv qui bien aussi (et parfois mieux.)

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

  • # Retour d'erreur ?

    Posté par  . Évalué à 6. Dernière modification le 22 février 2022 à 22:53.

    Salut,
    Tu ne teste pas les retours de tes fonctions, c’est mal.

    man 3 iconv
    
    RETURN VALUE
    … -1 on error.
    

    Même chose pour iconv_open.
    Rajoute la gestion des erreurs, ça t’aidera à trouver ce qui cloche.

    • [^] # Re: Retour d'erreur ?

      Posté par  (site web personnel) . Évalué à 1. Dernière modification le 23 février 2022 à 09:29.

      oui, tu as raison… ce n'est pas bien. En fait comme j'utilise gdb parfois j'oublie de tester ces valeurs de retour pendant la phase de mise au point… Mais ce n'est peut être pas un bon comportement et il faut probablement l'inclure tout de suite.

      Les logiciels de traitement de texte sont à la rédaction ce que la 2CV est à l'automobile, une vieille voiture dont on se souvient avec nostalgie mais technologiquement dépassée

      • [^] # Re: Retour d'erreur ?

        Posté par  . Évalué à 4. Dernière modification le 23 février 2022 à 10:32.

        Quand j’ai testé le code chez moi hier soir, j’avais un retour d’erreur sur iconv. Après, c’est peut-être lié à mon terminal qui n’était peut-être pas en UTF8, mais ton programme doit toujours vérifier tous les codes de retour qui peuvent échouer.

        Quand je ne vérifie pas un code de retour volontairement, par exemple printf, je le cast :

        void exemple(char * str)
        {
            (void) printf("Str : %s\n",str);
        }

        Je sais que printf n’est pas c++, mais c’est l’exemple qui m’est venu immédiatement en tête. ;-)

  • # Erreur du débutant

    Posté par  . Évalué à 2. Dernière modification le 23 février 2022 à 08:29.

         char destination[100];
         size_t dl = strlen (destination);
    

    Il y a très peu de chance que dl contienne la taille du tableau.
    Il faudrait écrire cela:

         char destination[100];
         size_t dl = sizeof (destination);
    
    • [^] # Re: Erreur du débutant

      Posté par  (site web personnel) . Évalué à 1.

      oui, c'est vrai que j'ai des problèmes avec ce point. Merci pour le tuyau !

      Les logiciels de traitement de texte sont à la rédaction ce que la 2CV est à l'automobile, une vieille voiture dont on se souvient avec nostalgie mais technologiquement dépassée

  • # une solution ?

    Posté par  (site web personnel) . Évalué à 3.

    Bonjour à tous et merci beaucoup pour votre aide.
    Ci-dessous un début de solution qui intègrent vos conseils et qui semble fonctionner.
    La ligne qui change tout (pour moi) c'est std::setlocale(LC_ALL, "en_US.UTF-8");
    J'ai tester en local et sur un compilo en ligne dont je vous mets le lien ici.
    https://godbolt.org/z/4sW4eWjKe

        #include <cctype>
        #include <cstddef>
        #include <cstdio>
        #include <cstring>
        #include <filesystem>
        #include <iconv.h>
        #include <ios>
        #include <iostream>
    
        int main(int argc, char *argv[])
        {
          std::setlocale(LC_ALL, "en_US.UTF-8");
          if (argc > 1)
          {
            for (int i = 1; i < argc; ++i)
            {
              char *origin = argv[i];
              size_t ol    = strlen(origin);
              char destination[4 * ol];
              size_t dl = sizeof(destination);
              memset(destination, 0x00, dl);
    
              char *pin  = origin;
              char *pout = destination;
    
              iconv_t conv = iconv_open("ascii//TRANSLIT", "utf-8");
    
              if (conv == (iconv_t)-1)
              {
                if (errno == EINVAL)
                  std::cout << "The conversion is not supported by the implementation."
                            << std::endl;
                else
                  std::cout << "something got wrong..." << std::endl;
              }
    
              int ret = iconv(conv, &pin, &ol, &pout, &dl);
              if (iconv_close(conv) != 0)
                std::cerr << "error with iconv_close" << std::endl;;
    
              std::cout << "origin: " << origin << std::endl;
              std::cout << "destination: " << destination << std::endl;
            }
          }
          else
            std::cout << "no argument provided" << std::endl;
          return 0;
        }

    Les logiciels de traitement de texte sont à la rédaction ce que la 2CV est à l'automobile, une vieille voiture dont on se souvient avec nostalgie mais technologiquement dépassée

    • [^] # Re: une solution ?

      Posté par  . Évalué à 3.

      Tu as oublié de traiter le cas :

      int ret = iconv(conv, &pin, &ol, &pout, &dl);
      $ man 3 iconv
      ICONV(3)                                                                                                                       Linux Programmer's Manual                                                                                                                      ICONV(3)
      
      NAME
             iconv - perform character set conversion
      
      SYNOPSIS
             #include <iconv.h>
      
             size_t iconv(iconv_t cd,
                          char **inbuf, size_t *inbytesleft,
                          char **outbuf, size_t *outbytesleft);
      
      DESCRIPTION
             The  iconv()  function converts a sequence of characters in one character encoding to a sequence of characters in another character encoding.  The cd argument is a conversion descriptor, previously created by a call to iconv_open(3); the conversion descriptor defines the
             character encodings that iconv() uses for the conversion.  The inbuf argument is the address of a variable that points to the first character of the input sequence; inbytesleft indicates the number of bytes in that buffer.  The outbuf argument is the address of  a  vari‐
             able that points to the first byte available in the output buffer; outbytesleft indicates the number of bytes available in the output buffer.
      
             The  main  case  is  when inbuf is not NULL and *inbuf is not NULL.  In this case, the iconv() function converts the multibyte sequence starting at *inbuf to a multibyte sequence starting at *outbuf.  At most *inbytesleft bytes, starting at *inbuf, will be read.  At most
             *outbytesleft bytes, starting at *outbuf, will be written.
      
             The iconv() function converts one multibyte character at a time, and for each character conversion it increments *inbuf and decrements *inbytesleft by the number of converted input bytes, it increments *outbuf and decrements *outbytesleft by the number of converted  out‐
             put  bytes,  and  it  updates the conversion state contained in cd.  If the character encoding of the input is stateful, the iconv() function can also convert a sequence of input bytes to an update to the conversion state without producing any output bytes; such input is
             called a shift sequence.  The conversion can stop for four reasons:
      
             1. An invalid multibyte sequence is encountered in the input.  In this case, it sets errno to EILSEQ and returns (size_t) -1.  *inbuf is left pointing to the beginning of the invalid multibyte sequence.
      
             2. The input byte sequence has been entirely converted, that is, *inbytesleft has gone down to 0.  In this case, iconv() returns the number of nonreversible conversions performed during this call.
      
             3. An incomplete multibyte sequence is encountered in the input, and the input byte sequence terminates after it.  In this case, it sets errno to EINVAL and returns (size_t) -1.  *inbuf is left pointing to the beginning of the incomplete multibyte sequence.
      
             4. The output buffer has no more room for the next converted character.  In this case, it sets errno to E2BIG and returns (size_t) -1.
      
             A different case is when inbuf is NULL or *inbuf is NULL, but outbuf is not NULL and *outbuf is not NULL.  In this case, the iconv() function attempts to set cd's conversion state to the initial state and store a corresponding shift sequence at *outbuf.   At  most  *out‐
             bytesleft bytes, starting at *outbuf, will be written.  If the output buffer has no more room for this reset sequence, it sets errno to E2BIG and returns (size_t) -1.  Otherwise, it increments *outbuf and decrements *outbytesleft by the number of bytes written.
      
             A third case is when inbuf is NULL or *inbuf is NULL, and outbuf is NULL or *outbuf is NULL.  In this case, the iconv() function sets cd's conversion state to the initial state.
      
      RETURN VALUE
             The iconv() function returns the number of characters converted in a nonreversible way during this call; reversible conversions are not counted.  In case of error, it sets errno and returns (size_t) -1.
      
      ERRORS
             The following errors can occur, among others:
      
             E2BIG  There is not sufficient room at *outbuf.
      
             EILSEQ An invalid multibyte sequence has been encountered in the input.
      
             EINVAL An incomplete multibyte sequence has been encountered in the input.
      
      VERSIONS
             This function is available in glibc since version 2.1.
      
      ATTRIBUTES
             For an explanation of the terms used in this section, see attributes(7).
      
             ┌──────────┬───────────────┬─────────────────┐
             │Interface │ Attribute     │ Value           │
             ├──────────┼───────────────┼─────────────────┤
             │iconv()   │ Thread safety │ MT-Safe race:cd │
             └──────────┴───────────────┴─────────────────┘
             The iconv() function is MT-Safe, as long as callers arrange for mutual exclusion on the cd argument.
      
      CONFORMING TO
             POSIX.1-2001, POSIX.1-2008.
      
      NOTES
             In each series of calls to iconv(), the last should be one with inbuf or *inbuf equal to NULL, in order to flush out any partially converted input.
      
             Although  inbuf  and  outbuf are typed as char **, this does not mean that the objects they point can be interpreted as C strings or as arrays of characters: the interpretation of character byte sequences is handled internally by the conversion functions.  In some encod‐
             ings, a zero byte may be a valid part of a multibyte character.
      
             The caller of iconv() must ensure that the pointers passed to the function are suitable for accessing characters in the appropriate character set.  This includes ensuring correct alignment on platforms that have tight restrictions on alignment.
      
      SEE ALSO
             iconv_close(3), iconv_open(3), iconvconfig(8)
      
      COLOPHON
             This page is part of release 5.10 of the Linux man-pages project.  A description of the project, information about reporting bugs, and the latest version of this page, can be found at https://www.kernel.org/doc/man-pages/.
    • [^] # Re: une solution ?

      Posté par  . Évalué à 3.

      c'est plus proche du C que du C++ ça :D
      Mais bon vu que ça utilise une bibliothèque C…

      ensuite

                iconv_t conv = iconv_open("ascii//TRANSLIT", "utf-8");
      
                if (conv == (iconv_t)-1)
                {
                  if (errno == EINVAL)
                    std::cout << "The conversion is not supported by the implementation."
                              << std::endl;
                  else
                    std::cout << "something got wrong..." << std::endl;
                }

      dans la boucle for? j'ai comme un doute

      et toujours dans la même idée, on arrive pas à le faire; mais on fait quand même

        int ret = iconv(conv, &pin, &ol, &pout, &dl);

      j'ajouterai

      char *origin = argv[i];
      [...]
      char *pin  = origin;
      
      

      n'a pas d'intérêt pas de copie des chaines, juste des pointeurs, on se retrouve avec 3 variables différentes qui pointent au même endroit argv[i], origin, pin…

      Il ne faut pas décorner les boeufs avant d'avoir semé le vent

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.