Swift和正则表达式:Swift

/ Mac / 没有评论 / 1379浏览

Swift和正则表达式:Swift

在本系列的第一个教程中,我们探讨了正则表达式的基础知识,包括编写正则表达式的语法。 在本教程中,我们将运用到目前为止所学的知识来利用Swift中的正则表达式。

1. Swift中的正则表达式

打开Xcode,创建一个新的Playground,将其命名为RegExTut ,然后将Platform设置为OS X。 iOS或OS X平台的选择与我们将要使用的API无关。 创建一个游乐场

在开始之前,您需要了解另一件事。 在Swift中,对于在正则表达式中使用的每个反斜杠,都需要使用两个反斜杠\\ 。 原因与Swift具有C样式的字符串文字有关。 反斜杠除了在Swift的字符串插值中的作用外,还作为字符转义进行处理。 换句话说,您需要转义转义字符。 如果听起来很奇怪,请不要担心。 请记住,要使用两个反斜杠而不是一个。

在第一个人为设计的示例中,我们假设正在翻阅字符串以查找非常特定类型的电子邮件地址。 该电子邮件地址符合以下条件:

将以下代码添加到操场上,让我们逐步介绍该代码段。

import Cocoa

// (1):
let pat = "\\b([a-z])\\.([a-z]{2,})@([a-z]+)\\.ac\\.uk\\b"
// (2):
let testStr = "x.wu@strath.ac.uk, ak123@hotmail.com     e1s59@oxford.ac.uk, ee123@cooleng.co.uk, a.khan@surrey.ac.uk"
// (3):
let regex = try! NSRegularExpression(pattern: pat, options: [])
// (4):
let matches = regex.matchesInString(testStr, options: [], range: NSRange(location: 0, length: testStr.characters.count))

第1步

我们定义一个模式。 请注意双转义的反斜杠。 在(常规)正则表达式表示中,例如RegExr网站上使用的那个 ,这将是([az])\.([az]{2,})@([az]+)\.ac\.uk 。 还要注意括号的使用。 它们被用来定义捕获组,通过它们我们可以提取与正则表达式的该部分匹配的子字符串。

您应该能够确定第一个捕获组捕获了用户名的第一个字母,第二个捕获了用户的姓氏,第三个捕获了大学的名称。 还请注意,使用反斜杠转义句点字符以表示其字面含义。 或者,我们可以将其单独放在一个字符集中( [.] )。 在那种情况下,我们不需要逃脱它。

第2步

这是我们在其中搜索模式的字符串。

第三步

我们创建一个NSRegularExpression对象,传入不带选项的模式。 在选项列表中,可以指定NSRegularExpressionOption常量,例如:

因为初始化程序正在抛出,所以我们使用try关键字。 例如,如果传入无效的正则表达式,则会引发错误。

第4步

我们通过调用matchesInString(_:options:range:)搜索测试字符串中的匹配项,并传入一个范围以指示我们感兴趣的字符串部分。该方法还接受选项列表。 为了简单起见,在此示例中,我们不传递任何选项。 我将在下一个示例中讨论选项。

匹配项以NSTextCheckingResult对象的数组形式返回。 我们可以提取匹配项,包括捕获组,如下所示:

for match in matches {
    for n in 0..<match.numberOfRanges {
        let range = match.rangeAtIndex(n)
        let r = testStr.startIndex.advancedBy(range.location) ..<
            testStr.startIndex.advancedBy(range.location+range.length)
        testStr.substringWithRange(r)
    }
}

上面的代码片段遍历了数组中的每个NSTextCheckingResult对象。 在numberOfRanges ,每个匹配项的numberOfRanges属性的值均为4 ,一个匹配项的整个子字符串对应一个电子邮件地址(例如a.khan@surrey.ac.uk),其余三个对应于三个捕获组在匹配中(分别为“ a”,“ khan”和“ surrey”)。

rangeAtIndex(_:)方法返回字符串rangeAtIndex(_:)字符串的范围,因此我们可以提取它们。 请注意, rangeAtIndex(0)使用rangeAtIndex(0) ,您还可以将range属性用于整个匹配。

单击右侧结果面板中的显示结果按钮。 这向我们显示了“ Surrey”,即循环的最后一次迭代的testStr.substringWithRange(r)的值。 右键单击结果字段,然后选择“ 值历史记录”以显示值的历史记录显示价值记录

您可以修改上面的代码,以对匹配项和/或捕获组做一些有意义的事情。

使用具有特殊语法来表示捕获组的模板字符串,可以方便地执行查找和替换操作。 继续进行示例,假设我们想用“ lastname,initial,university”形式的子字符串替换每个匹配的电子邮件地址,我们可以执行以下操作:

let replacedStr = regex.stringByReplacingMatchesInString(testStr, 
                    options: [], 
                    range: NSRange(location: 0, length: testStr.characters.count), 
                    withTemplate: "($2, $1, $3)")

请注意模板中的$n语法,该语法充当捕获组n的文本的占位符。 请记住, $0代表整个匹配。

2.一个更高级的例子

