(Примечание: я использую Swift 3.0.1 в Xcode 8.2.1 с macOS Sierra 10.12.3)
Все ответы, которые я здесь видел, упускали из виду, что он мог искать LF или CRLF. Если все пойдет хорошо, он / она может просто сопоставить LF и проверить возвращенную строку на наличие лишнего CR в конце. Но общий запрос включает несколько строк поиска. Другими словами, разделитель должен быть a Set<String>
, где набор не пуст и не содержит пустую строку, а не одну строку.
С моей первой попытки в прошлом году я попытался поступить «правильно» и найти общий набор строк. Это было слишком сложно; вам нужен полноценный парсер, конечные автоматы и все такое. Я отказался от этого и от проекта, частью которого он был.
Теперь я снова занимаюсь этим проектом и снова сталкиваюсь с той же проблемой. Теперь я собираюсь выполнить жесткий поиск по CR и LF. Я не думаю, что кому-то понадобится искать два полунезависимых и полузависимых символа, подобных этому, вне анализа CR / LF.
Я использую методы поиска, предоставленные Data
, поэтому я не использую здесь строковые кодировки и прочее. Просто необработанная двоичная обработка. Предположим, у меня есть надмножество ASCII, например ISO Latin-1 или UTF-8. Вы можете обрабатывать строковое кодирование на следующем, более высоком уровне, и вы можете понять, считается ли CR / LF с присоединенными вторичными кодовыми точками как CR или LF.
Алгоритм: просто продолжайте поиск следующего CR и следующего LF от вашего текущего байтового смещения.
- Если ни один из них не найден, считайте, что следующая строка данных находится от текущего смещения до конца данных. Обратите внимание, что длина терминатора равна 0. Отметьте это как конец цикла чтения.
- Если сначала найден LF или найден только LF, считайте, что следующая строка данных будет от текущего смещения до LF. Обратите внимание, что длина ограничителя равна 1. Переместите смещение после LF.
- Если найден только CR, действуйте как в случае LF (только с другим значением байта).
- В противном случае мы получили CR, за которым следует LF.
- Если они находятся рядом, то обработайте как в случае LF, за исключением того, что длина терминатора будет равна 2.
- Если между ними есть один байт, и указанный байт также является CR, то мы получили «разработчик Windows написал двоичный файл \ r \ n в текстовом режиме, что дало проблему \ r \ r \ n». Также обращайтесь с ним, как с корпусом LF, за исключением того, что длина терминатора будет 3.
- В противном случае CR и LF не подключены и обрабатываются как в случае с просто CR.
Вот код для этого:
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
}
}
Конечно, если у вас есть Data
блок, длина которого составляет, по крайней мере, значительную долю гигабайта, вы получите удар всякий раз, когда из текущего байтового смещения больше не существует CR или LF; всегда бесплодно ищет до конца на каждой итерации. Чтение данных по частям может помочь:
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)))
}
}
Вы должны сами смешать эти идеи, так как я еще этого не сделал. Рассматривать:
- Конечно, вы должны учитывать строки, полностью содержащиеся в чанке.
- Но вам нужно обрабатывать, когда концы строки находятся в соседних блоках.
- Или когда между конечными точками есть хотя бы один кусок
- Большая сложность возникает, когда строка заканчивается многобайтовой последовательностью, но указанная последовательность состоит из двух частей! (Строка, заканчивающаяся только на CR, которая также является последним байтом в фрагменте, является эквивалентным случаем, поскольку вам нужно прочитать следующий фрагмент, чтобы увидеть, действительно ли ваш just-CR является CRLF или CR-CRLF. Есть аналогичные махинации, когда чанк заканчивается CR-CR.)
- И вам нужно обработать, когда от вашего текущего смещения больше нет терминаторов, но конец данных находится в более позднем фрагменте.
Удачи!