Swift 提取正则表达式匹配

Posted

技术标签:

【中文标题】Swift 提取正则表达式匹配【英文标题】:Swift extract regex matches 【发布时间】:2015-01-10 20:04:04 【问题描述】:

我想从匹配正则表达式模式的字符串中提取子字符串。

所以我正在寻找这样的东西:

func matchesForRegexInText(regex: String!, text: String!) -> [String] 
   ???

这就是我所拥有的:

func matchesForRegexInText(regex: String!, text: String!) -> [String] 

    var regex = NSRegularExpression(pattern: regex, 
        options: nil, error: nil)

    var results = regex.matchesInString(text, 
        options: nil, range: NSMakeRange(0, countElements(text))) 
            as Array<NSTextCheckingResult>

    /// ???

    return ...

问题是,matchesInString 为我提供了一个 NSTextCheckingResult 数组,其中 NSTextCheckingResult.range 的类型为 NSRange

NSRangeRange&lt;String.Index&gt; 不兼容,所以它阻止我使用text.substringWithRange(...)

知道如何在没有太多代码行的情况下快速实现这个简单的事情吗?

【问题讨论】:

【参考方案1】:

即使matchesInString() 方法将String 作为第一个参数, 它在内部与NSString 一起工作,并且必须给出范围参数 使用 NSString 长度而不是 Swift 字符串长度。否则会 对于“扩展字素簇”(例如“标志”)失败。

Swift 4 (Xcode 9) 开始,Swift 标准 库提供了在Range&lt;String.Index&gt; 之间转换的函数 和NSRange

func matches(for regex: String, in text: String) -> [String] 

    do 
        let regex = try NSRegularExpression(pattern: regex)
        let results = regex.matches(in: text,
                                    range: NSRange(text.startIndex..., in: text))
        return results.map 
            String(text[Range($0.range, in: text)!])
        
     catch let error 
        print("invalid regex: \(error.localizedDescription)")
        return []
    

例子:

let string = "??€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]

注意: 强制解包 Range($0.range, in: text)! 是安全的,因为 NSRange 指的是给定字符串text 的子字符串。 但是,如果您想避免它,请使用

        return results.flatMap 
            Range($0.range, in: text).map  String(text[$0]) 
        

改为。


(Swift 3 及更早版本的旧答案:)

因此,您应该将给定的 Swift 字符串转换为 NSString,然后提取 范围。结果将自动转换为 Swift 字符串数组。

(Swift 1.2 的代码可以在编辑历史中找到。)

Swift 2 (Xcode 7.3.1):

func matchesForRegexInText(regex: String, text: String) -> [String] 

    do 
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = text as NSString
        let results = regex.matchesInString(text,
                                            options: [], range: NSMakeRange(0, nsString.length))
        return results.map  nsString.substringWithRange($0.range)
     catch let error as NSError 
        print("invalid regex: \(error.localizedDescription)")
        return []
    

例子:

let string = "??€4€9"
let matches = matchesForRegexInText("[0-9]", text: string)
print(matches)
// ["4", "9"]

Swift 3 (Xcode 8)

func matches(for regex: String, in text: String) -> [String] 

    do 
        let regex = try NSRegularExpression(pattern: regex)
        let nsString = text as NSString
        let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
        return results.map  nsString.substring(with: $0.range)
     catch let error 
        print("invalid regex: \(error.localizedDescription)")
        return []
    

例子:

let string = "??€4€9"
let matched = matches(for: "[0-9]", in: string)
print(matched)
// ["4", "9"]

【讨论】:

你让我免于发疯。不开玩笑。非常感谢! @MathijsSegers:我已经更新了 Swift 1.2/Xcode 6.3 的代码。谢谢你告诉我! 但是如果我想在标签之间搜索字符串怎么办?我需要相同的结果(匹配信息),例如:regex101.com/r/cU6jX8/2。你会建议哪种正则表达式模式? 更新是针对 Swift 1.2,而不是 Swift 2。代码不能用 Swift 2 编译。 谢谢!如果您只想提取正则表达式中 () 之间的实际内容怎么办?例如,在“[0-9]3([0-9]6)”中,我只想获取最后 6 个数字。【参考方案2】:

我的答案建立在给定答案之上,但通过添加额外支持使正则表达式匹配更加健壮:

