私は、CR
、LF
、および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
)に読み込んでいます。 視覚的な改行セパレータは同じように見えます。ただ新しい行。私は単純なテキストエディタ、テキストを選択し、カット、コピー、ペースト、内のテキストをナビゲートすることができます
...
質問:私は取得できますかline
とcolumn
数absolute
文字位置から(すなわち選択NSRange )?また、absolute
の文字の位置は、既知のline
とcolumn
の番号を取得するにはどうすればよいですか?
ありがとうございます!
UPDATE 1:
line
とcolumn
数 - シンプルカーソル位置を意味します。line
およびcolumn
番号 - の番号を1つ指定します。absolute
文字の位置 - ゼロベースの番号付け。
現在のソリューションのサンプルコード。これは、line
とcolumn
の数値から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
など、あなたがマッチした結果の中のループその後、あなたは
\n
、\r
と\r\n
を見れば、正規表現はようになります部分文字列を分割したいことができると言うとインデックス&範囲を取得しますhttps://stackoverflow.com/questions/31746223/swift-number-of-occurrences-of-substring-in-stringを使って、 '\ r \ n'(最初)、次に' \ n'の数を数えます\ 'r」を指定すると、「0からabsoluteLocation」の部分文字列から必要な情報を得ることができるはずですが、iterationを使ってabsoluteLocationを取得するのと同じ方法ですか?列 "を指定することもできますが、単純に「s」と言ってみましょう。 – Larme
行末が混在しているのは非官能的です。私は、テキストビューは\ nだけを認識すると信じています。行末を\ nだけ正規化して問題を解決する必要があります。 – PhoneyDeveloper