NSRegularExpression中命名的捕获组 - 获取范围的组名称

问题描述 投票:7回答:3

Apple称NSRegularExpression基于ICU正则表达式库:https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSRegularExpression_Class/

当前支持的模式语法是ICU指定的语法。 ICU正则表达式在http://userguide.icu-project.org/strings/regexp中描述。

该页面(在icu-project.org上)声称现在支持命名捕获组,使用与.NET Regular Expressions相同的语法:

(?<name>...)命名为捕获组。 <angle brackets>是字面意思 - 它们出现在模式中。

我编写了一个程序,它获得了一个看似正确的多个范围的匹配 - 虽然每个范围都返回两次(原因未知) - 但我唯一的信息是范围索引及其文本范围。

例如,正则表达式:^(?<foo>foo)\.(?<bar>bar)\.(?<bar2>baz)$与测试字符串foo.bar.baz

给我这些结果:

Idx    Start    Length     Text
0      0        11         foo.bar.baz
1      0         3         foo
2      4         3         bar
3      8         3         baz

有没有办法知道“baz”来自捕获组bar2

ios regex nsregularexpression icu
3个回答
4
投票

由于支持iOS11命名的捕获组。 NSTextCheckingResult具有open func range(withName name: String) -> NSRange功能。

使用正则表达式:^(?<foo>foo)\.(?<bar>bar)\.(?<bar2>baz)$和测试字符串foo.bar.baz给出4个结果匹配。函数match.range(withName: "bar2")返回String baz的范围


1
投票

我面临同样的问题,最终支持我自己的解决方案。随意评论或改进;-)

extension NSRegularExpression {
    typealias GroupNamesSearchResult = (NSTextCheckingResult, NSTextCheckingResult, Int)

    private func textCheckingResultsOfNamedCaptureGroups() throws -> [String:GroupNamesSearchResult] {
        var groupnames = [String:GroupNamesSearchResult]()

        let greg = try NSRegularExpression(pattern: "^\\(\\?<([\\w\\a_-]*)>.*\\)$", options: NSRegularExpressionOptions.DotMatchesLineSeparators)
        let reg = try NSRegularExpression(pattern: "\\([^\\(\\)]*\\)", options: NSRegularExpressionOptions.DotMatchesLineSeparators)
        let m = reg.matchesInString(self.pattern, options: NSMatchingOptions.WithTransparentBounds, range: NSRange(location: 0, length: self.pattern.utf16.count))
        for (n,g) in m.enumerate() {
            let gstring = self.pattern.substringWithRange(g.rangeAtIndex(0).toRange()!)
            print(self.pattern.substringWithRange(g.rangeAtIndex(0).toRange()!))
            let gmatch = greg.matchesInString(gstring, options: NSMatchingOptions.Anchored, range: NSRange(location: 0, length: gstring.utf16.count))
            if gmatch.count > 0{
                groupnames[gstring.substringWithRange(gmatch[0].rangeAtIndex(1).toRange()!)] = (g,gmatch[0],n)
            }

        }
        return groupnames
    }
    func indexOfNamedCaptureGroups() throws -> [String:Int] {
        var groupnames = [String:Int]()
        for (name,(_,_,n)) in try self.textCheckingResultsOfNamedCaptureGroups() {
            groupnames[name] = n + 1
        }
        //print(groupnames)
        return groupnames
    }

    func rangesOfNamedCaptureGroups(match:NSTextCheckingResult) throws -> [String:Range<Int>] {
        var ranges = [String:Range<Int>]()
        for (name,(_,_,n)) in try self.textCheckingResultsOfNamedCaptureGroups() {
            ranges[name] = match.rangeAtIndex(n+1).toRange()
        }
        return ranges
    }
}

这是一个用法示例:

let node = "'test_literal'"
let regex = try NSRegularExpression(pattern: "^(?<delimiter>'|\")(?<value>.*)(?:\\k<delimiter>)$", options: NSRegularExpressionOptions.DotMatchesLineSeparators)
let match = regex.matchesInString(node, options: NSMatchingOptions.Anchored, range: NSRange(location: 0,length: node.utf16.count))
if match.count > 0 {

    let ranges = try regex.rangesOfNamedCaptureGroups(match[0])
    guard let range = ranges["value"] else {

    }
}

1
投票

我参与了由Daniele Bernardini创建的示例。

