在NSRegularExpression中命名捕获组 – 获取一个范围的组名

苹果说,NSRegularExpression是基于ICU正则expression式库: https : //developer.apple.com/library/ios/documentation/Foundation/Reference/NSRegularExpression_Class/

目前支持的模式语法是由ICU指定的。 ICU的正则expression式在http://userguide.icu-project.org/strings/regexp上有描述。

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

(?<name>...)命名的捕获组。 <angle brackets>是文字 – 它们出现在模式中。

我已经写了一个程序,得到一个单一的匹配与多个范围似乎是正确的 – 虽然每个范围返回两次(原因未知) – 但唯一的信息是范围的索引和文本范围。

例如,正则expression式: ^(?<foo>foo)\.(?<bar>bar)\.(?<bar2>baz)$ with test string 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吗?

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

 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 { } } 

我曾经在Daniele Bernardini创造的例子上工作过。

有一些变化:

  • 首先,代码现在与Swift 3兼容
  • Daniele的代码有一个缺陷,它不会捕获嵌套的捕获。 我已经使正则expression式略微不积极,以允许嵌套捕获组。
  • 我更喜欢实际收到一个集合中的实际捕获。 我添加了一个名为captureGroups()的方法,它以string而不是范围的forms返回捕获。

     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”]

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

使用正则expression式: ^(?<foo>foo)\.(?<bar>bar)\.(?<bar2>baz)$与testingstringfoo.bar.baz给出4个结果匹配。 函数match.range(withName: "bar2")返回String baz的范围