Obtendo std :: ifstream para lidar com LF, CR e CRLF?

Especificamente, estou interessado em istream& getline ( istream& is, string& str ); . Existe uma opção para o construtor ifstream dizer a ele para converter todas as codificações de nova linha para ‘\ n’ sob o capô? Eu quero ser capaz de chamar getline e fazer com que ele manipule todos os finais de linha.

Atualização : Para esclarecer, eu quero ser capaz de escrever código que compila praticamente em qualquer lugar, e receberei informações de praticamente qualquer lugar. Incluindo os arquivos raros que possuem ‘\ r’ sem ‘\ n’. Minimizando a inconveniência para qualquer usuário do software.

É fácil solucionar o problema, mas ainda estou curioso para saber o caminho certo, no padrão, para lidar de maneira flexível com todos os formatos de arquivo de texto.

getline lê em uma linha completa, até um ‘\ n’, em uma string. O ‘\ n’ é consumido pelo stream, mas o getline não o inclui na string. Até agora, tudo bem, mas pode haver um ‘\ r’ logo antes do ‘\ n’ que é incluído na string.

Existem três tipos de finais de linha vistos em arquivos de texto: ‘\ n’ é o final convencional em máquinas Unix, ‘\ r’ foi (eu acho) usado em sistemas operacionais Mac antigos e o Windows usa um par ‘\ r’ seguindo por ‘\ n’.

O problema é que getline deixa o ‘\ r’ no final da string.

 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 Obrigado ao Neil por apontar que f.good() não é o que eu queria. !f.fail() é o que eu quero.

Eu mesmo posso removê-lo manualmente (veja a edição desta pergunta), que é fácil para os arquivos de texto do Windows. Mas estou preocupado que alguém se alimente em um arquivo contendo apenas ‘\ r’. Nesse caso, presumo que o getline consuma o arquivo inteiro, pensando que é uma linha única!

.. e isso não é mesmo considerando Unicode 🙂

.. talvez Boost tem uma maneira agradável de consumir uma linha de cada vez a partir de qualquer tipo de arquivo de texto?

Edit Estou usando isso, para lidar com os arquivos do Windows, mas ainda sinto que não deveria precisar! E isso não vai bifurcar para os arquivos ‘\ r’.

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

   

Como Neil apontou, “o tempo de execução do C ++ deve lidar corretamente com qualquer que seja a convenção de término de linha para sua plataforma específica”.

No entanto, as pessoas movem arquivos de texto entre diferentes plataformas, o que não é bom o suficiente. Aqui está uma function que lida com todos os três finais de linha (“\ r”, “\ n” e “\ 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; } } } 

E aqui está um programa de teste:

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

O tempo de execução do C ++ deve lidar corretamente com qualquer convenção de linha final para sua plataforma específica. Especificamente, esse código deve funcionar em todas as plataformas:

 #include  #include  using namespace std; int main() { string line; while( getline( cin, line ) ) { cout < < line << endl; } } 

Claro, se você está lidando com arquivos de outra plataforma, todas as apostas estão desativadas.

Como as duas plataformas mais comuns (Linux e Windows) ambos terminam linhas com um caractere de nova linha, com o Windows precedendo-o com um retorno de carro, você pode examinar o último caractere da cadeia de line no código acima para ver se ele é e, se for o caso, remova-o antes de fazer o processamento específico do aplicativo.

Por exemplo, você poderia fornecer uma function de estilo getline parecida com essa (não testada, uso de índices, substr etc., apenas para fins pedagógicos):

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

Você está lendo o arquivo no modo BINARY ou no modo TEXT ? No modo TEXTO , o par de retorno de linha / alimentação de linha, CRLF , é interpretado como TEXTO no final da linha ou no final do caractere de linha, mas em BINARY você busca somente UM byte de cada vez, o que significa que qualquer caractere DEVE ser ignorado e deixado em o buffer a ser buscado como outro byte! Retorno de carro significa, na máquina de escrever, que o carro de máquina de escrever, onde está o arm de impressão, atingiu a borda direita do papel e é retornado para a borda esquerda. Este é um modelo muito mecânico, o da máquina de escrever mecânica. Em seguida, o avanço de linha significa que o rolo de papel é girado um pouco para cima, de forma que o papel esteja em posição de iniciar outra linha de digitação. Até onde eu me lembro, um dos dígitos baixos em ASCII significa mover para a direita um caractere sem digitar, o caractere morto e, claro, \ b significa backspace: mova o caractere de um carro para trás. Dessa forma você pode adicionar efeitos especiais, como subjacente (tipo underscore), tachado (tipo menos), aproximar diferentes acentos, cancelar (tipo X), sem precisar de um teclado estendido, apenas ajustando a posição do carro ao longo da linha antes entrando no feed de linha. Assim, você pode usar tensões ASCII de tamanho de bytes para controlar automaticamente uma máquina de escrever sem um computador no meio. Quando a máquina de escrever automática é introduzida, AUTOMATICO significa que, uma vez que você alcança a extremidade mais distante do papel, o carro é retornado para a esquerda E o avanço de linha é aplicado, ou seja, o carro é retornado automaticamente quando o rolo sobe! Portanto, você não precisa dos dois caracteres de controle, apenas um, o \ n, nova linha ou feed de linha.

