正则表达式

一个 Swift 的正则表达式库

简介

此库使用 NSRegularExpression 来执行正则表达式模式匹配的实际逻辑。 但是,它提供了一个更简洁的接口,并且专门设计为充分利用 Swift。 正则表达式支持的语法可以在这里找到。您还应该查看 NSRegularExpression.OptionsNSRegularExpression.MatchingOptions。 我建议使用 https://regex101.com/ 来测试您的正则表达式模式。

安装

Swift Package Manager (推荐)

  1. 在 Xcode 中,打开要将此包添加到其中的项目。
  2. 从菜单栏中,选择 File > Swift Packages > Add Package Dependency...
  3. 将此存储库的 URL 粘贴到搜索字段中。
  4. 按照提示添加包。

Cocoa Pods

将其添加到您的 Podfile

pod 'RegularExpressions', :git => 'https://github.com/Peter-Schorn/RegularExpressions.git'

使用正则表达式对象

String.regexMatchString.regexFindAllString.regexSubString.regexSplit 都接受符合 RegexProtocol 的对象。 此对象包含有关正则表达式的信息,包括

RegexProtocol 还定义了许多便利方法

func asNSRegex() throws -> NSRegularExpression
将自身转换为 NSRegularExpression。

func numberOfCaptureGroups() throws -> Int
返回正则表达式中捕获组的数量。

func patternIsValid() -> Bool
如果正则表达式模式有效,则返回 true。 否则返回 false。

NSRegularExpression 已被扩展为符合 RegexProtocol,但它对 matchingOptionsgroupNames 属性**总是**返回 []nil。 使用 Regex 或符合 RegexProtocol 的其他类型来自定义这些选项。

此库提供的 Regex 结构体符合 RegexProtocol,并且是创建可与此库中的方法一起使用的正则表达式对象的最简单方法。

初始化 Regex 结构体

init(
    pattern: String,
    regexOptions: NSRegularExpression.Options = [],
    matchingOptions: NSRegularExpression.MatchingOptions = [],
    groupNames: [String]? = nil
) throws

如果模式无效则抛出。

init(
    _ pattern: String,
    _ regexOptions: NSRegularExpression.Options = []
) throws

如果模式无效则抛出。

init(
    nsRegularExpression: NSRegularExpression,
    matchingOptions: NSRegularExpression.MatchingOptions = [],
    groupNames: [String]? = nil
)

NSRegularExpression 创建 Regex 对象。

提取匹配项和捕获组

String.regexMatchString.regexFindAll 都使用 RegexMatch 结构体来保存有关正则表达式匹配的信息。 它包含以下属性

RegexMatch 还有一个按名称检索组的方法
func group(named name: String) -> RegexGroup?
如果未找到名称,**或者**由于在正则表达式模式中指定为可选,因此该函数将返回 nil。

RegexGroup 结构体保存有关捕获组的信息,它具有以下属性

查找正则表达式的第一个匹配项

String.regexMatch 将返回字符串中正则表达式的第一个匹配项,如果未找到匹配项,则返回 nil。 它有两个重载

func regexMatch<RegularExpression: RegexProtocol>(
    _ regex: RegularExpression,
    range: Range<String.Index>? = nil
) throws -> RegexMatch?
func regexMatch(
    _ pattern: String,
    regexOptions: NSRegularExpression.Options = [],
    matchingOptions: NSRegularExpression.MatchingOptions = [],
    groupNames: [String]? = nil,
    range: Range<String.Index>? = nil
) throws -> RegexMatch?

patternregexOptionsmatchingOptionsgroupNames 参数对应于 RegexProtocol 的实例属性。
range 表示在其中搜索模式的字符串的范围。
如果模式无效,或者组名称的数量与捕获组的数量不匹配,这些方法将抛出 (参见 RegexError)。 如果未找到匹配项,它们将**永远不会**抛出错误。
有关这些函数返回的 RegexMatch 的信息,请参阅 提取匹配项和捕获组

警告:如果您更改源字符串,则匹配项和捕获组的范围可能会失效。 使用 String.regexsub 执行多个替换。

示例

var inputText = "name: Chris Lattner"