不仅返回匹配项,而且还返回每个匹配项的所有捕获组(参见下面的示例) 此解决方案支持可选匹配,而不是返回空数组 通过不打印到控制台来避免do/catch使用guard 构造matchingStrings 添加为String扩展

斯威夫特 4.2

//: Playground - noun: a place where people can play

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.range(at: $0).location != NSNotFound
                    ? nsString.substring(with: result.range(at: $0))
                    : ""
            
        
    


"prefix12 aaa3 prefix45".matchingStrings(regex: "fix([0-9])([0-9])")
// Prints: [["fix12", "1", "2"], ["fix45", "4", "5"]]

"prefix12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["prefix12", "12"]]

"12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["12", "12"]], other answers return an empty array here

// Safely accessing the capture of the first match (if any):
let number = "prefix12suffix".matchingStrings(regex: "fix([0-9]+)su").first?[1]
// Prints: Optional("12")

斯威夫特 3

//: Playground - noun: a place where people can play

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))
                    : ""
            
        
    


"prefix12 aaa3 prefix45".matchingStrings(regex: "fix([0-9])([0-9])")
// Prints: [["fix12", "1", "2"], ["fix45", "4", "5"]]

"prefix12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["prefix12", "12"]]

"12".matchingStrings(regex: "(?:prefix)?([0-9]+)")
// Prints: [["12", "12"]], other answers return an empty array here

// Safely accessing the capture of the first match (if any):
let number = "prefix12suffix".matchingStrings(regex: "fix([0-9]+)su").first?[1]
// Prints: Optional("12")

斯威夫特 2

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.matchesInString(self, options: [], range: NSMakeRange(0, nsString.length))
        return results.map  result in
            (0..<result.numberOfRanges).map 
                result.rangeAtIndex($0).location != NSNotFound
                    ? nsString.substringWithRange(result.rangeAtIndex($0))
                    : ""
            
        
    

【讨论】:

关于捕获组的好主意。但是为什么“守卫”比“做/抓”更敏捷?? 我同意 nshipster.com/guard-and-defer 这样的人的观点,他们说 Swift 2.0 似乎确实鼓励了一种提前返回的风格 [...] 而不是嵌套的 if 语句。恕我直言,嵌套的 do/catch 语句也是如此。 try/catch 是 Swift 中的原生错误处理。 try? 如果您只对调用结果感兴趣,而不是对可能的错误消息感兴趣,则可以使用。所以是的,guard try? .. 很好,但是如果你想打印错误,那么你需要一个 do-block。两种方式都是 Swifty。 我已将单元测试添加到您漂亮的 sn-p,gist.github.com/neoneye/03cbb26778539ba5eb609d16200e4522 在我看到这个之前,我正要根据@MartinR 的答案来写我自己的。谢谢!【参考方案3】:

在 Swift 5 中返回所有匹配项和捕获组的最快方法

extension String 
    func match(_ regex: String) -> [[String]] 
        let nsString = self as NSString
        return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)).map  match in
            (0..<match.numberOfRanges).map  match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) 
         ?? []
    

返回一个二维字符串数组:

"prefix12suffix fix1su".match("fix([0-9]+)su")

返回...

[["fix12su", "12"], ["fix1su", "1"]]

// First element of sub-array is the match
// All subsequent elements are the capture groups

【讨论】:

真的需要options: []吗?【参考方案4】:

如果你想从字符串中提取子字符串,不仅仅是位置,(而是实际的字符串,包括表情符号)。那么,以下可能是一个更简单的解决方案。

extension String 
  func regex (pattern: String) -> [String] 
    do 
      let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpressionOptions(rawValue: 0))
      let nsstr = self as NSString
      let all = NSRange(location: 0, length: nsstr.length)
      var matches : [String] = [String]()
      regex.enumerateMatchesInString(self, options: NSMatchingOptions(rawValue: 0), range: all) 
        (result : NSTextCheckingResult?, _, _) in
        if let r = result 
          let result = nsstr.substringWithRange(r.range) as String
          matches.append(result)
        
      
      return matches
     catch 
      return [String]()
    
  
 

示例用法:

"someText ???⚽️ pig".regex("?⚽️")

将返回以下内容:

["?⚽️"]

注意使用“\w+”可能会产生意想不到的“”

"someText ???⚽️ pig".regex("\\w+")

将返回这个字符串数组

["someText", "️", "pig"]

【讨论】:

这就是我想要的 不错!它需要对 Swift 3 进行一些调整,但它很棒。 @Jelle 它需要什么调整?我正在使用 swift 5.1.3【参考方案5】:

