2017-11-09 12 views
0

私は、CRLF、およびCRLFの改行セパレータを含むテキストがあるとします。iOS:文字列。絶対位置から行番号と列番号を取得する

このように:"\n \n Lorem \r Ipsum \n is \r\n simply \n dummy \r\n text of \n the printing \r and typesetting industry. \n \n"

このテキストをテキストエディタ(NSTextView/UITextView)に読み込んでいます。 視覚的な改行セパレータは同じように見えます。ただ新しい行。私は単純なテキストエディタ、テキストを選択し、カット、コピー、ペースト、内のテキストをナビゲートすることができます

...

質問:私は取得できますかlinecolumnabsolute文字位置から(すなわち選択NSRange )?また、absoluteの文字の位置は、既知のlinecolumnの番号を取得するにはどうすればよいですか?

ありがとうございます!


UPDATE 1

  • linecolumn数 - シンプルカーソル位置を意味します。
  • lineおよびcolumn番号 - の番号を1つ指定します。
  • absolute文字の位置 - ゼロベースの番号付け。

現在のソリューションのサンプルコード。これは、linecolumnの数値からabsoluteの文字の位置を計算し、その逆の場合も同様です。しかし、テキストの変更に関するマッピングは再計算されません。

struct TextString { 

    struct Cursor { 
     let line: Int 
     let column: Int 
    } 

    struct Mapping { 
     let lineNumber: Int 
     let lineLength: Int 
     let absolutePosition: Int 

     fileprivate var absoluteStart: Int { 
     return absolutePosition - lineLength 
     } 
    } 

    let string: String 
    private (set) var mappings: [Mapping] = [] 

    init(string: String) { 
     self.string = string 
     mappings = setupMappings() 
    } 
} 

extension TextString { 

    func cursor(from position: Int) -> Cursor? { 
     guard position > 0 else { 
     return nil 
     } 
     guard let mapping = mappings.first(where: { $0.absolutePosition >= position && $0.absoluteStart <= position }) else { 
     return nil 
     } 
     let result = Cursor(line: mapping.lineNumber, column: position - mapping.absoluteStart) 
     return result 
    } 

    func position(from cursor: Cursor) -> Int? { 
     guard let line = mappings.element(at: cursor.line - 1) else { 
     return nil 
     } 
     guard line.lineLength >= cursor.column else { 
     return nil 
     } 
     let result = line.absoluteStart + cursor.column 
     return result 
    } 
} 

extension TextString { 

    private func setupMappings() -> [Mapping] { 
     var mappings: [Mapping] = [] 
     var line = 1 
     var previousAbsolutePosition = 0 
     var delta = 0 
     let scanner = Scanner(string: string) 
     scanner.charactersToBeSkipped = nil 
     while !scanner.isAtEnd { 
     if scanner.scanUpToCharacters(from: .newlines) != nil { 
      let charactersLocation = scanner.scanLocation - delta 
      if let newLines = scanner.scanCharacters(from: .newlines) { 
       for index in 0..<newLines.count { 
        let absolutePosition = charactersLocation + 1 + index // `+1` is newLine itself 
        mappings.append(Mapping(lineNumber: line, lineLength: absolutePosition - previousAbsolutePosition, 
              absolutePosition: absolutePosition)) 
        previousAbsolutePosition = absolutePosition 
        line += 1 
       } 
       delta = scanner.scanLocation - previousAbsolutePosition 
      } else { 
       // Only happens when we at last line withot newline. 
       let absolutePosition = charactersLocation 
       mappings.append(Mapping(lineNumber: line, lineLength: absolutePosition - previousAbsolutePosition, 
             absolutePosition: absolutePosition)) 
       line += 1 
       previousAbsolutePosition = charactersLocation 
      } 
     } else if let newLines = scanner.scanCharacters(from: .newlines) { // Text begins with new lines. 
      for index in 0..<newLines.count { 
       let absolutePosition = 1 + index // `+1` is newLine itself 
       mappings.append(Mapping(lineNumber: line, lineLength: absolutePosition - previousAbsolutePosition, 
             absolutePosition: absolutePosition)) 
       previousAbsolutePosition = absolutePosition 
       line += 1 
      } 
      delta = scanner.scanLocation - previousAbsolutePosition 
     } 
     } 
     assert(previousAbsolutePosition == string.count) 
     return mappings 
    } 
} 

UPDATE 2:正規表現のバージョン。

private func setupMappingsUsingRegex() throws -> [Mapping] { 
    if string.isEmpty { 
     return [] 
    } 
    var mappings: [Mapping] = [] 
    let regex = try NSRegularExpression(pattern: "(\\r\\n)|(\\n)|(\\r)") 
    let matches = regex.matches(in: string, range: NSRange(location: 0, length: string.unicodeScalars.count)) 
    var line = 1 
    var previousAbsolutePosition = 0 
    var delta = 0 

    // String without any newline. 
    if matches.isEmpty { 
     let mapping = Mapping(lineNumber: 1, lineLength: string.count, absolutePosition: string.count) 
     mappings.append(mapping) 
     return mappings 
    } 

    for match in matches { 
     let absolutePosition = match.range.location - delta + 1 
     let mapping = Mapping(lineNumber: line, lineLength: absolutePosition - previousAbsolutePosition, 
          absolutePosition: absolutePosition) 
     mappings.append(mapping) 
     delta += match.range.length - 1 
     previousAbsolutePosition = absolutePosition 
     line += 1 
    } 

    // Rest of the string without newline at the end. 
    if previousAbsolutePosition < string.count { 
     let mapping = Mapping(lineNumber: line, lineLength: string.count - previousAbsolutePosition, 
          absolutePosition: string.count) 
     mappings.append(mapping) 
     previousAbsolutePosition = string.count 
    } 
    assert(previousAbsolutePosition == string.count) 
    return mappings 
} 

パフォーマンス:22400文字(200行)は1000倍を分析しました。

  • 正規表現:5.120s
  • スキャナ:6.603s
+0

など、あなたがマッチした結果の中のループその後、あなたは\n\r\r\nを見れば、正規表現は

var content: String = <Your text here> let regex = try! NSRegularExpression(pattern: "(\\n)|(\\r)|(\\r\\n)") let matchs = regex.matches(in: content, range: NSRange(location: 0, length: content.count)).map{(content as NSString).substring(with: $0.range)} 

ようになります部分文字列を分割したいことができると言うとインデックス&範囲を取得しますhttps://stackoverflow.com/questions/31746223/swift-number-of-occurrences-of-substring-in-stringを使って、 '\ r \ n'(最初)、次に' \ n'の数を数えます\ 'r」を指定すると、「0からabsoluteLocation」の部分文字列から必要な情報を得ることができるはずですが、iterationを使ってabsoluteLocationを取得するのと同じ方法ですか?列 "を指定することもできますが、単純に「s」と言ってみましょう。 – Larme

+1

行末が混在しているのは非官能的です。私は、テキストビューは\ nだけを認識すると信じています。行末を\ nだけ正規化して問題を解決する必要があります。 – PhoneyDeveloper

答えて

2

私はあなたが正規表現を使用して文字列を分離することをお勧めします。私は思います

+0

残念なことに、 'components(separatedBy:.newlines)'は** 2個の見えない文字 '\ r \ n'を食べ、残りの計算されたインデックスは** eaten **改行の数で整列しません:0 – Vlad

+1

@LeoDabus 'unicodeScalars'が動作します。真実。 – Vlad

関連する問題