Получение 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.
}

Изменить Благодаря Нилу, указав, что f.good() не то, что я хотел. !f.fail() - это то, что я хочу.

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

.. и что даже не рассматривая Unicode: -)

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

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

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

Ответ 1

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

Однако люди перемещают текстовые файлы между различными платформами, поэтому это недостаточно. Вот функция, которая обрабатывает все три конца строки ( "\ 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;
}

Ответ 2

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

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

Ответ 3

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

Это не имеет никакого отношения к программированию, но ASCII старше и HEY! похоже, что некоторые люди не думали, когда они начали делать текстовые вещи! Платформа UNIX предполагает автоматическую автоматическую typemachine; модель 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 IS просто \r:\n включает\r в режиме ТЕКСТ, но не в BINARY, поэтому \n и\r\n эквивалентны... или должно быть. Это очень пустая отраслевая путаница, типичная инерция промышленности, поскольку конвенция должна говорить о CRLF на всех платформах, а затем попадать в разные бинарные интерпретации. Строго говоря, файлы, включающие ТОЛЬКО 0x0d (возврат каретки) как \n (CRLF или фид), искажены в режиме TEXT (машинка для пишущей машинки: просто верните автомобиль и зачеркните все...), и это нестрочная ориентированная двоичный формат (или\r или \r\n, что означает ориентацию по линии), поэтому вы не должны читать как текст! Код должен быть неудачным, может быть, с некоторым сообщением пользователя. Это не зависит только от ОС, но также от реализации библиотеки C, добавляя к путанице и возможным вариациям... (особенно для прозрачных слоев перевода UNICODE, добавляющих еще одну точку артикуляции для путающих вариаций).

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

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

Ответ 4

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

Ответ 5

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