如何在Swift中处理来自C的缓冲字符串?

时间:2015-12-19 15:33:34

标签: swift utf-8 libxml2 null-terminated

我正在使用libxml2的sax解析器来读取大型xml文件。大多数回调处理程序都提供了一个NULL终止的char指针。使用String.fromCString这些可以在Swift中转换为常规字符串。但是sax使用缓冲区来读取字节,因此可以使用字符串的一部分(即缓冲区的大小)调用其中一个回调(characters)。这个部分字符串甚至可能在Unicode代码点的中途开始/结束。回调将被多次调用,直到提供完整的字符串(以块为单位)。

我想要连接所有块,直到可以组装完整的字符串,或以某种方式检测部分字符串中的代码点边界,只处理完成直到无效的代码点。

处理这种情况的最佳方法是什么?处理应该尽可能快,同时仍然正确。内存使用应保持最小,但不能以性能为代价。

2 个答案:

答案 0 :(得分:2)

如果处理速度是你的第一个目标,那么我会收集 所有字符,直到完全处理XML元素 调用endElement。这可以使用NSMutableData完成 来自Foundation框架。所以你需要一个属性

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 

答案 1 :(得分:1)

最长的合法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)

这只是一种直截了当的方式。另一种可能更快的方法是向后遍历字节,寻找代码点的最后一个开始(一个不以&#34开头的字节; 10&#34;)。然后你可以一举处理所有事情,特殊情况只是最后几个字节。