Получение std :: ifstream для обработки LF, CR и CRLF? - PullRequest
77 голосов
/ 22 мая 2011

Конкретно меня интересует istream& getline ( istream& is, string& str );. Есть ли возможность для конструктора ifstream сказать ему, чтобы он конвертировал все новые строки в '\ n' под капотом? Я хочу, чтобы можно было звонить getline и иметь возможность корректно обрабатывать все окончания строк.

Обновление : Чтобы уточнить, я хочу иметь возможность писать код, который компилируется практически где угодно и будет принимать данные практически из любого места. Включая редкие файлы, которые имеют «\ r» без «\ n». Минимизация неудобств для любых пользователей программного обеспечения.

Обойти эту проблему легко, но мне все еще интересно, как в стандарте гибко обрабатывать все форматы текстовых файлов.

getline читает всю строку, вплоть до '\ n', в строку. '\ N' используется из потока, но getline не включает его в строку. Пока все хорошо, но перед строкой '\ n', которая может быть включена в строку, может быть '\ r'.

В текстовых файлах можно увидеть три типа окончаний строк : «\ n» - это обычное окончание на машинах Unix, «\ r» использовался (я думаю) в старых операционных системах Mac, а в Windows используется пара «\ r», следующая за «\ n».

Проблема в том, что getline оставляет '\ r' в конце строки.

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.
}

Редактировать Спасибо Нейлу за то, что он указал, что f.good() не то, что я хотел. !f.fail() это то, что я хочу.

Я могу удалить его вручную (см. Редактирование этого вопроса), что легко для текстовых файлов Windows. Но я обеспокоен тем, что кто-то добавит файл, содержащий только '\ r'. В этом случае я предполагаю, что getline будет использовать весь файл, думая, что это одна строка!

.. и это даже не учитывая Unicode: -)

.. может быть, в Boost есть хороший способ использовать по одной строке из любого типа текстового файла?

Редактировать Я использую это для обработки файлов Windows, но я все еще чувствую, что не должен был! И это не будет разветвляться только для файлов \ r '.

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

Ответы [ 5 ]

104 голосов
/ 22 мая 2011

Как отметил Нил, «среда выполнения C ++ должна правильно работать с любым соглашением о конце строки для вашей конкретной платформы».

Однако люди перемещают текстовые файлы между различными платформами, так что это не хорошодовольно.Вот функция, которая обрабатывает все три конца строки ("\ r", "\ n" и "\ r \ n"):

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():
            // Also handle the case when the last line has no line ending
            if(t.empty())
                is.setstate(std::ios::eofbit);
            return is;
        default:
            t += (char)c;
        }
    }
}

А вот тестовая программа:

int main()
{
    std::string path = ...  // insert path to test file here

    std::ifstream ifs(path.c_str());
    if(!ifs) {
        std::cout << "Failed to open the file." << std::endl;
        return EXIT_FAILURE;
    }

    int n = 0;
    std::string t;
    while(!safeGetline(ifs, t).eof())
        ++n;
    std::cout << "The file contains " << n << " lines." << std::endl;
    return EXIT_SUCCESS;
}
10 голосов
/ 22 мая 2011

Среда выполнения C ++ должна корректно работать с любым соглашением о конечной линии для вашей конкретной платформы. В частности, этот код должен работать на всех платформах:

#include <string>
#include <iostream>
using namespace std;

int main() {
    string line;
    while( getline( cin, line ) ) {
        cout << line << endl;
    }
}

Конечно, если вы имеете дело с файлами с другой платформы, все ставки сняты.

Поскольку две наиболее распространенные платформы (Linux и Windows) оканчивают строки символом новой строки, а Windows предшествует возврату каретки, вы можете проверить последний символ строки line в приведенном выше коде, чтобы увидеть если это \r, и если это так, удалите его, прежде чем выполнять обработку для вашего приложения.

Например, вы можете предоставить себе функцию стиля getline, которая выглядит примерно так (не проверено, использование индексов, substr и т. Д. Только для педагогических целей):

ostream & safegetline( ostream & os, string & line ) {
    string myline;
    if ( getline( os, myline ) ) {
       if ( myline.size() && myline[myline.size()-1] == '\r' ) {
           line = myline.substr( 0, myline.size() - 1 );
       }
       else {
           line = myline;
       }
    }
    return os;
}
7 голосов
/ 28 июня 2012

Вы читаете файл в режиме BINARY или в режиме TEXT ? В режиме TEXT возврат парной каретки / перевод строки CRLF интерпретируется как TEXT конец строки или символ конца строки, но в ДВОЙНОЙ вы выбираете только ОДИН байт за раз, что означает, что любой символ ДОЛЖЕН игнорироваться и оставляться в буфере для выборки в качестве другого байта! Возврат каретки означает, что в пишущей машинке машина печатной машинки, в которой лежит печатный рычаг, достигла правого края бумаги и возвращается к левому краю. Это очень механическая модель механической пишущей машинки. Затем перевод строки означает, что рулон бумаги немного повернут вверх, чтобы бумага смогла начать другую строку для печати. Насколько я помню, одна из младших цифр в ASCII означает перемещение на один символ вправо без ввода, мертвый символ и, конечно, \ b означает возврат на одну позицию: переместите машину на один символ назад. Таким образом, вы можете добавлять специальные эффекты, такие как базовый (подчеркивание типа), зачеркивание (печатать минус), приближать различные акценты, отменять (печатать X), не нуждаясь в расширенной клавиатуре, просто регулируя положение автомобиля вдоль линии перед ввод строки. Таким образом, вы можете использовать байтовое напряжение ASCII для автоматического управления пишущей машинкой без компьютера между ними. Когда введена автоматическая пишущая машинка, АВТОМАТИЧЕСКИЙ означает, что как только вы достигнете самого дальнего края бумаги, машина возвращается влево И перевод строки, то есть машина предполагается, что возвращается автоматически при движении вверх! Таким образом, вам не нужны оба управляющих символа, только один, \ n, новая строка или перевод строки.

