(Hinweis: Ich verwende Swift 3.0.1 unter Xcode 8.2.1 mit macOS Sierra 10.12.3.)
Alle Antworten, die ich hier gesehen habe, haben übersehen, dass er nach LF oder CRLF suchen könnte. Wenn alles gut geht, könnte er / sie einfach mit LF übereinstimmen und die zurückgegebene Zeichenfolge am Ende auf eine zusätzliche CR überprüfen. Die allgemeine Abfrage umfasst jedoch mehrere Suchzeichenfolgen. Mit anderen Worten, das Trennzeichen muss a sein Set<String>
, wobei die Menge weder leer ist noch die leere Zeichenfolge enthält, sondern eine einzelne Zeichenfolge.
Bei meinem ersten Versuch in diesem letzten Jahr habe ich versucht, das "Richtige" zu tun und nach einem allgemeinen Satz von Zeichenfolgen zu suchen. Es war zu schwer; Sie benötigen einen ausgewachsenen Parser und Zustandsautomaten und so weiter. Ich habe es aufgegeben und das Projekt, an dem es beteiligt war.
Jetzt mache ich das Projekt wieder und stehe wieder vor der gleichen Herausforderung. Jetzt gehe ich zur Hardcode-Suche nach CR und LF. Ich glaube nicht, dass irgendjemand außerhalb der CR / LF-Analyse nach zwei halbunabhängigen und halbabhängigen Zeichen wie diesem suchen müsste.
Ich verwende die Suchmethoden von Data
, also mache ich hier keine String-Codierungen und so. Nur rohe Binärverarbeitung. Nehmen wir einfach an, ich habe hier eine ASCII-Obermenge wie ISO Latin-1 oder UTF-8. Sie können die Zeichenfolgencodierung auf der nächsthöheren Ebene verarbeiten und prüfen, ob ein CR / LF mit angehängten sekundären Codepunkten weiterhin als CR oder LF zählt.
Der Algorithmus: Suchen Sie einfach weiter nach dem nächsten CR und dem nächsten LF aus Ihrem aktuellen Byte-Offset.
- Wenn beides nicht gefunden wird, wird davon ausgegangen, dass die nächste Datenzeichenfolge vom aktuellen Offset bis zum Datenende reicht. Beachten Sie, dass die Terminatorlänge 0 ist. Markieren Sie dies als das Ende Ihrer Leseschleife.
- Wenn zuerst ein LF gefunden wird oder nur ein LF gefunden wird, betrachten Sie die nächste Datenzeichenfolge als vom aktuellen Offset zum LF. Beachten Sie, dass die Terminatorlänge 1 beträgt. Verschieben Sie den Versatz nach nach dem LF.
- Wenn nur ein CR gefunden wird, mögen Sie den LF-Fall (nur mit einem anderen Bytewert).
- Ansonsten haben wir eine CR gefolgt von einer LF.
- Wenn die beiden benachbart sind, behandeln Sie sie wie im LF-Fall, außer dass die Terminatorlänge 2 beträgt.
- Wenn sich zwischen ihnen ein Byte befindet und das Byte auch CR ist, haben wir den Fehler "Windows-Entwickler hat im Textmodus eine Binärdatei \ r \ n geschrieben, was ein \ r \ r \ n" -Problem verursacht. Behandeln Sie es auch wie das LF-Gehäuse, außer dass die Terminatorlänge 3 beträgt.
- Andernfalls sind CR und LF nicht verbunden und werden wie der Just-CR-Fall behandelt.
Hier ist ein Code dafür:
struct DataInternetLineIterator: IteratorProtocol {
typealias LineLocation = (offset: Int, length: Int, terminatorLength: Int)
static let cr: UInt8 = 13
static let crData = Data(repeating: cr, count: 1)
static let lf: UInt8 = 10
static let lfData = Data(repeating: lf, count: 1)
let data: Data
private var lineStartOffset: Int = 0
init(data: Data) {
self.data = data
}
mutating func next() -> LineLocation? {
guard self.data.count - self.lineStartOffset > 0 else { return nil }
let nextCR = self.data.range(of: DataInternetLineIterator.crData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
let nextLF = self.data.range(of: DataInternetLineIterator.lfData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
var location: LineLocation = (self.lineStartOffset, -self.lineStartOffset, 0)
let lineEndOffset: Int
switch (nextCR, nextLF) {
case (nil, nil):
lineEndOffset = self.data.count
case (nil, let offsetLf):
lineEndOffset = offsetLf!
location.terminatorLength = 1
case (let offsetCr, nil):
lineEndOffset = offsetCr!
location.terminatorLength = 1
default:
lineEndOffset = min(nextLF!, nextCR!)
if nextLF! < nextCR! {
location.terminatorLength = 1
} else {
switch nextLF! - nextCR! {
case 2 where self.data[nextCR! + 1] == DataInternetLineIterator.cr:
location.terminatorLength += 1
fallthrough
case 1:
location.terminatorLength += 1
fallthrough
default:
location.terminatorLength += 1
}
}
}
self.lineStartOffset = lineEndOffset + location.terminatorLength
location.length += self.lineStartOffset
return location
}
}
Wenn Sie einen Data
Block mit einer Länge haben, die mindestens einen signifikanten Bruchteil eines Gigabytes ausmacht , werden Sie natürlich immer dann einen Treffer erzielen, wenn vom aktuellen Byte-Offset kein CR oder LF mehr vorhanden ist. Immer erfolglos bis zum Ende bei jeder Iteration suchen. Das Lesen der Daten in Blöcken würde helfen:
struct DataBlockIterator: IteratorProtocol {
let data: Data
private(set) var blockOffset = 0
private(set) var bytesRemaining: Int
let blockSize: Int
init(data: Data, blockSize: Int) {
precondition(blockSize > 0)
self.data = data
self.bytesRemaining = data.count
self.blockSize = blockSize
}
mutating func next() -> Data? {
guard bytesRemaining > 0 else { return nil }
defer { blockOffset += blockSize ; bytesRemaining -= blockSize }
return data.subdata(in: blockOffset..<(blockOffset + min(bytesRemaining, blockSize)))
}
}
Sie müssen diese Ideen selbst mischen, da ich es noch nicht getan habe. Erwägen:
- Natürlich müssen Sie Linien berücksichtigen, die vollständig in einem Block enthalten sind.
- Sie müssen jedoch damit umgehen, wenn sich die Enden einer Linie in benachbarten Abschnitten befinden.
- Oder wenn zwischen den Endpunkten mindestens ein Block liegt
- Die große Komplikation besteht darin, dass die Zeile mit einer Mehrbyte-Sequenz endet, diese Sequenz jedoch zwei Blöcke überspannt! (Eine Zeile, die nur mit CR endet und gleichzeitig das letzte Byte im Block ist, ist ein äquivalenter Fall, da Sie den nächsten Block lesen müssen, um festzustellen, ob Ihr just-CR tatsächlich eine CRLF oder CR-CRLF ist. Es gibt ähnliche Spielereien, wenn die Chunk endet mit CR-CR.)
- Und Sie müssen damit umgehen, wenn Ihr aktueller Offset keine Terminatoren mehr enthält, das Datenende jedoch in einem späteren Abschnitt liegt.
Viel Glück!