Как бороться с буферизованными строками из C в Swift?
Я работаю с синтаксическим анализатором libxml2 для чтения больших XML-файлов. Большинству обработчиков обратного вызова предоставляется указатель на символ NULL с завершением. С помощью String.fromCString
они могут быть преобразованы в обычную строку в Swift. Однако sax использует буфер для чтения байтов, поэтому один из обратных вызовов (characters
) может вызываться с частью строки, а именно с размером буфера. Эта частичная строка может даже начинаться / заканчиваться на полпути кодовой точки Unicode. Обратный вызов будет вызываться несколько раз, пока не будет предоставлена полная строка (кусками).
Я имею в виду либо объединение всех фрагментов до тех пор, пока не будет собрана полная строка, либо каким-либо образом определение границ кодовых точек в частичных строках, только обработка завершается до недопустимой кодовой точки.
Каков наилучший способ справиться с такими обстоятельствами? Обработка должна быть максимально быстрой, но при этом правильной. Использование памяти должно быть минимальным, но не за счет производительности.
2 ответа
Если ваша цель - скорость обработки, тогда я собираю все символы, пока элемент XML не будет полностью обработан иendElement
называется. Это можно сделать с помощью NSMutableData
от основы фонда. Так что вам нужна недвижимость
var charData : NSMutableData?
который инициализируется в startElement
:
charData = NSMutableData()
в characters
к обратному вызову вы добавляете все данные:
charData!.appendBytes(ch, length: Int(len))
(Принудительное развертывание здесь допустимо. charData
может быть только nil
если startElement
ранее не вызывался, что означает, что вы допустили ошибку программирования или libxml2 работает неправильно).
Наконец в endElement
создайте строку Swift и отпустите данные:
defer {
// Release data in any case before function returns
charData = nil
}
guard let string = String(data: charData!, encoding: NSUTF8StringEncoding) else {
// Handle invalid UTF-8 data situation
}
// string is the Swift string
Самый длинный допустимый символ UTF-8 составляет 4 байта ( RFC 3629, раздел 3). Таким образом, вам не нужен очень большой буфер для безопасности. Правила того, сколько байтов вам понадобится, тоже довольно просты (просто посмотрите на первый байт). Так что я бы просто поддерживал буфер, который содержит от 0 до 3 байтов. Когда у вас есть правильное число, передайте его и попробуйте построить строку. Примерно так (только слегка протестированные, могут быть угловые случаи, которые до сих пор не работают):
final class UTF8Parser {
enum Error: ErrorType {
case BadEncoding
}
var workingBytes: [UInt8] = []
func updateWithBytes(bytes: [UInt8]) throws -> String {
workingBytes += bytes
var string = String()
var index = 0
while index < workingBytes.count {
let firstByte = workingBytes[index]
var numBytes = 0
if firstByte < 0x80 { numBytes = 1 }
else if firstByte < 0xE0 { numBytes = 2 }
else if firstByte < 0xF0 { numBytes = 3 }
else { numBytes = 4 }
if workingBytes.count - index < numBytes {
break
}
let charBytes = workingBytes[index..<index+numBytes]
guard let newString = String(bytes: charBytes, encoding: NSUTF8StringEncoding) else {
throw(Error.BadEncoding)
}
string += newString
index += numBytes
}
workingBytes.removeFirst(index)
return string
}
}
let parser = UTF8Parser()
var string = ""
string += try parser.updateWithBytes([UInt8(65)])
print(string)
let partial = try parser.updateWithBytes([UInt8(0xCC)])
print(partial)
let rest = try parser.updateWithBytes([UInt8(0x81)])
print(rest)
string += rest
print(string)
Это только один способ, который довольно прост. Другой подход, который, вероятно, быстрее, состоит в том, чтобы идти назад через байты в поисках последнего начала кодовой точки (байта, который не начинается с "10"). Затем вы можете обработать все до этого момента одним махом, а особый случай - только последние несколько байтов.