有很多变化:

  • 首先,代码现在与Swift 3兼容
  • Daniele的代码有一个缺陷,它不会捕获嵌套的捕获。我使正则表达式稍微不那么激进,以允许嵌套捕获组。
  • 我更喜欢实际接收Set中的实际捕获。我添加了一个名为captureGroups()的方法,它将捕获作为字符串而不是范围返回。 import Foundation extension String { func matchingStrings(regex: String) -> [[String]] { guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] } let nsString = self as NSString let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)) return results.map { result in (0..<result.numberOfRanges).map { result.rangeAt($0).location != NSNotFound ? nsString.substring(with: result.rangeAt($0)) : "" } } } 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(utf16.startIndex, offsetBy: nsRange.location + nsRange.length, limitedBy: utf16.endIndex), let from = from16.samePosition(in: self), let to = to16.samePosition(in: self) else { return nil } return from ..< to } } extension NSRegularExpression { typealias GroupNamesSearchResult = (NSTextCheckingResult, NSTextCheckingResult, Int) private func textCheckingResultsOfNamedCaptureGroups() -> [String:GroupNamesSearchResult] { var groupnames = [String:GroupNamesSearchResult]() guard let greg = try? NSRegularExpression(pattern: "^\\(\\?<([\\w\\a_-]*)>$", options: NSRegularExpression.Options.dotMatchesLineSeparators) else { // This never happens but the alternative is to make this method throwing return groupnames } guard let reg = try? NSRegularExpression(pattern: "\\(.*?>", options: NSRegularExpression.Options.dotMatchesLineSeparators) else { // This never happens but the alternative is to make this method throwing return groupnames } let m = reg.matches(in: self.pattern, options: NSRegularExpression.MatchingOptions.withTransparentBounds, range: NSRange(location: 0, length: self.pattern.utf16.count)) for (n,g) in m.enumerated() { let r = self.pattern.range(from: g.rangeAt(0)) let gstring = self.pattern.substring(with: r!) let gmatch = greg.matches(in: gstring, options: NSRegularExpression.MatchingOptions.anchored, range: NSRange(location: 0, length: gstring.utf16.count)) if gmatch.count > 0{ let r2 = gstring.range(from: gmatch[0].rangeAt(1))! groupnames[gstring.substring(with: r2)] = (g, gmatch[0],n) } } return groupnames } func indexOfNamedCaptureGroups() throws -> [String:Int] { var groupnames = [String:Int]() for (name,(_,_,n)) in try self.textCheckingResultsOfNamedCaptureGroups() { groupnames[name] = n + 1 } return groupnames } func rangesOfNamedCaptureGroups(match:NSTextCheckingResult) throws -> [String:Range<Int>] { var ranges = [String:Range<Int>]() for (name,(_,_,n)) in try self.textCheckingResultsOfNamedCaptureGroups() { ranges[name] = match.rangeAt(n+1).toRange() } return ranges } private func nameForIndex(_ index: Int, from: [String:GroupNamesSearchResult]) -> String? { for (name,(_,_,n)) in from { if (n + 1) == index { return name } } return nil } func captureGroups(string: String, options: NSRegularExpression.MatchingOptions = []) -> [String:String] { return captureGroups(string: string, options: options, range: NSRange(location: 0, length: string.utf16.count)) } func captureGroups(string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> [String:String] { var dict = [String:String]() let matchResult = matches(in: string, options: options, range: range) let names = try self.textCheckingResultsOfNamedCaptureGroups() for (n,m) in matchResult.enumerated() { for i in (0..<m.numberOfRanges) { let r2 = string.range(from: m.rangeAt(i))! let g = string.substring(with: r2) if let name = nameForIndex(i, from: names) { dict[name] = g } } } return dict } }

使用新方法captureGroups()的一个例子是:

    let node = "'test_literal'"
    let regex = try NSRegularExpression(pattern: "^(?<all>(?<delimiter>'|\")(?<value>.*)(?:\\k<delimiter>))$", options: NSRegularExpression.Options.dotMatchesLineSeparators)
    let match2 = regex.captureGroups(string: node, options: NSRegularExpression.MatchingOptions.anchored)
    print(match2)

它将打印:

[“delimiter”:“\'”,“all”:“\'test_literal \'”,“value”:“test_literal”]

© www.soinside.com 2019 - 2024. All rights reserved.