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

Posted

技术标签:

【中文标题】NSRegularExpression 中的命名捕获组 - 获取范围组的名称【英文标题】:Named capture groups in NSRegularExpression - get a range's group's name 【发布时间】:2016-06-20 19:00:03 【问题描述】:

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 正则表达式相同的语法:

(?<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

【问题讨论】:

你看过Named capture groups with NSRegularExpression吗? @Thomas 我确实看到了,但是它是从 2014 年开始的,并且讨论都说不支持命名捕获组 - 但是(至少在 iOS 9 和 OS X 10.11 上)确实似乎是支持 - 他们至少在我的机器上工作,我只是无法从结果范围映射回他们来自的组。 以及 Apple 文档状态 在 iOS 4.0 及更高版本中可用... @Thomas 文档指出 NSRegularExpression 类已添加到 iOS 4.0,而不是 iOS 4.0 中添加了对命名捕获组的支持。 @Thomas 实际上,Apple 自己的 NSRegularExpression 文档没有列出命名捕获组的语法,它只出现在 ICU 自己的文档中,这表明命名捕获组是最近添加的。跨度> 【参考方案1】:

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

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

【讨论】:

我在此基础上编写了一个扩展,以从所有命名的捕获组及其值中创建一个字典:***.com/a/48309290/235297【参考方案2】:

我已经研究了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"]

【讨论】:

谢谢你,你救了我。【参考方案3】:

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

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 

    

【讨论】:

有趣!您能否通过一些用法示例修改您的答案? 完成。再看一遍后,我也意识到这并不明显......几个月前编写了这段代码,有理由在 2 个单独的调用中进行匹配和范围。

以上是关于NSRegularExpression 中的命名捕获组 - 获取范围组的名称的主要内容,如果未能解决你的问题,请参考以下文章

优化算法海洋捕食者算法(MPA)matlab源码

NSPredicate 和 NSRegularExpression

NSRegularExpression如何让NSRange超出范围?

非贪婪的 NSRegularExpression

如何在Objective C(NSRegularExpression)中编写正则表达式?

捕获组在 NSRegularExpression 中不起作用