2016-10-25 5 views
0

Я пытаюсь разобрать необработанный HTTP-ответ, и я получаю неправильный диапазон при попытке конвертировать NSRange в Range. Вот соответствующий код с детской площадки:странное поведение диапазона строк с использованием совпадений NSRegularExpression

public extension NSRange { 
    public func toStringRange(_ str: String) -> Range<String.Index>? { 
     guard str.characters.count >= length - location && location < str.characters.count else { return nil } 
     let fromIdx = str.characters.index(str.startIndex, offsetBy: self.location) 
     print("from: \(self.location) = \(fromIdx)") 
     let toIdx = str.characters.index(fromIdx, offsetBy: self.length) 
     return fromIdx..<toIdx 
    } 
} 

let responseString = "HTTP/1.0 200 OK\r\nContent-Length: 193\r\nContent-Type: application/json\r\n" 
let responseRange = NSRange(location: 0, length: responseString.characters.count) 
let responseRegex = try! NSRegularExpression(pattern: "^(HTTP/1.\\d) (\\d+) (.*?\r\n)(.*)", options: [.anchorsMatchLines]) 
guard let matchResult = responseRegex.firstMatch(in: responseString, options: [], range: responseRange), 
    matchResult.numberOfRanges == 5, 
    let versionRange = matchResult.rangeAt(1).toStringRange(responseString), 
    let statusRange = matchResult.rangeAt(2).toStringRange(responseString), 
    let headersRange = matchResult.rangeAt(4).toStringRange(responseString) 
    else { fatalError() } 

Выход из печати в toStringRange() является

from: 0 = Index(_base: Swift.String.UnicodeScalarView.Index(_position: 0), _countUTF16: 1) 
from: 9 = Index(_base: Swift.String.UnicodeScalarView.Index(_position: 9), _countUTF16: 1) 
from: 17 = Index(_base: Swift.String.UnicodeScalarView.Index(_position: 18), _countUTF16: 1) 

Почему третий toStringRange() вызов возвращает диапазон строку, которая начинается в 18 вместо из 17?

ответ

1

Ваш метод преобразования из NSRange в Range<String.Index> не корректно работают в течение длительного графемы кластеров и символов вне от «базовой многоязычной плоскости» (смайликов, флаги и т.д.).

NSRange подсчитывает UTF-16 кодовые точки (соответствующие представления в unichar в NSString). Range<String.Index> count Swift Characters которые представляют собой расширенные кластеры графемы.

В вашем конкретном случае, "\r\n" считается как две точки UTF-16 кода, но как единый Character, и это вызывает нежелательный «сдвиг».

Вот упрощенный пример:

let responseString = "OK\r\nContent-Length" 

let nsRange = (responseString as NSString).range(of: "Content") 
print(nsRange.location, nsRange.length) // 4 7 

if let sRange1 = nsRange.toStringRange(responseString) { 
    print(responseString.substring(with: sRange1)) // "ontent-" 
} 

Используя метод

extension String { 
    func range(from nsRange: NSRange) -> Range<String.Index>? { 
     guard 
      let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), 
      let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex), 
      let from = String.Index(from16, within: self), 
      let to = String.Index(to16, within: self) 
      else { return nil } 
     return from ..< to 
    } 
} 

от NSRange to Range<String.Index> вы получите ожидаемый результат:

if let sRange2 = responseString.range(from: nsRange) { 
    print(responseString.substring(with: sRange2)) // "Content" 
}