Obteniendo std :: ifstream para manejar LF, CR y CRLF?

Específicamente, estoy interesado en istream& getline ( istream& is, string& str ); . ¿Hay alguna opción para que el constructor de ifstream le diga que convierta todas las codificaciones de nueva línea a ‘\ n’ bajo el capó? Quiero poder llamar a getline y hacer que maneje con gracia todas las terminaciones de línea.

Actualización : para aclarar, quiero ser capaz de escribir código que comstack casi en cualquier lugar, y tomará la entrada desde casi cualquier lugar. Incluyendo los raros archivos que tienen ‘\ r’ sin ‘\ n’. Minimizando inconvenientes para cualquier usuario del software.

Es fácil solucionar el problema, pero todavía tengo curiosidad sobre la forma correcta, en el estándar, de manejar de manera flexible todos los formatos de archivo de texto.

getline lee en una línea completa, hasta una ‘\ n’, en una cadena. El ‘\ n’ se consume de la secuencia, pero getline no lo incluye en la cadena. Eso está bien hasta ahora, pero podría haber un ‘\ r’ justo antes de la ‘\ n’ que se incluye en la cadena.

Hay tres tipos de finales de línea en los archivos de texto: ‘\ n’ es la terminación convencional en máquinas Unix, ‘\ r’ fue (creo) usado en sistemas operativos antiguos de Mac, y Windows usa un par, ‘\ r’ siguiendo por ‘\ n’.

El problema es que getline deja el ‘\ r’ al final de la cadena.

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

Editar Gracias a Neil por señalar que f.good() no es lo que quería. !f.fail() es lo que quiero.

Puedo eliminarlo manualmente (ver edición de esta pregunta), que es fácil para los archivos de texto de Windows. Pero me preocupa que alguien alimente un archivo que contenga solo ‘\ r’. En ese caso, supongo que Getline consumirá todo el archivo, ¡pensando que es una sola línea!

.. y eso ni siquiera está considerando Unicode 🙂

.. tal vez Boost tiene una buena manera de consumir una línea a la vez de cualquier tipo de archivo de texto?

Editar Estoy usando esto para manejar los archivos de Windows, ¡pero todavía siento que no debería hacerlo! Y esto no funcionará con los archivos ‘solo’.

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

Como señaló Neil, “el tiempo de ejecución de C ++ debe tratar correctamente con cualquier convención de final de línea para su plataforma en particular”.

Sin embargo, las personas mueven archivos de texto entre diferentes plataformas, por lo que no es lo suficientemente bueno. Aquí hay una función que maneja las tres terminaciones de línea (“\ r”, “\ n” y “\ 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; } } } 

Y aquí hay un progtwig de prueba:

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

El tiempo de ejecución C ++ debe tratar correctamente con cualquier convención de línea final para su plataforma particular. Específicamente, este código debería funcionar en todas las plataformas:

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

Por supuesto, si está tratando con archivos de otra plataforma, todas las apuestas están desactivadas.

Como las dos plataformas más comunes (Linux y Windows) terminan líneas con un carácter de nueva línea, con Windows precediéndola con un retorno de carro, puede examinar el último carácter de la line de line en el código anterior para ver si es y si es así eliminarlo antes de hacer su procesamiento específico de la aplicación.

Por ejemplo, podría proporcionarse una función de estilo getline similar a esto (no probada, uso de índices, substr, etc., con fines pedagógicos solamente):

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

¿Estás leyendo el archivo en BINARIO o en modo TEXTO ? En el modo TEXTO , el par retorno de carro / avance de línea, CRLF , se interpreta como TEXTO fin de línea o fin de línea, pero en BINARIO se obtiene solo UN byte a la vez, lo que significa que cualquier carácter DEBE ignorarse y dejarse en el búfer que se va a buscar como otro byte! Retorno de carro significa, en la máquina de escribir, que el automóvil de la máquina de escribir, donde se encuentra el arm de impresión, ha llegado al borde derecho del papel y se regresa al borde izquierdo. Este es un modelo muy mecánico, el de la máquina de escribir mecánica. Luego, la alimentación de línea significa que el rollo de papel se gira un poco hacia arriba para que el papel esté en posición para comenzar otra línea de tipeo. Tan pronto como recuerdo uno de los dígitos bajos en ASCII significa mover al personaje correcto sin teclear, el caracter muerto, y por supuesto \ b significa retroceder: mover el coche un caracter hacia atrás. De esta forma puede agregar efectos especiales, como subyacente (escribir guion bajo), tachado (escribir menos), aproximar diferentes acentos, cancelar (tipo X), sin necesidad de un teclado extendido, simplemente ajustando la posición del automóvil a lo largo de la línea antes ingresando el avance de línea. De modo que puede usar voltajes ASCII de tamaño byte para controlar automáticamente una máquina de escribir sin una computadora en el medio. Cuando se introduce la máquina de escribir automática, AUTOMÁTICO significa que una vez que alcanzas el borde más alejado del papel, el coche vuelve a la izquierda Y se aplica la alimentación de línea, es decir, se supone que el automóvil se devuelve automáticamente a medida que el rollo sube. Por lo tanto, no necesita ambos caracteres de control, solo uno, \ n, nueva línea o avance de línea.