matchesInString(_:options:range:)方法是依赖于enumerateMatchesInString(_:options:range:usingBlock:)的几种便捷方法之一,这是NSRegularExpression类中最灵活,最通用(因此最复杂)的方法。 每次匹配后,此方法都会调用一个块,使您可以执行所需的任何操作。

通过使用NSMatchingOptions常量传入一个或多个匹配规则,可以确保在其他情况下调用该块。 对于长时间运行的操作,可以指定定期调用该块并在某个时刻终止该操作。 使用ReportCompletion选项,您可以指定在完成时调用该块。

该块具有一个flags参数,该参数报告这些状态中的任何一个,因此您可以决定要采取的操作。 与Foundation框架中的某些其他枚举方法类似,您也可以自行决定终止该块。 例如,如果长时间的比赛不成功,或者您找到足够的比赛来开始处理。

在这种情况下,我们将在一些文本中搜索看起来像日期的字符串,并检查是否存在特定的日期。 为了使示例易于管理,我们将假设日期字符串具有以下结构:

单位月份和日期可能用前导零填充。 有效的分隔符是破折号,句号和正斜杠。 除上述要求外,我们将不会验证日期是否确实有效。 例如,我们可以使用不是真实日期的日期,例如2000-04-31(4月只有30天)和2009-02-29(2009年不是a年,这意味着2月只有28天) 。

将以下代码添加到操场上,让我们逐步介绍该代码段。


// (1):
typealias PossibleDate = (year: Int, month: Int, day: Int)
// (2):
func dateSearch(text: String, _ date: PossibleDate) -> Bool {

  // (3):
  let datePattern = "\\b(?:20)?(\\d\\d)[-./](0?[1-9]|1[0-2])[-./](3[0-1]|[1-2][0-9]|0?[1-9])\\b"

  let dateRegex = try! NSRegularExpression(pattern: datePattern,
                                           options: [])
  // (4):
  var wasFound: Bool = false
  // (5):
  dateRegex.enumerateMatchesInString(text, options: [],
                                     range: NSRange(location: 0,
                                                    length: text.characters.count)) {
    // (6):
    (match, _, stop) in
    var dateArr = [Int]()
    for n in 1...3 {
      let range = match!.rangeAtIndex(n)
      let r = text.startIndex.advancedBy(range.location) ..<
      text.startIndex.advancedBy(range.location+range.length)
      dateArr.append(Int(text.substringWithRange(r))!)

    }
    // (7):
    if dateArr[0] == date.year
    && dateArr[1] == date.month
    && dateArr[2] == date.day {
      // (8):
      wasFound = true
      stop.memory = true
    }
  }

  return wasFound
}

let text = "   2015/10/10,11-10-20,     13/2/2     1981-2-2   2010-13-10"

let date1 = PossibleDate(15, 10, 10)
let date2 = PossibleDate(13, 1, 1)

dateSearch(text, date1) // returns true
dateSearch(text, date2) // returns false

第1步

我们要检查其存在的日期将采用标准格式。 我们使用一个命名的元组。 我们仅将两位数字整数传递给year,即16表示2016。

第2步

我们的任务是枚举看起来像日期的匹配项,从它们中提取年,月和日组成部分,并检查它们是否与传入的日期匹配。我们将创建一个函数来为我们完成所有这些工作。 该函数返回truefalse取决于是否找到日期。

第三步

日期模式具有一些有趣的功能:

第4步

该函数将返回布尔变量notFound ,指示是否找到了要查找的日期。

第5步

正在调用enumerateMatchesInString(_:options:range:usingBlock:) 。 我们没有使用任何选项,而是传递了要搜索的文本的整个范围。

第6步

每次匹配后调用的块对象具有三个参数:

如果找到所需的日期,则使用布尔值退出该块,因为我们不需要进一步查找。 提取日期成分的代码与前面的示例非常相似。

步骤7

我们检查从匹配的子字符串中提取的成分是否等于所需日期的成分。 请注意,我们将强制转换为Int ,我们确信不会失败,因为我们创建了相应的捕获组以仅匹配数字。

步骤8

如果找到匹配项,则将notFound设置为true 。 我们通过设置stop.memory退出该块 true 。 我们这样做是因为stop是一个指向布尔值指针 ,而Swift处理“指向”内存的方式是通过memory属性。

请注意,我们文本中的子字符串“ 2015/10/10”对应于PossibleDate( 15,10,10 ,这就是为什么函数在第一种情况下返回true原因。 但是,文本中没有字符串对应于PossibleDate(13,1,1) ,即“ 2013-01-01”,并且对该函数的第二次调用返回false输出量

结论

我们悠闲而合理地介绍了正则表达式的工作原理,但是,如果您感兴趣的话,还有很多东西需要学习,例如前瞻性断言和后断言,以及将正则表达式应用于Unicode字符串。我们在Foundation API中浏览了各种选项。

即使您决定不深入研究,也希望您在这里学到了足够的信息,以便能够识别正则表达式可能会派上用场的情况,以及有关如何设计正则表达式以解决模式搜索问题的一些指导。

翻译自: https://code.tutsplus.com/tutorials/swift-and-regular-expressions-swift--cms-26626