Это не имеет ничего общего с программированием, но ASCII старше и ЭЙ! похоже, что некоторые люди не думали, когда они начали делать текстовые вещи! Платформа UNIX предполагает электрический автоматический тип машины; модель Windows является более полной и позволяет управлять механическими машинами, хотя некоторые управляющие символы становятся все менее и менее полезными в компьютерах, например, символ звонка, 0x07, если я хорошо помню ... Некоторые забытые тексты должны быть первоначально захвачены управляющими символами для пишущих машинок с электрическим управлением, и это увековечило модель ...

На самом деле правильным вариантом было бы просто включить \ r, перевод строки, возврат каретки не нужен, то есть автоматический, следовательно:

char c;
ifstream is;
is.open("",ios::binary);
...
is.getline(buffer, bufsize, '\r');

//ignore following \n or restore the buffer data
if ((c=is.get())!='\n') is.rdbuf()->sputbackc(c);
...

будет наиболее правильным способом обработки файлов всех типов. Однако обратите внимание, что \ n в режиме TEXT на самом деле является байтовой парой 0x0d 0x0a, но 0x0d IS просто \ r: \ n включает \ r в режиме TEXT , но не в BINARY , поэтому \ n и \ r \ n эквивалентны ... или должны быть. На самом деле это очень простая отраслевая путаница, типичная отраслевая инерция, поскольку принято говорить о CRLF на ВСЕХ платформах, а затем делиться на разные двоичные интерпретации. Строго говоря, файлы, включающие ONLY 0x0d (возврат каретки) как \ n (CRLF или перевод строки), искажены в режиме TEXT (печатная машинка: просто верните машину и зачеркните все ...) и являются нелинейным ориентированным двоичным форматом (\ r или \ r \ n, означающим ориентированный на строки), поэтому вы не должны читать как текст! Код должен потерпеть неудачу, возможно, с каким-то сообщением пользователя. Это зависит не только от ОС, но и от реализации библиотеки C, что добавляет путаницу и возможные вариации ... (особенно для прозрачных слоев перевода UNICODE, добавляя еще одну точку артикуляции для путаницы вариаций).

Проблема с предыдущим фрагментом кода (механическая пишущая машинка) заключается в том, что он очень неэффективен, если после \ r нет символов \ n (текст автоматической пишущей машинки).Затем он также принимает режим BINARY , в котором библиотека C вынуждена игнорировать интерпретации текста (локаль) и выдавать явные байты.Не должно быть никакой разницы в фактических текстовых символах между обоими режимами, только в контрольных символах, поэтому, вообще говоря, чтение BINARY лучше, чем режим TEXT .Это решение эффективно для типичных текстовых файлов ОС Windows в режиме BINARY независимо от вариаций библиотеки C и неэффективно для других текстовых форматов платформы (включая веб-переводы в текст).Если вы заботитесь об эффективности, вы можете использовать указатель на функцию, выполнить тест для \ r vs \ r \ n линейных элементов управления так, как вам нравится, затем выбрать лучший код пользователя getline в указателе и вызвать его изЭто.

Между прочим, я помню, что нашел некоторые текстовые файлы \ r \ r \ n ... которые переводятся в двухстрочный текст, как это все еще требуется некоторым потребителям печатного текста.

1 голос
/ 08 марта 2016

Одним из решений было бы сначала найти и заменить все окончания строки на '\ n' - как, например, Git делает по умолчанию.

1 голос
/ 22 мая 2011

Кроме написания вашего собственного обработчика или использования внешней библиотеки, вам не повезло. Самое простое, что нужно сделать, это проверить, чтобы убедиться, что line[line.length() - 1] не '\ r'. В Linux это излишне, так как большинство строк заканчиваются на '\ n', что означает, что вы потеряете немало времени, если это будет в цикле. На Windows это тоже лишнее. Однако как насчет классических файлов Mac, которые заканчиваются на '\ r'? std :: getline не будет работать для этих файлов в Linux или Windows, потому что '\ n' и '\ r' '\ n' оба заканчиваются на '\ n', избавляя от необходимости проверять наличие '\ r'. Очевидно, что такая задача, которая работает с этими файлами, не будет работать хорошо. Конечно, существуют многочисленные системы EBCDIC, с которыми большинство библиотек не посмеют заняться.

Проверка '\ r', вероятно, является лучшим решением вашей проблемы. Чтение в двоичном режиме позволит вам проверить все три общих конца строки ('\ r', '\ r \ n' и '\ n'). Если вы заботитесь только о Linux и Windows, так как окончания строк в старом стиле Mac не должны существовать намного дольше, проверьте только «\ n» и удалите завершающий символ «\ r».

...