96 votes

Obtenir de std : : ifstream qu'il gère LF, CR et CRLF ?

Plus précisément, je suis intéressé par istream& getline ( istream& is, string& str ); . Existe-t-il une option dans le constructeur d'ifstream pour lui demander de convertir tous les encodages de nouvelles lignes en ' \n sous le capot ? Je veux pouvoir appeler getline et qu'il gère gracieusement toutes les fins de ligne.

Mise à jour : Pour clarifier, je veux être capable d'écrire du code qui compile presque n'importe où, et qui prend des données de presque n'importe où. Y compris les rares fichiers qui ont ' \r ' sans ' \n '. Minimiser les inconvénients pour les utilisateurs du logiciel.

Il est facile de contourner le problème, mais je suis toujours curieux de savoir quelle est la bonne façon, dans la norme, de gérer de manière flexible tous les formats de fichiers texte.

getline se lit sur une ligne complète, jusqu'à un '. \n dans une chaîne de caractères. La chaîne ' \n ' est consommé dans le flux, mais getline ne l'inclut pas dans la chaîne. Tout va bien jusqu'à présent, mais il se peut qu'il y ait un ' \r ' juste avant le ' \n qui est incluse dans la chaîne.

Il y a trois types de fin de ligne dans les fichiers texte : ' \n ' est la terminaison conventionnelle sur les machines Unix, ' \r était (je crois) utilisé sur les anciens systèmes d'exploitation Mac, et Windows utilise une paire, ' \r ' suivi de ' \n '.

Le problème est que getline laisse l'option ' \r ' à la fin de la chaîne.

ifstream f("a_text_file_of_unknown_origin");
string line;
getline(f, line);
if(!f.fail()) { // a non-empty line was read
   // BUT, there might be an '\r' at the end now.
}

Editer Merci à Neil de l'avoir signalé f.good() n'est pas ce que je voulais. !f.fail() c'est ce que je veux.

Je peux les supprimer manuellement (voir l'édition de cette question), ce qui est facile pour les fichiers texte de Windows. Mais je crains que quelqu'un n'introduise un fichier contenant uniquement des ' \r '. Dans ce cas, je suppose que getline consommera tout le fichier, pensant qu'il s'agit d'une seule ligne !

et c'est sans compter l'Unicode :-)

peut-être que Boost a une bonne façon de consommer une ligne à la fois à partir de n'importe quel type de fichier texte ?

Editer Je l'utilise pour gérer les fichiers Windows, mais j'ai toujours l'impression que je ne devrais pas avoir à le faire ! Et ce n'est pas le cas pour les fichiers ' \r Fichiers 'uniquement'.

if(!line.empty() && *line.rbegin() == '\r') {
    line.erase( line.length()-1, 1);
}

2 votes

\n signifie nouvelle ligne, quelle que soit la manière dont elle est présentée dans le système d'exploitation actuel. La bibliothèque s'en charge. Mais pour que cela fonctionne, un programme compilé sous Windows doit lire les fichiers texte de Windows, un programme compilé sous Unix, les fichiers texte d'Unix, etc.

1 votes

@George, même si je compile sur une machine Linux, j'utilise parfois des fichiers texte provenant d'une machine Windows. Je vais peut-être publier mon logiciel (un petit outil d'analyse de réseau), et je veux pouvoir dire aux utilisateurs qu'ils peuvent fournir presque n'importe quel type de fichier texte (de type ASCII).

0voto

Martin Thümmel Points 21

Si l'on connaît le nombre d'éléments/de chiffres de chaque ligne, on peut lire une ligne comportant par exemple 4 chiffres comme suit

string num;
is >> num >> num >> num >> num;

Cela fonctionne également avec d'autres terminaisons de ligne.

0voto

Gergely Nagy Points 33

Malheureusement, la solution acceptée ne se comporte pas exactement comme std::getline() . Pour obtenir ce comportement (d'après mes tests), la modification suivante est nécessaire :

std::istream& safeGetline(std::istream& is, std::string& t)
{
    t.clear();

    // The characters in the stream are read one-by-one using a std::streambuf.
    // That is faster than reading them one-by-one using the std::istream.
    // Code that uses streambuf this way must be guarded by a sentry object.
    // The sentry object performs various tasks,
    // such as thread synchronization and updating the stream state.

    std::istream::sentry se(is, true);
    std::streambuf* sb = is.rdbuf();

    for(;;) {
        int c = sb->sbumpc();
        switch (c) {
        case '\n':
            return is;
        case '\r':
            if(sb->sgetc() == '\n')
                sb->sbumpc();
            return is;
        case std::streambuf::traits_type::eof():
            is.setstate(std::ios::eofbit);       //
            if(t.empty())                        // <== change here
                is.setstate(std::ios::failbit);  // 
            return is;
        default:
            t += (char)c;
        }
    }
}

Según https://en.cppreference.com/w/cpp/string/basic_string/getline :

  1. Extrait les caractères de l'entrée et les ajoute à str jusqu'à ce que l'une des situations suivantes se produise (vérifiée dans l'ordre indiqué)

    1. condition de fin de fichier à l'entrée, auquel cas, getline met eofbit.
    2. le prochain caractère d'entrée disponible est delim, comme testé par Traits::eq(c, delim), auquel cas le caractère de délimitation est extrait de l'entrée, mais n'est pas ajouté à str.
    3. str.max_size() caractères ont été stockés, dans ce cas getline met failbit et retourne.
  2. Si aucun caractère n'a été extrait pour une raison quelconque (pas même le délimiteur rejeté), getline définit le bit de défaillance et les retours.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X