不幸的是,我发现已接受答案的解决方案无法在 Swift 3 for Linux 上编译。那么,这是一个修改后的版本:

import Foundation

func matches(for regex: String, in text: String) -> [String] 
    do 
        let regex = try RegularExpression(pattern: regex, options: [])
        let nsString = NSString(string: text)
        let results = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsString.length))
        return results.map  nsString.substring(with: $0.range) 
     catch let error 
        print("invalid regex: \(error.localizedDescription)")
        return []
    

主要区别在于:

    Linux 上的 Swift 似乎需要在 Foundation 对象上删除 NS 前缀,而没有 Swift 原生的等效对象。 (见Swift evolution proposal #86。)

    Linux 上的 Swift 还需要为 RegularExpression 初始化和 matches 方法指定 options 参数。

    出于某种原因,将 String 强制转换为 NSString 在 Linux 上的 Swift 中不起作用,但在源代码中使用 String 初始化新的 NSString 确实有效。

此版本也适用于 macOS / Xcode 上的 Swift 3,唯一的例外是您必须使用名称 NSRegularExpression 而不是 RegularExpression

【讨论】:

【参考方案6】:

没有 NSString 的 Swift 4。

extension String 
    func matches(regex: String) -> [String] 
        guard let regex = try? NSRegularExpression(pattern: regex, options: [.caseInsensitive]) else  return [] 
        let matches  = regex.matches(in: self, options: [], range: NSMakeRange(0, self.count))
        return matches.map  match in
            return String(self[Range(match.range, in: self)!])
        
    

【讨论】:

小心上述解决方案:NSMakeRange(0, self.count) 不正确,因为selfString (=UTF8) 而不是NSString (=UTF16)。所以self.count 不一定与nsString.length 相同(在其他解决方案中使用)。您可以将范围计算替换为NSRange(self.startIndex..., in: self)【参考方案7】:

@p4bloch 如果要从一系列捕获括号中捕获结果,则需要使用NSTextCheckingResultrangeAtIndex(index) 方法,而不是range。这是上面的 @MartinR 用于 Swift2 的方法,适用于捕获括号。在返回的数组中,第一个结果[0] 是整个捕获,然后各个捕获组从[1] 开始。我注释掉了map 操作(这样更容易看到我所做的更改)并将其替换为嵌套循环。

func matches(for regex: String!, in text: String!) -> [String] 

    do 
        let regex = try NSRegularExpression(pattern: regex, options: [])
        let nsString = text as NSString
        let results = regex.matchesInString(text, options: [], range: NSMakeRange(0, nsString.length))
        var match = [String]()
        for result in results 
            for i in 0..<result.numberOfRanges 
                match.append(nsString.substringWithRange( result.rangeAtIndex(i) ))
            
        
        return match
        //return results.map  nsString.substringWithRange( $0.range ) //rangeAtIndex(0)
     catch let error as NSError 
        print("invalid regex: \(error.localizedDescription)")
        return []
    

一个示例用例可能是,假设您想拆分一串title year,例如“Finding Dory 2016”,您可以这样做:

print ( matches(for: "^(.+)\\s(\\d4)" , in: "Finding Dory 2016"))
// ["Finding Dory 2016", "Finding Dory", "2016"]

【讨论】:

这个答案让我很开心。我花了 2 个小时寻找可以通过额外捕获组来满足正则表达式的解决方案。 这可行,但如果找不到任何范围,它将崩溃。我修改了这段代码,使函数返回[String?],并且在for i in 0..&lt;result.numberOfRanges 块中,您必须添加一个测试,该测试仅在范围!= NSNotFound 时附加匹配,否则它应该附加零。见:***.com/a/31892241/2805570【参考方案8】:

上面的大多数解决方案只给出完全匹配,结果忽略了捕获组,例如:^\d+\s+(\d+)

要按预期获得捕获组匹配,您需要类似 (Swift4) :

public extension String 
    public func capturedGroups(withRegex pattern: String) -> [String] 
        var results = [String]()

        var regex: NSRegularExpression
        do 
            regex = try NSRegularExpression(pattern: pattern, options: [])
         catch 
            return results
        
        let matches = regex.matches(in: self, options: [], range: NSRange(location:0, length: self.count))

        guard let match = matches.first else  return results 

        let lastRangeIndex = match.numberOfRanges - 1
        guard lastRangeIndex >= 1 else  return results 

        for i in 1...lastRangeIndex 
            let capturedGroupIndex = match.range(at: i)
            let matchedString = (self as NSString).substring(with: capturedGroupIndex)
            results.append(matchedString)
        

        return results
    

【讨论】:

如果您只想要第一个结果,这很好,以获得它需要的每个结果 for index in 0..&lt;matches.count 大约 let lastRange... results.append(matchedString) for 子句应如下所示:for i in 1...lastRangeIndex let capturedGroupIndex = match.range(at: i) if capturedGroupIndex.location != NSNotFound let matchedString = (self as NSString).substring(with: capturedGroupIndex) results.append(matchedString.trimmingCharacters(in: .whitespaces)) 【参考方案9】:

我就是这样做的,我希望它能带来一个新的视角,它是如何在 Swift 上工作的。

在下面的这个例子中,我将得到[]之间的任意字符串

var sample = "this is an [hello] amazing [world]"

var regex = NSRegularExpression(pattern: "\\[.+?\\]"
, options: NSRegularExpressionOptions.CaseInsensitive 
, error: nil)

var matches = regex?.matchesInString(sample, options: nil
, range: NSMakeRange(0, countElements(sample))) as Array<NSTextCheckingResult>

for match in matches 
   let r = (sample as NSString).substringWithRange(match.range)//cast to NSString is required to match range format.
    println("found= \(r)")

【讨论】:

【参考方案10】:

这是一个非常简单的解决方案,它返回一个带有匹配项的字符串数组

斯威夫特 3.

internal func stringsMatching(regularExpressionPattern: String, options: NSRegularExpression.Options = []) -> [String] 
        guard let regex = try? NSRegularExpression(pattern: regularExpressionPattern, options: options) else 
            return []
        

        let nsString = self as NSString
        let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))

        return results.map 
            nsString.substring(with: $0.range)
        
    

