Swift之正则表达式

NSRegularExpression

用到的常量

    public struct Options : OptionSet {

        public init(rawValue: UInt)

        
        public static var caseInsensitive: NSRegularExpression.Options { get } // 不区分大小写

        public static var allowCommentsAndWhitespace: NSRegularExpression.Options { get }//忽略空格和#

        public static var ignoreMetacharacters: NSRegularExpression.Options { get }//整体化

        public static var dotMatchesLineSeparators: NSRegularExpression.Options { get }//匹配任何字符,包括分隔符

        public static var anchorsMatchLines: NSRegularExpression.Options { get }// 允许^和$在匹配的结束行

        public static var useUnixLineSeparators: NSRegularExpression.Options { get }

        public static var useUnicodeWordBoundaries: NSRegularExpression.Options { get }
    }

     public struct MatchingOptions : OptionSet {

        public init(rawValue: UInt)

        
        public static var reportProgress: NSRegularExpression.MatchingOptions { get } /* Call the block periodically during long-running match operations. */

        public static var reportCompletion: NSRegularExpression.MatchingOptions { get } /* Call the block once after the completion of any matching. */

        public static var anchored: NSRegularExpression.MatchingOptions { get } /* Limit matches to those at the start of the search range. */

        public static var withTransparentBounds: NSRegularExpression.MatchingOptions { get } /* Allow matching to look beyond the bounds of the search range. */

        public static var withoutAnchoringBounds: NSRegularExpression.MatchingOptions { get } /* Prevent ^ and $ from automatically matching the beginning and end of the search range. */
    }

    public struct MatchingFlags : OptionSet {

        public init(rawValue: UInt)

        
        public static var progress: NSRegularExpression.MatchingFlags { get } /* Set when the block is called to report progress during a long-running match operation. */

        public static var completed: NSRegularExpression.MatchingFlags { get } /* Set when the block is called after completion of any matching. */

        public static var hitEnd: NSRegularExpression.MatchingFlags { get } /* Set when the current match operation reached the end of the search range. */

        public static var requiredEnd: NSRegularExpression.MatchingFlags { get } /* Set when the current match depended on the location of the end of the search range. */

        public static var internalError: NSRegularExpression.MatchingFlags { get } /* Set when matching failed due to an internal error. */
    }
}

方法

extension NSRegularExpression {

    
    /* The fundamental matching method on NSRegularExpression is a block iterator.  There are several additional convenience methods, for returning all matches at once, the number of matches, the first match, or the range of the first match.  Each match is specified by an instance of NSTextCheckingResult (of type NSTextCheckingTypeRegularExpression) in which the overall match range is given by the range property (equivalent to rangeAtIndex:0) and any capture group ranges are given by rangeAtIndex: for indexes from 1 to numberOfCaptureGroups.  {NSNotFound, 0} is used if a particular capture group does not participate in the match.
    */
    
    open func enumerateMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange, using block: (NSTextCheckingResult?, NSRegularExpression.MatchingFlags, UnsafeMutablePointer<ObjCBool>) -> Void)

    
    open func matches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> [NSTextCheckingResult]

    open func numberOfMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> Int

    open func firstMatch(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> NSTextCheckingResult?

    open func rangeOfFirstMatch(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> NSRange
}

替换方法

extension NSRegularExpression {

    
    /* NSRegularExpression also provides find-and-replace methods for both immutable and mutable strings.  The replacement is treated as a template, with $0 being replaced by the contents of the matched range, $1 by the contents of the first capture group, and so on.  Additional digits beyond the maximum required to represent the number of capture groups will be treated as ordinary characters, as will a $ not followed by digits.  Backslash will escape both $ and itself.
    */
    open func stringByReplacingMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange, withTemplate templ: String) -> String

    open func replaceMatches(in string: NSMutableString, options: NSRegularExpression.MatchingOptions = [], range: NSRange, withTemplate templ: String) -> Int

    
    /* For clients implementing their own replace functionality, this is a method to perform the template substitution for a single result, given the string from which the result was matched, an offset to be added to the location of the result in the string (for example, in case modifications to the string moved the result since it was matched), and a replacement template.
    */
    open func replacementString(for result: NSTextCheckingResult, in string: String, offset: Int, template templ: String) -> String

    
    /* This class method will produce a string by adding backslash escapes as necessary to the given string, to escape any characters that would otherwise be treated as template metacharacters. 
    */
    open class func escapedTemplate(for string: String) -> String
}