Esto no tiene nada que ver con la progtwigción, ¡pero ASCII es más viejo y HEY! parece que algunas personas no estaban pensando cuando comenzaron a hacer cosas de texto! La plataforma UNIX supone una máquina de escribir eléctrica automática; el modelo de Windows es más completo y permite el control de máquinas mecánicas, aunque algunos caracteres de control se vuelven cada vez menos útiles en computadoras, como el personaje de campana, 0x07 si mal no recuerdo … Algunos textos olvidados deben haber sido originalmente capturados con caracteres de control para máquinas de escribir controladas eléctricamente y perpetúa el modelo …

En realidad, la variación correcta sería incluir el \ r, avance de línea, el retorno de carro no es necesario, es decir, automático, por lo tanto:

 char c; ifstream is; is.open("",ios::binary); ... is.getline(buffer, bufsize, '\r'); //ignore following \n or restre the buffer data if ((c=is.get())!='\n') is.rdbuf()->sputbackc(c); ... 

sería la forma más correcta de manejar todo tipo de archivos. Sin embargo, tenga en cuenta que \ n en el modo TEXTO es realmente el par de bytes 0x0d 0x0a, pero 0x0d IS simplemente \ r: \ n incluye \ r en modo TEXTO pero no en BINARIO , por lo que \ n y \ r \ n son equivalentes … o debiera ser. Esta es una confusión de la industria muy básica, la inercia típica de la industria, ya que la convención es hablar de CRLF, en TODAS las plataformas, luego caen en diferentes interpretaciones binarias. Estrictamente hablando, los archivos que incluyen SOLAMENTE 0x0d (retorno de carro) como \ n (CRLF o avance de línea), están mal formados en modo TEXTO (máquina de escribir a máquina: simplemente devuelve el automóvil y tacha todo …), y no están orientados a la línea formato binario (ya sea \ r o \ r \ n significado orientado a la línea) ¡por lo que no debe leer como texto! El código debería fallar tal vez con algún mensaje del usuario. Esto no depende solo del sistema operativo, sino también de la implementación de la biblioteca C, lo que aumenta la confusión y las posibles variaciones … (particularmente para capas de traducción UNICODE transparentes que agregan otro punto de articulación para variaciones confusas).

El problema con el fragmento de código anterior (máquina de escribir mecánica) es que es muy ineficiente si no hay \ n caracteres después de \ r (texto automático de máquina de escribir). Luego, también asume el modo BINARIO donde la biblioteca C se ve obligada a ignorar las interpretaciones de texto (configuración regional) y regalar los bytes puros. No debe haber diferencia en los caracteres de texto reales entre ambos modos, solo en los caracteres de control, por lo tanto, en general, leer BINARY es mejor que el modo TEXT . Esta solución es eficiente para los archivos de texto típicos del sistema operativo BINARIO de Windows, independientemente de las variaciones de la biblioteca C, e ineficiente para otros formatos de texto de plataforma (incluidas las traducciones web al texto). Si te preocupa la eficiencia, el camino a seguir es usar un puntero a la función, hacer una prueba para los controles de línea \ r vs \ r \ n como quieras, luego seleccionar el mejor código de usuario getline en el puntero e invocarlo desde eso.

Por cierto, recuerdo que también encontré algunos \ r \ r \ n archivos de texto … lo que se traduce en texto de doble línea, tal como todavía lo requieren algunos consumidores de textos impresos.

Además de escribir su propio controlador personalizado o usar una biblioteca externa, no tiene suerte. Lo más fácil es verificar que la line[line.length() - 1] no sea ‘\ r’. En Linux, esto es superfluo ya que la mayoría de las líneas terminarán con ‘\ n’, lo que significa que perderás un poco de tiempo si esto está en un bucle. En Windows, esto también es superfluo. Sin embargo, ¿qué pasa con los archivos clásicos de Mac que terminan en ‘\ r’? std :: getline no funcionaría para esos archivos en Linux o Windows porque ‘\ n’ y ‘\ r’ ‘\ n’ terminan con ‘\ n’, lo que elimina la necesidad de buscar ‘\ r’. Obviamente, esa tarea que funciona con esos archivos no funcionaría bien. Por supuesto, existen los numerosos sistemas EBCDIC, algo que la mayoría de las bibliotecas no se atreverán a abordar.

La comprobación de ‘\ r’ es probablemente la mejor solución para su problema. Leer en modo binario le permitiría verificar las tres terminaciones de línea comunes (‘\ r’, ‘\ r \ n’ y ‘\ n’). Si solo le importan Linux y Windows, ya que las terminaciones de línea Mac de estilo antiguo no deberían durar mucho más, compruebe ‘\ n’ solamente y elimine el carácter ‘\ r’ posterior.

Una solución sería buscar primero y reemplazar todas las terminaciones de línea a ‘\ n’, al igual que, por ejemplo, Git lo hace de forma predeterminada.