Как бороться с буферизованными строками из 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"). Затем вы можете обработать все до этого момента одним махом, а особый случай - только последние несколько байтов.

Другие вопросы по тегам