【讨论】:

小心使用 NSMakeRange(0, self.count),因为 self 是一个字符串 (=UTF8) 而不是 NSString (=UTF16)。因此 self.count 不一定与 nsString.length 相同(如在其他解决方案中使用的那样)。您可以将范围计算替换为 NSRange(self.startIndex..., in: self)。【参考方案11】:

非常感谢 Lars Blumberg 他的 answer 使用 Swift 4 捕获组和完整匹配,这对我有很大帮助。当他们的正则表达式无效时,我还为那些确实想要 error.localizedDescription 响应的人添加了它:

extension String 
    func matchingStrings(regex: String) -> [[String]] 
        do 
            let regex = try NSRegularExpression(pattern: regex)
            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.range(at: $0).location != NSNotFound
                        ? nsString.substring(with: result.range(at: $0))
                        : ""
                
            
         catch let error 
            print("invalid regex: \(error.localizedDescription)")
            return []
        
    

对我来说,将本地化描述作为错误有助于理解转义出了什么问题,因为它显示了最终的正则表达式 swift 尝试实现的。

【讨论】:

【参考方案12】:

将@Mike Chirico 更新为Swift 5

extension String



  func regex(pattern: String) -> [String]?
    do 
        let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options(rawValue: 0))
        let all = NSRange(location: 0, length: count)
        var matches = [String]()
        regex.enumerateMatches(in: self, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: all) 
            (result : NSTextCheckingResult?, _, _) in
              if let r = result 
                    let nsstr = self as NSString
                    let result = nsstr.substring(with: r.range) as String
                    matches.append(result)
              
        
        return matches
     catch 
        return nil
    
  

【讨论】:

【参考方案13】:

基本电话号码匹配

let phoneNumbers = ["+79990001101", "+7 (800) 000-11-02", "+34 507 574 147 ", "+1-202-555-0118"]

let match: (String) -> String = 
    $0.replacingOccurrences(of: #"[^\d+]"#, with: "", options: .regularExpression)


print(phoneNumbers.map(match))
// ["+79990001101", "+78000001102", "+34507574147", "+12025550118"]

【讨论】:

以上是关于Swift 提取正则表达式匹配的主要内容,如果未能解决你的问题,请参考以下文章

swift Swift提取正则表达式匹配

Swift提取正则表达式匹配

Regex 正则表达式中几个符号([ ] ^ ?: ?= ?!)的概念

正则表达式如何匹配提取括号中的内容

C#正则表达式怎样提取匹配到的数据???

Swift 正则表达式匹配