(Примітка: я використовую Swift 3.0.1 на Xcode 8.2.1 з macOS Sierra 10.12.3)
Всі відповіді, які я тут бачив, пропускали, що він міг шукати НЧ чи КРЛ. Якщо все піде добре, він / вона може просто збігатися на 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. Перемістіть зміщення на значення після НЧ.
- Якщо знайдено лише CR, зробіть так, як LF (просто з іншим байтовим значенням).
- В іншому випадку ми отримали CR, за яким слідує LF.
- Якщо ці два суміжні, то обробляйте, як корпус НЧ, за винятком того, що довжина термінатора буде 2.
- Якщо між ними є один байт, і зазначений байт також є CR, то ми отримали "Розробник Windows написав двійковий файл \ r \ n, перебуваючи в текстовому режимі, даючи проблему \ r \ r \ n". Також обробляйте це як корпус НЧ, за винятком того, що довжина термінатора буде 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.)
- І вам потрібно обробляти, коли з вашого поточного зміщення більше не буде термінаторів, але кінець даних буде пізніше.
Удачі!