Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说正则表达式开始和结束_java正则表达式,希望能够帮助你!!!。
原文:Regular Expressions Tutorial: Getting Started
作者:Tom Elliott
译者:kmyhy
在这篇教程中,你将学习如何在 iOS app 中使用 Swift 4.2 实现正则表达式。
更新说明:Tom Elliott 将本教程升级至 Swift 4.2。原文作者是 James Frost。
如果你没有听过正则表达式——也叫正则式,那么在继续阅读本教程之前,你应该看一下正则表达式的介绍。幸好,我们已经为你准备好了。请阅读这篇《正则表达式介绍》教程。
了解了基本概念之后,来看看如何在 app 中使用正则表达式。
在本文顶部或底部有一个 Download Materials 按钮,可以下载开始项目。打开开始项目,运行它。
你准备给你的老板 —— 一个大坏蛋——编写一个日记 app。每个人都知道,那些超级大坏蛋为了统治世界,准备将他们的邪恶计划都记录下来。有很多计划需要执行,而你,作为小喽啰,也是这些计划的一部分,你的计划就是为其他计划编写这个 app!
app 的 UI 基本上就绪,但主要的功能需要使用正则表达式,它还没有完成!
本教程中,你的任务是将正则表达式添加到 app 中,为它添加亮点(从而避免被扔到炽热的岩浆中去)。
这里是几张最终完成的产品效果图:
最终 app 会有两个常见的使用正则式的场景:
首先实现最简单的正则表达式的用法:文本搜索。
这个 app 的搜索替换功能大概是这样的:
注:app 使用了 UITextView 中的 NSAttributedString 属性来高亮搜索结果。
你也可以用 Text Kit 来实现高亮。更多内容你可以参考这篇 Text Kit Swift 教程。
还有一个阅读模式按钮,允许你高亮所有日期、时间,以及日记中的分隔线。为了简单起见,你不需要了解文本中日期时间的所有可能格式。在本文最后,你将实现这个高亮功能。
首先让搜索功能起作用,将普通字符串表示的正则表达式转换成 NSRegularExpression 对象。
打开 SearchOptionsViewController.swift。SearchViewController 会模态地弹出这个 view controller,允许用户输入他/她要搜索的东西(或者替换),并指定大小写敏感或进匹配整词等选项。
看一下文件头部的 SearchOptions 结构。SearchOptions 是一个简单的结构体,封装了用户的搜索选项。代码会将一个 SearchOptions 实例返回给 SearchViewController。最好是能只用它构造一个对应的 NSRegularExpression。你可以写一个 NSRegularExpression 扩展,通过一个定制构造函数来实现。
选择 File ▸ New ▸ File… 然后选择 Swift File。名字就叫做 RegexHelpers.swift。打开文件,添加代码:
extension NSRegularExpression {
convenience init?(options: SearchOptions) throws {
let searchString = options.searchString
let isCaseSensitive = options.matchCase
let isWholeWords = options.wholeWords
let regexOption: NSRegularExpression.Options =
isCaseSensitive ? [] : .caseInsensitive
let pattern = isWholeWords ? "\\b\(searchString)\\b" : searchString
try self.init(pattern: pattern, options: regexOption)
}
}
代码为 NSRegularExpression 增加了一个便利构造函数。它使用传入的 SearchOption 参数进行配置。
需要注意的是:
如果因为某种原因,无法创建 NSRegularExpression,这个构造函数会出错,返回 nil。现在,你有了 NSRegularExpression 对象,就可以用它来匹配文本了。
打开 SearchViewController.swift,找到 searchForText(_:replaceWith:inTextView:),在这个空方法中添加:
if let beforeText = textView.text, let searchOptions = self.searchOptions {
let range = NSRange(beforeText.startIndex..., in: beforeText)
if let regex = try? NSRegularExpression(options: searchOptions) {
let afterText = regex?.stringByReplacingMatches(
in: beforeText,
options: [],
range: range,
withTemplate: replacementText
)
textView.text = afterText
}
}
首先,这个方法从 UITextView 中获得当前文本,算出整个字符串的 NSRange。可以只将正则表达式应用在文本的某个范围,这也是为什么要在这里计算 NSRange 的原因。这里,我们使用了整个字符串,因此这个正则表达式会应用到整个文本。
真正关键的语句是调用 stringByReplacingMatches。这个方法返回一个新的字符串,而不是修改原来的字符串。然后这个方法将新字符串设置到 UITextView 上,这样用户就看到了结果。
仍然在 SearchViewController 中,找到 highlightText(_:inTextView:) 方法添加:
// 1
let attributedText = textView.attributedText.mutableCopy() as! NSMutableAttributedString
// 2
let attributedTextRange = NSMakeRange(0, attributedText.length)
attributedText.removeAttribute(
NSAttributedString.Key.backgroundColor,
range: attributedTextRange)
// 3
if let searchOptions = self.searchOptions,
let regex = try? NSRegularExpression(options: searchOptions) {
let range = NSRange(textView.text.startIndex..., in: textView.text)
if let matches = regex?.matches(in: textView.text, options: [], range: range) {
// 4
for match in matches {
let matchRange = match.range
attributedText.addAttribute(
NSAttributedString.Key.backgroundColor,
value: UIColor.yellow,
range: matchRange
)
}
}
}
// 5
textView.attributedText = (attributedText.copy() as! NSAttributedString)
以上代码解释如下:
Build & run。尝试搜索各种单词和词组!你会看到全文中的搜索词都高亮了,如下图所示:
试图搜索 the 这个词,查看加上各种选项的效果。注意,例如整词搜索,在 then 中的 the 字就不会高亮。
同样测试搜索替换功能,看看你的文本有没有如期望的一样被替换。同样,再试试 match case (大小写敏感)和 whole words (整词匹配)选项。
高亮和替换文字都好了。但 app 中的另一个正则表达式功能怎么做呢?
许多 app 都会有用户输入功能,比如用户输入 email 地址或电话号码。你想对用户输入进行某些校验,以确保数据完整性并通知用户输入错误。
正则表达式能完美胜任各种数据校验,因为它在模式匹配方面非常擅长。
需要在你的 app 中添加两个地方:校验模式自身,以及用这些模式进行用户校验的机制。
作为练习,请用正则表达式校验下列字符串(忽略大小写敏感的问题):
当然,在开发时,你可以用 materials 文件夹下的 iRegex playground。
你要怎样使用这些正则表达式?如果你感觉困难,只要回到教程顶部的清单,在上面寻找对你有帮助的部分。
下面是答案。但是,在进一步阅读之前,请先自己进行尝试,并对比答案:
"^[a-z]{1,10}$", // First name
"^[a-z]$", // Middle Initial
"^[a-z'\\-]{2,20}$", // Last Name
"^[a-z0-9'.\\-\\s]{2,20}$" // Super Villain name
"^(?=\\P{Ll}*\\p{Ll})(?=\\P{Lu}*\\p{Lu})(?=\\P{N}*\\p{N})(?=[\\p{L}\\p{N}]*[^\\p{L}\\p{N}])[\\s\\S]{8,}$" // Password validator
打开 AccountViewController.swift 在 viewDidLoad() 中添加代码:
textFields = [
firstNameField,
middleInitialField,
lastNameField,
superVillianNameField,
passwordField
]
let patterns = [ "^[a-z]{1,10}$",
"^[a-z]$",
"^[a-z'\\-]{2,20}$",
"^[a-z0-9'.\\-\\s]{2,20}$",
"^(?=\\P{Ll}*\\p{Ll})(?=\\P{Lu}*\\p{Lu})(?=\\P{N}*\\p{N})(?=[\\p{L}\\p{N}]*[^\\p{L}\\p{N}])[\\s\\S]{8,}$" ]
regexes = patterns.map {
do {
let regex = try NSRegularExpression(pattern: $0, options: .caseInsensitive)
return regex
} catch {
#if targetEnvironment(simulator)
fatalError("Error initializing regular expressions. Exiting.")
#else
return nil
#endif
}
}
将 view controller 中的 texst field 添加到一个数组中,并创建另一个数组用于正则式模板。然后用 Swift 的 map 函数创建一个 NSRegularExpression 数组,每个模板创建一个正则表达式对象。如果创建正则表达式对象失败,对于开发环境,可以在模拟器中抛出一个 fatalError,但是对于生产环境则忽略,因为你不想让用户的 app 崩了!
为了创建用于校验 first name 的正则表达式,首先要匹配字符串的开始标记。然后匹配 A-Z 字符,然后匹配字符串结束标记并确保长度为 1-10 。
接下来两个模板 —— 中名首字母 和 last name —— 遵循同样的逻辑。对于中名,不需要用 {1} 指明长度,因为 1$ 默认只会匹配一个字符。超级大坏蛋名字的模板也类似,但更复杂一些,因为需要支持特殊字符:撇号、连号和点号。
注意,你无需关心大小写问题,这里 —— 你要在实例化正则表达式时才需要关心。
密码校验写的是什么鬼?需要强调一点,这只是为了演示怎样使用正则式才这样用的,不要在真正的 app 中这么用!
然后再来看它是怎样子使用的。首先,讲几个正则表达式中的概念:
可以将这个表达是分成几段:
嘘,总算搞定!
你可以从正则表达式中获得许多灵感。解决上述问题还有其他方法,比如用 \d 替换 [0-0]。管它白猫黑猫,只要能抓住老鼠的猫就是好猫。
模式已经有了,你需要在 text field 中去使用它们了。
仍然是 AccountViewController.swift,找到validate(string:withRegex:) 方法并替换为:
let range = NSRange(string.startIndex..., in: string)
let matchRange = regex.rangeOfFirstMatch(
in: string,
options: .reportProgress,
range: range
)
return matchRange.location != NSNotFound
在后面的 validateTextField(_? 方法添加下列代码:
let index = textFields.index(of: textField)
if let regex = regexes[index!] {
if let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) {
let valid = validate(string: text, withRegex: regex)
textField.textColor = (valid) ? .trueColor : .falseColor
}
}
这和你在 SearchViewController.swift 所做的类似。在 validateTextField(_? 中,首先从 regexes 数组中找到对应的 regex 对象,将用户在 text field 中输入的空白字符删除。
然后,在 validate(string:withRegex:) 中,你创建了一个 NSRange,包含了整个文本,用 rangeOfFirstMatch(in:options:range:) 方法检查结果是否匹配。这可能是最高效的检查是否匹配的方法,因为如果它找到第一个匹配结果,方法就会返回。但是如果你想知道总的匹配数有多少的话,可以使用numberOfMatches(in:options:range:)。
最后,将 allTextFieldsAreValid() 的代码替换为:
for (index, textField) in textFields.enumerated() {
if let regex = regexes[index] {
if let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) {
let valid = text.isEmpty || validate(string: text, withRegex: regex)
if !valid {
return false
}
}
}
}
return true
上面同样也调用了 validate(string:withRegex:) 方法,这个方法简单判断每个非空的 text field 是否校验通过。
运行项目,点击左上角的 Account 图标,在注册表单中输入信息。填完所有字段后,文本会根据是否有效变绿或变红。如下图所示:
保存你的账户。注意,你只能在所有字段校验通过时才能保存。重新启动 app。这次,当 app 打开,会显示一个登录表单,然后才能查看日记中的秘密计划。输入之前创建的密码,然后点击 login。
注:这是正则表达式教程而不是登录认证教程。不要将代码用于登录认证。再次强调,密码是以纯文本的形式存储在设备上的。LoginViewController 中的 loginAction 只检查密码是否和设备上保存的一致,而不会和服务器中存储的密码进行比对。无论如何这都是不安全的。
你还没有用到导航栏上的 Reading Mode 按钮吧。当用户点击它的时候,app 会进入一种“聚焦”模式,高亮文本中的日期时间字段,并对日记中每个子项末尾进行高亮。
打开 SearchViewController.swift 找到关于 Reading Mode 按钮的下列代码:
//MARK: Underline dates, times, and splitters
@IBAction func toggleReadingMode(_ sender: AnyObject) {
if !self.readingModeEnabled {
readingModeEnabled = true
decorateAllDatesWith(.underlining)
decorateAllTimesWith(.underlining)
decorateAllSplittersWith(.underlining)
} else {
readingModeEnabled = false
decorateAllDatesWith(.noDecoration)
decorateAllTimesWith(.noDecoration)
decorateAllSplittersWith(.noDecoration)
}
}
上面的方法用 3 个助手方法对日期、时间和分隔线进行修饰。每个方法会带一个 decoration 选项,用于表示是否高亮或者不修饰(移除高亮效果)。如果你查看每个助手方法,你会发现它们是空的!
在关心如何实现修饰效果之前,先来看看如何创建和定义 NSRegularExpression 对象吧。一个简单的方法是在 NSRegularExpression 上创建一个静态变量。回到 RegexHelpers.swift,在 NSRegularExpression 扩展中添加:
static var regularExpressionForDates: NSRegularExpression? {
let pattern = ""
return try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)
}
static var regularExpressionForTimes: NSRegularExpression? {
let pattern = ""
return try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)
}
static var regularExpressionForSplitter: NSRegularExpression? {
let pattern = ""
return try? NSRegularExpression(pattern: pattern, options: [])
}
现在,让你来写这些正则表达式模板!要求如下:
日期
时间
查找简单时间比如 9am huozhe 11 pm:1 或 2 位数字 + 0 到多为空格 + 小写的 am 或 pm。
分隔线
一个波浪号(~),长度大于 10。
你可以用那个 playground 文件来测试。看看是否能够写出相应的正则表达式。
给你 3 个正则式模板作为例子。用下列模板替换 regularExpressionForDates 中的空白模板:
(\\d{1,2}[-/.]\\d{1,2}[-/.]\\d{1,2})|((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)((r)?uary|(tem|o|em)?ber|ch|il|e|y|)?)\\s*(\\d{1,2}(st|nd|rd|th)?+)?[,]\\s*\\d{4}
这个模板被 | (或)字符串分成两部分。这表示要么匹配第一部分,要么匹配第二部分。
第一部分是 (\d{1,2}[-/.]\d{1,2}[-/.]\d{1,2})。这意味着两位数字之后是一个 - 或 / 字符,然后是两位数字,然后又是 - 或 / 字符,然后是两位数字。
第二部分的第一部分是 ((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(®?uary|(tem|o|em)?ber|ch|il|e|y|)?),匹配完整和缩写的月份名。
接下来是 \s*\d{1,2}(st|nd|rd|th)?,匹配 0 或多个空格,加 1-2 位数字,加上一个可选的序数词后缀。例如,1 和 1st。
最后是 [,]\s*\d{4},匹配一个逗号,加 0 或多个空格,加一个 4 位数字年份。
这个正则表达式好恐怖呀!但是,你会发现正则表达式太简洁了,能将大量信息压缩成一个类似于加密的字符串。
接下来的模板是 regularExpressionForTimes 和 regularExpressionForSplitters。将空白模板用下列表达式替换:
// Times
\\d{1,2}\\s*(pm|am)
// Splitters
~{10,}
作为练习,看看你能否根据上面的规范解释这写正则表达式模式。
最后,打开 SearchViewController.swift,实现其中的修饰方法:
func decorateAllDatesWith(_ decoration: Decoration) {
if let regex = NSRegularExpression.regularExpressionForDates {
let matches = matchesForRegularExpression(regex, inTextView: textView)
switch decoration {
case .underlining:
highlightMatches(matches)
case .noDecoration:
removeHighlightedMatches(matches)
}
}
}
func decorateAllTimesWith(_ decoration: Decoration) {
if let regex = NSRegularExpression.regularExpressionForTimes {
let matches = matchesForRegularExpression(regex, inTextView: textView)
switch decoration {
case .underlining:
highlightMatches(matches)
case .noDecoration:
removeHighlightedMatches(matches)
}
}
}
func decorateAllSplittersWith(_ decoration: Decoration) {
if let regex = NSRegularExpression.regularExpressionForSplitter {
let matches = matchesForRegularExpression(regex, inTextView: textView)
switch decoration {
case .underlining:
highlightMatches(matches)
case .noDecoration:
removeHighlightedMatches(matches)
}
}
}
每个方法都使用了 NSRegularExpression 的静态变量来创建对应的正则表达式。然后搜索匹配的字符串并调用 highlightMatches(_? 方法彩色高亮每个字符串,或者调用 removeHighlightedMatches(_? 去反转样式。如果有兴趣,你可以读一下它们的源码。
Build & run。现在,点击 Reading Mode 图标。你会看到日期、时间和分隔线使用了链接样式进行高亮:
再次点击 Reading Mode,将文本转回默认样式。
这个 app 作为示例已经差不多了,你可以看看为什么时间的正则表达式无法匹配一些很常见的搜索模式?比如,它无法匹配 3:15pm,而只会匹配 28pm。
这是一个课后作业!请重写时间的正则表达式,让它能够匹配更多的时间格式。
特别是,你的答案应该匹配标准 12 小时制的时间格式 ab:cd am/pm。比如:11:45 am,10:33pm,04:12am,但不匹配 2pm,0:00 am 18:44am 9:63pm 或 7:4 am。在 am/pm 前至少有 1 个空格。另外,允许匹配 14:33am 中的 4:33am。
下面是答案之一,但在查看答案之前请先自己尝试写出。用 accompanying.playground 去检验结果。
答案:
"(1[0-2]|0?[1-9]):([0-5][0-9]\\s?(am|pm))"
恭喜!你已经学会了正则表达式的使用。
你可以用下面的 Download Materials 按钮下载本教程完整版的项目代码。
正则表达式使用起来强大而有趣——就像是解决某种数学问题。它的灵活性体现在你能够根据需要创建各种模板,比如过滤输入中的空格,在解析 HTML/XML 时删除标签,或者查找特殊的 XML/HTML 标签 —— 以及更多功能!
在真实环境中有许多使用正则表达式进行校验的例子。作为最后的练习,请解释一下这个用于 email 地址校验的正则表达式:
[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?
初一看它就是一堆乱七八糟的字符,但利用你学过的知识(以及参考下面的连接)你会一步一步地理解它,使自己成为一个正则表达式高手!
以下列出一些有用的正则表达式资源:
希望你喜欢这篇 NSRegularExpression 教程,有任何问题和建议,请到论坛中留言。
Download Materials
a-z ↩︎
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章