正则表达式的测试程序

func testRegularExpression (regex:String,validateString:String) -> [String]{
    do {
        let regex: NSRegularExpression = try NSRegularExpression(pattern: regex, options: [])
        let matches = regex.matches(in: validateString, options: [], range: NSMakeRange(0, validateString.count))
        return matches.map {(validateString as NSString).substring(with: $0.range)}
    } catch {
        print("Error when create regex!")
        return []
    }
}

正则表达式

两种模糊匹配

  • 横向:匹配长度不固定,形式{m,n},表示至少重复m次,至多重复n次。例如ab{2,5}c可以匹配:abbcabbbcabbbbcabbbbbc
  • 纵向:具体到某一个字符,可能不止一种情况,形式[chars]。例如a[123]b可以匹配a1ba2ba3b

字符组

  • 表示范围。
    • [1-9]可以匹配任何数字。
    • [a-z]可以匹配任何小写字母。
    • [A-Z]可以匹配任何大写字母。
  • 排除字符组。用^符号,比如[^abc]可以匹配abc之外的任何字符。
  • 常见字符组。
    • \d <=====> [0-9]
    • \D <=====> [^0-9]
    • \w <=====> [0-9a-zA-Z]
    • \W <=====> [^0-9a-zA-Z]
    • \s <=====> [\t\v\n\r\f]
    • \S <=====> [^\t\v\n\r\f]
    • . <=====> [^\n\r\u2028\u2029]
    • \:如果要匹配以下字符必须使用转义符号:*?+[(){}^$|\/>

量词

量词也叫重复。知道{m,n}的含义之后,需要熟记一下简写形式:

  • {m,}表示至少重复m次。
  • {m}等价于{m,m}表示重复m次。
  • ?等价于{0,1}表示出现或者不出现。
  • +等价于{1,},表示至少出现一次。
  • *等价于{0,},表示出现任意次。
  • 贪婪匹配:尽可能多的匹配。实现方式:量词后面加上+,比如{m,n}+?+++
  • 惰性匹配:尽可能少的匹配。实现方式:量词后面加上?,比如{m,n}???+?

多选分支

多选分支支持多个子模式任选其一。实现方式p1|p2|p3,其中p1、p2、p3是子模式。

let code = "abxc   adzc   aeyc"
print(testRegularExpression(regex: "a(bx|ey)c", validateString: code))
//Print ["abxc", "aeyc"]

//需要自己体会区别
let code = "goodboy   badboy"
print(testRegularExpression(regex: "good|goodboy", validateString: code))
//Print ["good"]
print(testRegularExpression(regex: "goodboy|good", validateString: code))
//Print ["goodboy"]
print(testRegularExpression(regex: "badboy|goodboy", validateString: code))
//Print ["goodboy", "badboy"]

正则表达式中的括号

分组和分支功能

  • (ab)+
  • (p1|p2)

1.2.5.2. 引用分组用以提取数据

func testRegularExpressionCaptureGroups (regex:String,validateString:String) -> [[String]]{
    do {
        let regex: NSRegularExpression = try NSRegularExpression(pattern: regex, options: [])
        let matches = regex.matches(in: validateString, options: [], range: NSMakeRange(0, validateString.count))
        var data = [[String]]()
        for match in matches {
            var currentMatch = [String]()
            for idx in 0..<match.numberOfRanges {
                currentMatch.append((validateString as NSString).substring(with: match.range(at: idx)))
            }
            data.append(currentMatch)
        }
        return data
    } catch {
        print("Error when create regex!")
        return []
    }
}
let regex = "(\\d{4})-(\\d{2})-(\\d{2})"
var string1 = "2017-06-12";
print(testRegularExpressionCaptureGroups(regex: regex, validateString: string1))
// Print [["2017-06-12", "2017", "06", "12"]]

分组引用

以日期为例,匹配三种格式的日期:2021-05-272021/05/272021.05.27,首先想到的正则可能是"\\d{4}(-|\\/|\\.)\\d{2}\\1\\d{2}"但是这种写法也能匹配到形如2021-05/27形式的日期即分隔符不相同的日期。

解决方案是分组引用,大部分情况下用\number来引用已经存在的分组,比如:

let regex = "\\d{4}(-|\\/|\\.)\\d{2}\\1\\d{2}"
var date1 = "2021-05-27";
var date2 = "2021/05/27";
var date3 = "2021.05.27";
var dateWithWrongFormat = "2021-05/27";

print(testRegularExpressionCaptureGroups(regex: regex, validateString: date1)) //Print: [["2021-05-27", "-"]]
print(testRegularExpressionCaptureGroups(regex: regex, validateString: date2))//print: [["2021/05/27", "/"]]
print(testRegularExpressionCaptureGroups(regex: regex, validateString: date3))//Print: [["2021.05.27", "."]]
print(testRegularExpressionCaptureGroups(regex: regex, validateString: dateWithWrongFormat))
//Print: No matches in "2021-05/27" []

很多语言支持命名分组,语法为(?P<GROUP_NAME>RegularExpression)。由官方文档可知,Swift似乎暂> 不支持<>运算符,也就无从支持命名分组了。不过NSTextCheckingResult类中提供了函数
open func range(withName name: String) -> NSRange,似乎又可存有期待。

分组嵌套

当分组出现嵌套时,如何区分分组编号?很简单:从左至右数左括号是第几个,就是第几个子组。

let regex = "((\\d{4})-(\\d{2})-(\\d{2}))   ((\\d{2}):(\\d{2}):(\\d{2}))"
let code = "2021-05-27   09:13:18"
print(testRegularExpressionCaptureGroups(regex: regex, validateString: code))
//Print: [["2021-05-27   09:13:18", "2021-05-27", "2021", "05", "27", "09:13:18", "09", "13", "18"]]

不捕获子组

某些情况下,只是把括号里的内容看成一个整体,后续不再使用,即不必要保存子组,不保存子组可以提高正则的性能,也可以理解为只分组不分配响应的编号。语法为(?:RgularExpression)

四种常见的匹配模式

  • 不区分大小写

      let regex = "(?i)cat"
      let code = "cat CAT Cat cAt caT"
      print(testRegularExpression(regex: regex, validateString: code))
      //Print: ["cat", "CAT", "Cat", "cAt", "caT"]
    
  • 点号通配符模式

  • 多行匹配模式:(?m)RegularExpression

  • 注释模式:(?#COMMMENT)

断言

单词边界

func replaceWhenRegularExpressionMatch(_ regex: String, _ validateString: String, with template: String) -> String{
    do {
        let regex: NSRegularExpression = try NSRegularExpression(pattern: regex, options: [])
        let temp = regex.stringByReplacingMatches( in: validateString , options: [], range: NSMakeRange(0, validateString.count),withTemplate: template)
        return temp
    } catch {
        return "Error when create regex!"
    }
}
let regex = "tom"
var code = "tom asked me if I would go fishing with him tomorrow."
print(replaceWhenRegularExpressionMatch(regex, code, with: "Jerry"))
//Print: Jerry asked me if I would go fishing with him Jerryorrow.

很明显,这样子替换是有问题的,把tomorrow中的tom也替换了,解决方案是使用单词边界。

let regex = "\\btom\\b"
var code = "tom asked me if I would go fishing with him tomorrow."
print(replaceWhenRegularExpressionMatch(regex, code, with: "Jerry"))
//Print: Jerry asked me if I would go fishing with him tomorrow.

环视(Look Around)

环视就是要求在匹配的前面或后面要求满足/不满足某些规则,有时也叫零宽断言

正则 名称 含义 示例
(?<=Y) 肯定逆序环视(Positive look behind) 左边是Y (?<=\d)左边是数字的th,可以匹配9th
(?<!Y) 否定逆序环视(negative look behind) 左边不是Y (?<=\d)左边不是数字的th,可以匹配health
(?=Y) 肯定逆序环视(Positive look ahead) 右边是Y six(?=\d)右边是数字的six,可以匹配six6
(?!Y) 肯定逆序环视(negative look ahead) 右边不是Y hi(?!\d)右边不是数字的hi,可以匹配high

记忆口诀:左尖括号代表看左边,没尖括号代表看右边,感叹号表示非。

环视中也有括号,但是不保存为子组。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容