【问题标题】:Swift 3 - How do I extract captured groups in regular expressions?Swift 3 - 如何在正则表达式中提取捕获的组?
【发布时间】:2017-08-05 00:32:48
【问题描述】:

我正在使用 Swift 3 并尝试访问捕获的组。

let regexp = "((ALREADY PAID | NOT ALR | PROVIDER MAY | READY | MAY BILL | BILL YOU | PAID)((.|\\n)*))(( \\d+)(\\.+|-+)(\\d\\d))"

// check if some substring is in the recognized text
if let range = stringText.range(of:regexp, options: .regularExpression) {
    let result = tesseract.recognizedText.substring(with:range)
}

我希望能够提取捕获的最后两个数字 (\d\d),因此如果文本是:ALREADY PAID asfasdfadsfasdf 39.15,它将提取 15。这是一个显示我想要的正则表达式生成器。通常,我可以使用$8 来获取第 8 组被提取的信息,但我不知道如何在 Swift 3 中执行此操作。

http://regexr.com/3fh1e

【问题讨论】:

标签: regex swift swift3


【解决方案1】:

但我不知道如何在 Swift 3 中做到这一点。

当你收到来自 NSRegularExpression 的匹配时,你得到的是一个 NSTextCheckingResult。您调用rangeAt 来获取特定的捕获组。

例子:

let s = "hey ho ha"
let pattern = "(h).*(h).*(h)"
// our goal is capture group 3, "h" in "ha"
let regex = try! NSRegularExpression(pattern: pattern)
let result = regex.matches(in:s, range:NSMakeRange(0, s.utf16.count))
let third = result[0].rangeAt(3) // <-- !!
third.location // 7
third.length // 1

【讨论】:

  • 第三个是NSRange,如何将其转换为Range类型,以便在s.substring中使用?否则,这里的“h”结果在哪里?将s转换为NSString的唯一方法是什么?有没有更简单的方法来使用正则表达式?这看起来太过分了。
  • @Efren NSRange 到字符串的范围转换是 Swift 4 的一项新功能。
  • 谢谢@matt,我想在那之前没有更简单的方法了。
  • @Efren 正则表达式是 Cocoa 的功能,而不是 Swift 的功能,这很烦人,但事实就是如此。显然,Cocoa 在 NSString 和 NSRange 中思考。但是在 Swift 4 中 Range 和 NSRange 即使对于字符串也是相互强制的,所以真的没问题。
  • @Efren:关于与 NSRegularExpression 相关的 NSRange/Range 转换,this Q&A 可能很有趣。
【解决方案2】:

斯威夫特 4、斯威夫特 5

extension String {
    func groups(for regexPattern: String) -> [[String]] {
    do {
        let text = self
        let regex = try NSRegularExpression(pattern: regexPattern)
        let matches = regex.matches(in: text,
                                    range: NSRange(text.startIndex..., in: text))
        return matches.map { match in
            return (0..<match.numberOfRanges).map {
                let rangeBounds = match.range(at: $0)
                guard let range = Range(rangeBounds, in: text) else {
                    return ""
                }
                return String(text[range])
            }
        }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}
}

示例:

let res = "1my 2own 3string".groups(for:"(([0-9]+)[a-z]+) ")

(lldb) 孔 ▿ 2 个元素
▿ 0 : 3 个元素

- 0 : "1my "

- 1 : "1my"

- 2 : "1"   

▿ 1 : 3 个元素

- 0 : "2own "

- 1 : "2own"

- 2 : "2"

【讨论】:

    【解决方案3】:

    与以往一样,一个简单的扩展似乎可以解决 swift 奇怪的过度复杂性...

    extension NSTextCheckingResult {
        func groups(testedString:String) -> [String] {
            var groups = [String]()
            for i in  0 ..< self.numberOfRanges
            {
                let group = String(testedString[Range(self.range(at: i), in: testedString)!])
                groups.append(group)
            }
            return groups
        }
    }
    

    像这样使用它:

    if let match = myRegex.firstMatch(in: someString, range: NSMakeRange(0, someString.count)) {
         let groups = match.groups(testedString: someString)
         //... do something with groups
    }
    

    【讨论】:

      【解决方案4】:

      基于@Vyacheslav 的回答,采用不同的错误处理方法略有改动的版本:

      enum ParsingError: Error {
          // You can pass more info here with parameter(s) if you want, e.g. `case let invalidRange(originalString, failedAtRange)`
          case invalidRange 
      }
      
      protocol StringUtilityRequired {
          var stringUtility: StringUtility { get }
      }
      
      extension StringUtilityRequired {
          var stringUtility: StringUtility { StringUtility() }
      }
      
      enum StringUtility {
          func groups(_ str: String, pattern: String) throws -> [[String]] {
              let regex = try NSRegularExpression(pattern: pattern)
              let matches = regex.matches(in: str, range: NSRange(str.startIndex..., in: str))
              return try matches.map { match throws in
                  return try (0 ..< match.numberOfRanges).map { range throws in
                      let rangeBounds = match.range(at: range)
                      guard let range = Range(rangeBounds, in: str) else {
                          throw ParsingError.invalidRange
                      }
                      return String(str[range])
                  }
              }
          }
      
          // This component is stateless; it doesn't have any side effect
          case pure
          init() { self = .pure }
      }
      

      用法:

      struct MyComponent: StringUtilityRequired {
          func myFunc() throws {
              let groups = try stringUtility.groups("Test 123", pattern: "(.+)\s(.+)")
              print(groups)
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-02
        • 2017-10-26
        • 2015-07-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多