Man sollte meinen, dass Zeilenumbrüche in Textdateien ein so triviales Thema ist, dass sich fast die Frage aufdrängt, ob es sich überhaupt lohnt, darüber zu schreiben. Wie man’s nimmt – in einer idealen Welt, in der sich die Hersteller von unterschiedlichen Betriebssystemen auf gemeinsame Standards einigen oder zumindest bereits bestehendes berücksichtigen würden, wäre es in der Tat eine elementare Sache, die keiner Erwähnung bedürfte. Die IT-Realität sieht jedoch anders aus. Viele Hersteller kochen oft ihr eigenes Süppchen – daher erlaube ich mir, ein paar Zeile darüber zu Papier zu bringen.
Jedoch warum ist es so? Warum werden parallel Lösungen entwickelt, obwohl es in vielen Fällen bereits was Bestehendes gibt? Nun – da ich selbst ITler bin, habe ich in mich selbst hineingehorcht und ein paar Gründe identifiziert, warum ich oft fertige Lösungen links liegen lasse und stattdessen selbst entwickele:
- Ich programmiere sehr gerne und setze die Zeit lieber für spannendere Themen, wie die Entwicklung ein, als stundenlang langweilige Recherche zu betreiben, ob es vielleicht bereits eine fertig entwickelte Klasse gibt, die ich für die Problemlösung einsetzen könnte. Aber auch wenn ich weiß, dass es eine fertige Klasse gibt, habe ich nicht immer Lust, ihre Doku (sofern natürlich überhaupt vorhanden) zu studieren.
- Manchmal entwickele ich die Problemlösung selbst, wenn ich der Meinung bin, ich könnte das Problem besser, eleganter und schneller lösen, als es der Entwickler einer bereits fertiggestellten Klasse getan hat – in manchen Fällen ist es jedoch eine klare Selbstüberschätzung meinerseits.
- Oft setze ich auf individuelle Lösung, wenn eine bestehende Klasse meine Anforderungen nicht zu 100% abgeckt und ich keine Lust auf Kompromisse habe.
Wer weiß, vielleicht haben ähnliche Überlegungen der damaligen Entwickler dazu geführt, dass Zeilenumbrüche je nach Betriebssystemfamilie unterschiedlich umgesetzt wurden.
Umsetzung von Zeilenumbrüchen
in Schreibmaschinen
Wir blicken mal weit in die Zeit der Dinosaurier zurück. In die Zeit, in der es keine PC’s, sondern nur Schreibmaschinen gab. Schrieb man auf einer Schreibmaschine, bewegte sich mit jedem Tasteneinschlag der Schlitten mit dem Papier jeweils um eine Position weiter links. Kam der Schlitten am äußersten linken Rand an, war die Zeile zu Ende. Um dann weiter schreiben zu können, musste man den Schlitten mit der Taste CarriegeReturn(CR) komplett wieder nach rechts schieben und mit der Taste LineFeed(LF) eine Zeile nach unten springen.
in der Windows-Welt
Als Anfang der 80er Jahr MS-DOS herauskam, adaptierte man dort die Schreibmaschinenlogik für Zeilenumbrüche in Textdateien. Auch im aktuellen Windows-Betriebssystem besteht ein Zeilenumbruch technisch gesehen aus zwei Steuerzeichen CR und LF, in der HEX-Schreibweise ausgedrückt ist es 0x0D0A.
in der Unix-Welt
Jedoch haben sich die Herrschaften aus Redmond nicht davon beeindrucken lassen, dass in den späten 60ern entwickelte UNIX-Betriebssystem den Zeilenumbruch anders, nämlich nur mit LineFeed (0x0A) abgebildet hat.
in der Apple-Welt
Da bisher jeder Hersteller das Rad neu erfand, wollte Apple offenbar auch nicht zu kurz kommen und setze in den 80ern den Zeilenumbruch mit CarriegeReturn (0x0D) um. Neuere Apple-Betriebssysteme (ab der Jahrtausendwende) nutzen jedoch mittlerweile die UNIX-Variante der Zeilenumbrüche mit LineFeed (0x0A).
Zeilenumbruchprobleme beim Einsatz in der Praxis
Der Problematik mit unterschiedlichen Zeilenumbrüchen bin ich in einem meiner größeren Projekte begegnet, in dem es um die Entwicklung von Einleseroutinen für Klartextdateien ging. Es waren keine typischen Austauschformate wie XML oder CSV, sondern semistrukturierte TXT-Dateien, die aus PDF-Berichten erstellt wurden. Da die Dateien von unterschiedlichen Plattformen kamen, mussten die Betriebssysteme -Besonderheiten bei Zeilenumbrüchen berücksichtigt werden.
Für die Umsetzung der 3 Zeilenumbruchsvarianten habe ich mich damals für eine einfache und pragmatische Lösung entschieden. Die Lösung sah so aus, dass der Inhalt der jeweiligen Datei als erstes komplett in den Speicher eingelesen wurde. Anschließend wurde der Inhalt der split-Funktion übergeben, die es mit CRLF aufzuteilen versuchte. War es nicht erfolgreich, wurde die Aufteilung mit CR probiert. War dies ebenso wenig erfolgreich, wurde anschließend mit LF versucht. Schlug auch dies fehlt, war es eine nicht aufteilbare Datei, was mit einer entsprechenden Exception quittiert wurde. Konnte einer der Zweige die Datei erfolgreich aufteilen, wurde diese Routine verlassen und der in Zeilen aufgeteilte Inhalt an die fachliche Verarbeitung übergeben.
Die Tests verliefen ohne Beanstandungen und das Programm wurde produktiv gesetzt. Eines Tages wurde mir ein Fehler in der Verarbeitung gemeldet. Als ich es mir auf die Schnelle angeschaut habe, konnte ich mir erstmal keinen Reim darauf machen. Die Datei wurde korrekt eingelesen und mit CRLF aufgeteilt. Trotz der erfolgreichen Aufteilung enthielt die aufgeteilte Datei nach jedem Datensatz je eine leere Zeile, die nur aus einem CR-Zeichen bestanden, was für Probleme in der Weiterverarbeitung gesorgt hat. Erst genauere Analyse der Inputdatei (es lebe der HEX-Plugin für NodePad++) gab den Aufschluss über das Problem. Die Inputdatei enthielt nämlich nicht CRLF als Trennzeichen, sondern eine Kombination aus CR und CRLF, was beim Aufteilen immer ein CR-Überbleibsel hinterließ.
Wie diese Konstellation zustande kommen konnte, konnte ich nicht ganz nachvollziehen. Meine Vermutung ist, dass diese Datei auf einem Windows-Rechner entstanden ist und anschließend jedoch auf einem älteren Apple-Rechner bearbeitet wurde, welcher offenbar ein zusätzliches CR-Zeichen an jede Zeile hinzugefügt hatte. Älterer Rechner vermutlich deswegen, weil modernere Apple-Rechner LF als Trennzeichen nutzen und deren Texteditoren eigentlich in der Lage sind, Textdateien mit anderen Trennzeichen korrekt zu behandeln.
Da solche Konstellationen ab und zu vorkamen und ich weitere seltsame Kombinationen mit CR und LF nicht ausschließen konnte, entscheid ich mich für eine andere Lösung, die ich im nächsten Kapitel kurz vorstellen möchte.
Alternativer Aufteilungsansatz von Textdateien
Die Lösung des Problems war eine neue Klasse, die anhand der übergebenen Datei ermitteln konnte, mit welcher Trennzeichenkombination oder einem einzelnen Trennzeichen Zeilen getrennt wurden.
Dabei wird ermittelt, an welcher Position im Dateiinhalt das erste Trennzeichen (CR oder LF) auftritt. An dieser Position wird aufgesetzt und solange Zeichen für Zeichen durchlaufen, solange es sich entweder um CR oder ein LF handelt. Anschließend werden die so ermittelten, direkt beieinander liegenden Trennzeichen zusammengeführt und an den Aufrufer der Klasse geliefert. Der Aufrufer nutzt dann die ermittelten Trennzeichen (egal wie schräg deren Kombination auch aussehen mag), um die Datei sauber in Datensätze aufzuteilen. Seitdem diese Klasse im Einsatz ist, wurden mir keine Probleme mehr gemeldet.
Alles in einem war die Entwicklung der neuen Klasse für eine saubere Ermittlung kein großer Aufwand – die Problembehebung hat sogar Spaß gemacht und wieder vor Augen geführt, dass es in der Praxis eigentlich wirklich jede Konstellation auftreten kann.
Guter Artikel! Zeilenumbrüche in Textdateien ist wirklich nicht so trivial, wie viele Anwender glauben. Die meisten werden es aber erst dann verstehen, wenn Sie das erste Mal vor einem Problem stehen. VG
Hallo Michael, vielen Dank! Ja, in der Tat, das Thema ist komplexer, als es den Anschein hat. Verglichen jedoch mit der Fülle und Komplexität an Zeichensätzen und Zeichenkodierungen, die es gibt, wirkt das Thema Zeilenumbrüche dann doch fast trivial 🙂
Viele Grüße
Tony