// If you use comments in the pattern, 
// you MUST use `.allowCommentsAndWhitespace` for the `regexOptions`
let pattern = #"""
name:     # the literal string 'name'
\s+       # one more more whitespace characters
([a-z]+)  # one or more lowercase letters
\s+       # one more more whitespace characters
([a-z]+)  # one or more lowercase letters
"""#

// create the regular expression object
let regex = try! Regex(
    pattern: pattern,
    regexOptions: [.caseInsensitive, .allowCommentsAndWhitespace],
    groupNames: ["first name", "last name"]
    // the names of the capture groups
)
 
if let match = try inputText.regexMatch(regex) {
    print("full match: '\(match.fullMatch)'")
    print("first capture group: '\(match.groups[0]!.match)'")
    print("second capture group: '\(match.groups[1]!.match)'")
    
    // perform a replacement on the first capture group
    inputText.replaceSubrange(
        match.groups[0]!.range, with: "Steven"
    )
    
    print("after replacing text: '\(inputText)'")
}

// full match: 'name: Chris Lattner'
// first capture group: 'Chris'
// second capture group: 'Lattner'
// after replacing text: 'name: Steven Lattner'
let inputText = """
Man selects only for his own good: \
Nature only for that of the being which she tends.
"""
let pattern = #"Man selects ONLY FOR HIS OWN (\w+)"#

let searchRange =
        (inputText.startIndex)
        ..<
        (inputText.index(inputText.startIndex, offsetBy: 40))
        
let match = try inputText.regexMatch(
    pattern,
    regexOptions: [.caseInsensitive],
    matchingOptions: [.anchored],  // anchor matches to the beginning of the string
    groupNames: ["word"],  // the names of the capture groups
    range: searchRange  // the range of the string in which to search for the pattern
)
if let match = match {
    print("full match:", match.fullMatch)
    print("capture group:", match.group(named: "word")!.match)
}

// full match: Man selects only for his own good
// capture group: good

查找正则表达式的所有匹配项

String.regexFindAll 将返回字符串中正则表达式的所有匹配项,如果未找到匹配项,则返回一个空数组。 它与 String.regexMatch 具有完全相同的重载

func regexFindAll<RegularExpression: RegexProtocol>(
    _ regex: RegularExpression,
    range: Range<String.Index>? = nil
) throws -> [RegexMatch]
func regexFindAll(
    _ pattern: String,
    regexOptions: NSRegularExpression.Options = [],
    matchingOptions: NSRegularExpression.MatchingOptions = [],
    groupNames: [String]? = nil,
    range: Range<String.Index>? = nil
) throws -> [RegexMatch]

警告:如果您更改源字符串,则匹配项和捕获组的范围可能会失效。 使用 String.regexsub 执行多个替换。

patternregexOptionsmatchingOptionsgroupNames 参数对应于 RegexProtocol 的实例属性。
String.regexMatch 一样,range 表示在其中搜索模式的字符串的范围。
如果模式无效,或者组名称的数量与捕获组的数量不匹配,这些方法将抛出 (参见 RegexError)。 如果未找到匹配项,它们将**永远不会**抛出错误。
有关这些函数返回的 RegexMatch 的信息,请参阅 提取匹配项和捕获组

示例

var inputText = "season 8, EPISODE 5; season 5, episode 20"

// create the regular expression object
let regex = try Regex(
    pattern: #"season (\d+), Episode (\d+)"#,
    regexOptions: [.caseInsensitive],
    groupNames: ["season number", "episode number"]
    // the names of the capture groups
)
        
let results = try inputText.regexFindAll(regex)
for result in results {
    print("fullMatch: '\(result.fullMatch)'")
    print("capture groups:")
    for captureGroup in result.groups {
        print("    \(captureGroup!.name!): '\(captureGroup!.match)'")
    }
    print()
}
let firstResult = results[0]
// perform a replacement on the first full match
inputText.replaceSubrange(
    firstResult.range, with: "new value"
)
print("after replacing text: '\(inputText)'")

// fullMatch: 'season 8, EPISODE 5'
// capture groups:
//     'season number': '8'
//     'episode number': '5'
//
// fullMatch: 'season 5, episode 20'
// capture groups:
//     'season number': '5'
//     'episode number': '20'
//
// after replacing text: 'new value; season 5, episode 20'

通过模式出现的位置分割字符串

String.regexSplit 将通过模式出现的位置分割字符串。

func regexSplit(
    _ pattern: String,
    regexOptions: NSRegularExpression.Options = [],
    matchingOptions: NSRegularExpression.MatchingOptions = [],
    ignoreIfEmpty: Bool = false,
    maxLength: Int? = nil,
    range: Range<String.Index>? = nil
) throws -> [String]
func regexSplit<RegularExpression: RegexProtocol>(
    _ regex: RegularExpression,
    ignoreIfEmpty: Bool = false,
    maxLength: Int? = nil,
    range: Range<String.Index>? = nil
) throws -> [String]

patternregexOptionsmatchingOptionsgroupNames 参数对应于 RegexProtocol 的实例属性。

示例

let colors = "red,orange,yellow,blue"
let array = try colors.regexSplit(",")
print(array)

// array = ["red", "orange", "yellow", "blue"]
let colors = "red and orange ANDyellow and    blue"

// create the regular expression object
let regex = try Regex(#"\s*and\s*"#, [.caseInsensitive])

let array = try colors.regexSplit(regex, maxLength: 3)
print(array)

// array = ["red", "orange", "yellow"]
// note that "blue" is not returned because the length of the
// array was limited to 3 items.

执行正则表达式替换

String.regexSubString.regexSubInPlace 将执行正则表达式替换。 它们具有完全相同的参数和重载。

func regexSub(
    _ pattern: String,
    with template: String = "",
    regexOptions: NSRegularExpression.Options = [],
    matchingOptions: NSRegularExpression.MatchingOptions = [],
    range: Range<String.Index>? = nil
) throws -> String
func regexSub<RegularExpression: RegexProtocol>(
    _ regex: RegularExpression,
    with template: String = "",
    range: Range<String.Index>? = nil
) throws -> String

patternregexOptionsmatchingOptionsgroupNames 参数对应于 RegexProtocol 的实例属性。

示例

let name = "Peter Schorn"
// The .anchored matching option only looks for matches
// at the beginning of the string.
// Consequently, only the first word will be matched.
let regexObject = try Regex(
    pattern: #"\w+"#,
    regexOptions: [.caseInsensitive],
    matchingOptions: [.anchored]
)
let replacedText = try name.regexSub(regexObject, with: "word")
print(replacedText)

// replacedText = "word Schorn"
let name = "Charles Darwin"
let reversedName = try name.regexSub(
    #"(\w+) (\w+)"#,
    with: "$2 $1"
    // $1 and $2 represent the
    // first and second capture group, respectively.
    // $0 represents the entire match.
)
print(reversedName)

// reversedName = "Darwin Charles"

使用自定义闭包执行正则表达式替换

如果您需要进一步自定义正则表达式替换,可以使用以下方法

func regexSub<RegularExpression: RegexProtocol>(
    _ regex: RegularExpression,
    range: Range<String.Index>? = nil,
    replacer: (_ matchIndex: Int, _ match: RegexMatch) -> String?
) throws -> String
func regexSub(
    _ pattern: String,
    regexOptions: NSRegularExpression.Options = [],
    matchingOptions: NSRegularExpression.MatchingOptions = [],
    groupNames: [String]? = nil,
    range: Range<String.Index>? = nil,
    replacer: (_ matchIndex: Int, _ match: RegexMatch) -> String?
) throws -> String

patternregexOptionsmatchingOptionsgroupNames 参数对应于 RegexProtocol 的实例属性。

示例

let inputString = """
Darwin's theory of evolution is the \
unifying theory of the life sciences.
"""

let pattern = #"\w+"#  // match each word in the input string
let replacedString = try inputString.regexSub(pattern) { indx, match in
    if indx > 5 { return nil }  // only replace the first 5 matches
    return match.fullMatch.uppercased() // uppercase the full match
}
print(replacedString)

// replacedString = """
// DARWIN'S THEORY OF EVOLUTION IS the \
// unifying theory of the life sciences.
// """