Isso não tem nada a ver com programação, mas o ASCII é mais antigo e HEY! Parece que algumas pessoas não estavam pensando quando começaram a fazer coisas de texto! A plataforma UNIX assume uma máquina automática elétrica; o modelo do Windows é mais completo e permite o controle de máquinas mecânicas, embora alguns caracteres de controle se tornem cada vez menos úteis em computadores, como o caractere de campainha, 0x07 se bem me lembro … Alguns textos esquecidos devem ter sido capturados originalmente com caracteres de controle para máquinas de escrever controladas eletricamente e perpetuou o modelo …

Na verdade, a variação correta seria apenas include o \ r, avanço de linha, sendo desnecessário o retorno de carro, isto é, automático, portanto:

 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); ... 

seria a maneira mais correta de lidar com todos os tipos de arquivos. Note, entretanto, que \ n no modo TEXT é na verdade o par de bytes 0x0d 0x0a, mas 0x0d é apenas \ r: \ n inclui \ r no modo TEXT mas não no BINARY , então \ n e \ r \ n são equivalentes … ou deveria estar. Essa é uma confusão muito básica na indústria, a inércia típica da indústria, já que a convenção é falar de CRLF, em TODAS as plataformas, e então cair em diferentes interpretações binárias. Estritamente falando, arquivos como APENAS 0x0d (retorno de carro) como sendo \ n (CRLF ou alimentação de linha), são malformados no modo TEXTO (máquina typewritter: apenas devolva o carro e riscam tudo …), e são não orientados a linha formato binário (ou \ r ou \ r \ n significando linha orientada), então você não deve ler como texto! O código deve falhar talvez com alguma mensagem do usuário. Isso não depende somente do sistema operacional, mas também da implementação da biblioteca C, aumentando a confusão e possíveis variações … (particularmente para camadas de tradução UNICODE transparentes, adicionando outro ponto de articulação para confundir variações).

O problema com o trecho de código anterior (máquina de escrever mecânica) é que ele é muito ineficiente se não houver \ n caracteres após \ r (texto de máquina de escrever automática). Em seguida, ele também assume o modo BINARY , no qual a biblioteca C é forçada a ignorar interpretações de texto (localidade) e distribuir os bytes. Não deve haver diferença nos caracteres de texto reais entre os dois modos, apenas nos caracteres de controle, de modo geral, ler BINARY é melhor do que o modo TEXT . Essa solução é eficiente para arquivos de texto típicos do sistema operacional Windows em modo BINARY , independentemente das variações da biblioteca C, e ineficiente para outros formatos de texto de plataforma (incluindo traduções da Web em texto). Se você se preocupa com eficiência, o caminho a percorrer é usar um ponteiro de function, fazer um teste para \ r vs \ r \ n controles de linha da maneira que desejar, então selecionar o melhor código de usuário getline no ponteiro e invocá-lo isto.

Aliás, lembro que também encontrei alguns \ r \ r \ n arquivos de texto … o que se traduz em texto de linha dupla, como ainda é exigido por alguns consumidores de texto impresso.

Além de escrever seu próprio manipulador personalizado ou usar uma biblioteca externa, você está sem sorte. O mais fácil é verificar se a line[line.length() - 1] não é ‘\ r’. No Linux, isso é supérfluo, já que a maioria das linhas terminará com ‘\ n’, o que significa que você perderá um bom tempo se estiver em um loop. No Windows, isso também é supérfluo. No entanto, e os arquivos clássicos do Mac que terminam em ‘\ r’? O std :: getline não funcionaria para esses arquivos no Linux ou Windows porque ‘\ n’ e ‘\ r’ ‘\ n’ ambos terminam em ‘\ n’, eliminando a necessidade de verificar ‘\ r’. Obviamente, tal tarefa que funciona com esses arquivos não funcionaria bem. É claro, então existem os numerosos sistemas EBCDIC, algo que a maioria das bibliotecas não ousa abordar.

Verificar ‘\ r’ é provavelmente a melhor solução para o seu problema. A leitura no modo binário permitiria verificar todos os três terminais de linhas comuns (‘\ r’, ‘\ r \ n’ e ‘\ n’). Se você se importa apenas com o Linux e o Windows como as antigas terminações de linha do Mac não devem estar por aí por muito tempo, verifique apenas ‘\ n’ e remova o caractere ‘\ r’ à direita.

Uma solução seria pesquisar primeiro e replace todos os finais de linha por ‘\ n’ – assim como, por exemplo, o Git faz por padrão.