Получение std: ifstream для обработки LF, CR и CRLF?


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

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

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

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

здесь три типа линии концовки видели в текстовых файлах: '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.
}

Edit спасибо Нилу за указание на это f.good() не то, что я хотел. !f.fail() то, что я хочу.

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

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

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

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

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

5 ответов:

Как отметил Нил, " среда выполнения 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;
}

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

#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;
}

Вы читаете файл в BINARY или текст режим? В текст режим пары возврат каретки/перевод строки, CRLF трактуется как текст - конец строки или конец строки символ, но в BINARY вы получаете только один байт за раз, что означает, что либо символ должны игнорируется и оставляется в буфере для извлечения в качестве другого байта! Возврат каретки означает, в пишущая машинка, то есть машина пишущей машинки, в которой находится печатающий рычаг, достигла правого края бумаги и возвращается к левому краю. Это очень механическая модель, механическая пишущая машинка. Затем подача строки означает, что рулон бумаги поворачивается немного вверх, так что бумага находится в положении, чтобы начать другую строку ввода. Как fas, насколько я помню, одна из низких цифр в ASCII означает переход к правому символу без ввода, мертвый символ и, конечно же, \b означает backspace: переместите автомобиль на один символ назад. Таким образом, вы можете добавлять специальные эффекты, такие как базовый (тип подчеркивания), зачеркивание (тип минус), приблизительные различные акценты, отмена (тип 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 в текст режим на самом деле пара байт 0x0d 0x0a, 0x0d, но и просто \r: \n включает \r в текст режиме, но не в BINARY, поэтому \n и \r\n эквивалентны... или должен быть. Это очень базовая отраслевая путаница на самом деле, типичная отраслевая инерция, поскольку конвенция должна говорить о CRLF, во всех платформах, то попадают в различные бинарные интерпретации. Строго говоря, файлы, в том числе только 0x0d (возврат каретки) как \n (CRLF или подача строки), искажены в текст режим (пишущая машинка машина: просто верните автомобиль и зачеркните все...), и являются нелинейно ориентированным двоичным форматом (либо \r, либо \r\n, ориентированным на строку), поэтому вы не должны читать как текст! Код должен потерпеть неудачу, возможно, с некоторым сообщением пользователя. От этого не зависит только на ОС,но и на реализации библиотеки C, добавляя к путанице и возможным вариациям... (особенно для прозрачных слоев перевода UNICODE, добавляющих еще одну точку артикуляции для запутанных вариаций).

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

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

кроме написания собственного пользовательского обработчика или использования внешней библиотеки, вам не повезло. Самое простое, что нужно сделать, это проверить, чтобы убедиться 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'.

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