如果您需要为每个单独的捕获组执行替换,可以使用 RegexMatch 结构体的 replaceGroups 方法

func replaceGroups(
    _ replacer: (
        _ groupIndex: Int, _ group: RegexGroup
    ) -> String?
) -> String

示例

let inputText = "name: Peter, id: 35, job: programmer"
let pattern = #"name: (\w+), id: (\d+)"#
let groupNames = ["name", "id"]

let match = try inputText.regexMatch(
    pattern, groupNames: groupNames
)!
let replacedMatch = match.replaceGroups { indx, group in
    if group.name == "name" { return "Steven" }
    if group.name == "id" { return "55" }
    return nil
}
print(replacedMatch)

// match.fullMatch = "name: Peter, id: 35"
// replacedMatch = "name: Steven, id: 55"

您可以按以下方式将上述方法组合在一起

let inputString = """
name: sally, id: 26
name: alexander, id: 54
"""
let regexObject = try Regex(
    pattern: #"name: (\w+), id: (\d+)"#,
    groupNames: ["name", "id"]
)
let replacedText = try inputString.regexSub(regexObject) { indx, match in
    
    if indx == 0 { return nil }
    
    return match.replaceGroups { indx, group in
        if group.name == "name" {
            return group.match.uppercased()
        }
        if group.name == "id" {
            return "redacted"
        }
        return nil
    }
}
print(replacedText)

// replacedText = """
// name: sally, id: 26
// name: ALEXANDER, id: redacted
// """

检查 switch 语句中的正则表达式匹配项

模式匹配运算符 ~= 已被重载,以支持在 switch 语句中检查正则表达式的匹配项。 例如

let inputStrig = #"user_id: "asjhjcb""#

switch inputStrig {
    case try Regex(#"USER_ID: "[a-z]+""#, [.caseInsensitive]):
        print("valid user id")
    case try? Regex(#"[!@#$%^&]+"#):
        print("invalid character in user id")
    case try! Regex(#"\d+"#):
        print("user id cannot contain numbers")
    default:
        print("no match")
}

// prints "valid user id"

可以根据您希望如何处理由无效正则表达式模式引起的错误,使用 trytry?try!。 遗憾的是,无法将正则表达式模式的匹配项绑定到变量。