假设我的文字包含混合的CR
,LF
和CRLF
换行符。
像这样:"\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
字符位置(即选择NSRange)获取column
和absolute
号码?此外,如何从已知的absolute
和line
号码中获取column
字符位置?
谢谢!
更新1 :
line
和column
数字 - 简单表示光标位置。line
和column
号码 - 包含一个编号。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
}
}
更新2 :RegEx版本。
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次。
答案 0 :(得分:2)
我建议你使用正则表达式来分隔你的字符串。假设您想要分割子字符串,如果看到\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)}
然后你可以在匹配的结果中循环并得到索引&amp;范围等
答案 1 :(得分:0)
这对我有用,以获得行号:
let content: String = <YourString>
let selectionRange: NSRange = <YourTextRange>
let regex = try! NSRegularExpression(pattern: "\n", options: [])
let lineNumber = regex.numberOfMatches(in: content, options: [], range: NSMakeRange(0, nsRange.location)) + 1
您可以自定义正则表达式以匹配所需的任何换行符。
要获取列号,您可以获取行范围,然后从选择范围中减去它的开头:
let lineRange = content.lineRange(for: selectionRange.location)
let column = selectionRange.location - lineRange.location