2015-12-19 5 views
3

Я работаю с sax-парсером libxml2, чтобы читать большие XML-файлы. Большинство обработчиков обратного вызова снабжены указателем на символ с символом NULL. Используя String.fromCString, они могут быть преобразованы в обычную строку в Swift. Однако sax использует буфер для чтения байтов, поэтому один из обратных вызовов (characters) может быть вызван с частью строки, а именно с размером буфера. Эта частичная строка может даже начинаться/заканчиваться наполовину кодовой точкой Юникода. Обратный вызов будет вызываться несколько раз, пока не будет предоставлена ​​полная строка (в кусках).Как справиться с буферизованными строками из C в Swift?

Я собираюсь либо конкатенировать все куски до тех пор, пока не будет собрана полная строка, или каким-либо образом обнаружит границы кодовых границ в частичных строках, только обработка завершится до недопустимого кодового слова.

Что было бы лучшим способом справиться с такими обстоятельствами? Обработка должна быть как можно быстрее, хотя и правильной. Использование памяти должно быть минимальным, но не ценой производительности.

+1

Я не испытывал с саксофоном анализатором Libxml2, но [здесь] (http://www.jamesh.id.au/ articles/libxml-sax/libxml-sax.html # символов) говорится, что: «В вашем обратном вызове вы, вероятно, захотите скопировать символы в другой буфер ...» *. Это можно сделать, например, с помощью NSMutableData. –

+0

Если вы используете 'String.fromCString', тогда данные кодируются в UTF-8.Вы можете посмотреть последние несколько байтов буфера, чтобы увидеть, где находится граница символа, а затем создать частичную строку, используя NSMutableString (bytes: length: encoding :). Затем сохраните любые дополнительные байты для добавления к следующему буферу, повторите и добавьте последующие строки в конец оригинала. –

ответ

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) 

Это только один способ, который отчасти просто. Другим подходом, который, вероятно, будет быстрее, будет идти назад через байты, ища последний старт кодовой точки (байт, который не начинается с «10»). Тогда вы можете обработать все до этого момента одним махом, а в специальном случае - только последние несколько байтов.

+0

Интересный подход, но только перенос оставшихся байтов, вероятно, улучшит производительность, требуя меньше копирования. – bouke

+0

Вы могли бы, конечно, создать более специальные случаи, чтобы избежать еще большего копирования. Например, поскольку вы знаете, сколько байтов вы хотите использовать из 'байтов', вы можете срезать их и добавлять их к« workingBytes », а затем работать с остальными как срез. По моему опыту, если копирование является серьезной проблемой, то «NSMutableData» является лучшим инструментом, поскольку он имеет гораздо более предсказуемые правила копирования и имеет «noCopy» оптимизацию для работы с иностранными буферами. Вы также можете работать в UnsafeMutableBufferPointers, но это также может быть очень сложно использовать правильно. –

+0

См. Http://robnapier.net/nsdata для моего гораздо более длительного разговора о выходе и возвращении к 'NSMutableData', почти полностью по поводу проблем с копированием. –

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 
+0

Спасибо за интересный подход. Был ли способ сделать это без времени выполнения ObjC? Вместо того, чтобы ждать «endElement», можно также проверить последний байт 'символов' для' NULL' правильно? – bouke

+0

@bouke: Извините, я не понимаю, что вы имеете в виду. В символах нет NULL. Что вы хотите проверить? (Обратите внимание, что многие функции Swift используют Foundation под капотом, так что библиотека используется в любом случае). - Если вы хотите обнаружить * недопустимый ввод * до вызова endElement, этот подход не работает, и вы должны продолжить, например. как в ответе Роба. Это * может * быть медленнее, потому что данные перемещаются чаще, но вы должны сами их протестировать. –

+0

Если проблема с копированием связана с проблемой, обратите внимание, что обычно проще понять производительность NSData, чем производительность [UInt8] '. Массивы - это типы значений с копированием на запись, но также оптимизация с одной ссылкой, чтобы избежать копирования. Это затрудняет точно знать, когда они будут скопированы, если вы будете манипулировать ими. 'NSMutableData' является ссылочным типом, поэтому из-за мутации никогда не появляются неявные копии. Я переключился на реализацию '[UInt8]', потому что код немного проще читать, а не потому, что он быстрее. –