(Uwaga: używam Swift 3.0.1 na Xcode 8.2.1 z macOS Sierra 10.12.3)
Wszystkie odpowiedzi, które tu widziałem, pominęły, że mógł szukać LF lub CRLF. Jeśli wszystko pójdzie dobrze, może po prostu dopasować się do LF i sprawdzić, czy na końcu zwrócony ciąg jest dodatkowy CR. Ale ogólne zapytanie obejmuje wiele ciągów wyszukiwania. Innymi słowy, separatorem musi być a Set<String>
, gdzie zestaw nie jest pusty ani nie zawiera pustego ciągu zamiast pojedynczego ciągu.
Podczas mojej pierwszej próby w zeszłym roku próbowałem zrobić „właściwą rzecz” i poszukać ogólnego zestawu ciągów. To było zbyt trudne; potrzebujesz pełnego parsera i maszyn stanowych i tym podobnych. Zrezygnowałem z tego i projektu, którego był częścią.
Teraz ponownie wykonuję projekt i znów stoję przed tym samym wyzwaniem. Teraz przejdę do wyszukiwania twardego kodu na CR i LF. Nie sądzę, żeby ktokolwiek musiał szukać takich dwóch częściowo niezależnych i częściowo zależnych znaków poza analizą CR / LF.
Używam metod wyszukiwania dostarczonych przez Data
, więc nie robię tutaj kodowania ciągów i innych rzeczy. Tylko surowe przetwarzanie binarne. Załóżmy tylko, że mam tutaj superset ASCII, taki jak ISO Latin-1 lub UTF-8. Możesz obsługiwać kodowanie ciągów w następnej wyższej warstwie i zastanawiasz się, czy CR / LF z dołączonymi dodatkowymi punktami kodowymi nadal liczy się jako CR czy LF.
Algorytm: po prostu szukaj dalej następnej CR i następnej LF z bieżącego przesunięcia bajtów.
- Jeśli żadne z nich nie zostanie znalezione, rozważ następny ciąg danych od bieżącego przesunięcia do końca danych. Zwróć uwagę, że długość terminatora wynosi 0. Oznacz to jako koniec pętli odczytu.
- Jeśli LF zostanie znaleziony jako pierwszy lub tylko LF zostanie znaleziony, rozważ następny ciąg danych od bieżącego przesunięcia do LF. Zwróć uwagę, że długość terminatora wynosi 1. Przesuń przesunięcie za LF.
- Jeśli zostanie znaleziona tylko CR, zrób jak przypadek LF (tylko z inną wartością bajtu).
- W przeciwnym razie otrzymaliśmy CR, a następnie LF.
- Jeśli oba sąsiadują ze sobą, uchwyt jak w przypadku LF, z tym że długość terminatora będzie wynosić 2.
- Jeśli między nimi jest jeden bajt, a wspomniany bajt to również CR, otrzymaliśmy komunikat „Programista Windows napisał plik binarny \ r \ n w trybie tekstowym, co daje problem \ r \ r \ n". Obsługuj to również jak w przypadku LF, z wyjątkiem terminatora o długości 3.
- W przeciwnym razie CR i LF nie są połączone i działają jak obudowa just-CR.
Oto kod:
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
}
}
Oczywiście, jeśli masz Data
blok o długości, która stanowi co najmniej znaczący ułamek gigabajta, otrzymasz trafienie, gdy nie będzie już CR ani LF z bieżącego przesunięcia bajtów; zawsze bezowocne poszukiwanie do końca podczas każdej iteracji. Odczytywanie danych w fragmentach pomogłoby:
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)))
}
}
Musisz samodzielnie wymieszać te pomysły, ponieważ jeszcze tego nie zrobiłem. Rozważać:
- Oczywiście musisz wziąć pod uwagę linie całkowicie zawarte w porcji.
- Ale musisz sobie z tym poradzić, gdy końce linii znajdują się w sąsiednich fragmentach.
- Lub gdy punkty końcowe mają co najmniej jeden fragment między nimi
- Dużą komplikacją jest sytuacja, gdy wiersz kończy się sekwencją wielobajtową, ale ta sekwencja obejmuje dwa fragmenty! (Wiersz kończący się po prostu CR, który jest również ostatnim bajtem w porcji, jest równoważnym przypadkiem, ponieważ musisz przeczytać następną porcję, aby sprawdzić, czy twój just-CR to w rzeczywistości CRLF lub CR-CRLF. fragment kończy się na CR-CR.)
- I musisz sobie z tym poradzić, gdy nie ma już terminatorów z bieżącego przesunięcia, ale koniec danych znajduje się w późniejszym fragmencie.
Powodzenia!