正则表达式也用过一段时间了,不同的语言中用起来并不相同,这里姑且做为iOS正则的一个总结贴。
[TOC]
iOS中的正则表达式
正则语法
1. 基本
我把同类正则符号做了分组列举,以/作为分隔
元字符 | 描述 |
---|---|
\ | 转义符 如\\n为\n,单\n为换行符 |
^ / $ | 行首与行尾 |
? / * / + / {n} / {m,n} | 零次或一次/>=0次/>=1次/n次/m-n次,mn可省略一端限制 |
? | 跟在(*,+,?,{n},{n,},{n,m})后面时,为非贪婪模式。如,对于“oooo”,o+得到oooo,o+?得到o |
x|y | 匹配x或y |
&& | 字符组与运算,有些语言支持,比如&&+[] Java中就支持,但JavaScript不支持。验证iOS中支持。 |
[xyz1-9] | 匹配xyz或者1-9数字其中一个 |
[^xyz] | 非xyz |
\b / \B | 单词/非单词的边界,可放前后。如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er” |
\d / \D | 数字/非数字 |
\s / \S | 不可见/可见字符。\s不可见,等价于[ \f\n\r\t\v] |
\w / \W | \w包括下划线的字符,包括字母/数字/中文/下划线,不包括emoj。 |
. | 匹配任何单个字符(注意不包括换行符) |
1.1 &&+[]运算
对于&&+[]
运算符,原话是这样描述的:
有些语言支持,比如&&+[] Java中就支持,但JavaScript不支持。
示例7:匹配英文字母中除去元音字符的字符
[[a-z]&&[^aeiou]] // a-z 且排除掉5个元音字符aeiou
我的验证,iOS中是支持的。
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", @"[[a-z ]&&[^acdkn]]+"];
NSLog(@"测试&&:%d", [predicate1 evaluateWithObject:@"better boy"]);
NSLog(@"测试&&:%d", [predicate1 evaluateWithObject:@"better boy run"]);
NSPredicate *predicate11 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", @"[1234567890&&[^6]]+"];
NSLog(@"测试&&:%d", [predicate11 evaluateWithObject:@"1234567890"]);
NSLog(@"测试&&:%d", [predicate11 evaluateWithObject:@"123457890"]);
/*打印结果:
2018-10-11 14:15:03.206346+0800 WEEX_TEST[96757:22569846] 测试&&:1
2018-10-11 14:15:03.206425+0800 WEEX_TEST[96757:22569846] 测试&&:0
2018-10-11 14:15:03.206534+0800 WEEX_TEST[96757:22569846] 测试&&:0
2018-10-11 14:15:03.206600+0800 WEEX_TEST[96757:22569846] 测试&&:1
*/
2. 进阶
元字符 | 描述 |
---|---|
\f/\n/\r/\t/\v | 换页符/换行符/回车符/制表符/垂直制表符 |
\xn | 十六进制转义值,可表示ASCII编码。如:“\x41”匹配“A”。 |
\un | 四个十六进制数字表示的Unicode字符。如,\u00A9 |
\n | 标识一个八进制转义值或引用。注:引用优先于转义,如前面不足n个表达式,才转义生效。 |
\num | 对所获取的匹配的引用。例如,"78aaa",用@"(.)\\1",得到"aa"。456678aa87aaaas,用@"(\d)(\d)(\w)\3\2\1",得到"78aa87"。注:引用优先于转义,如前面不足n个表达式,才转义生效。 |
3. 非获取匹配
非获取匹配,匹配pattern但不获取匹配结果.
元字符 | 描述 |
---|---|
?:pattern | 例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。 |
?=pattern | 正向肯定预查. |
?<=pattern | 反向肯定预查. |
?!pattern | 正向否定预查. |
? | 反向否定预查. |
其中,包含了4个概念:
- 正向 --> 在查找目标右侧
- 反向 --> 在查找目标左侧
- 肯定 --> 是pattern
- 否定 --> 不是pattern
正常读起来就是:
- 正向肯定预查:在目标右侧是pattern
- 反向肯定预查:在目标左侧是pattern
- 正向否定预查:在目标右侧不是pattern
- 反向否定预查:在目标左侧不是pattern
来个例子,查找中括号内的emoji表情,但不要中括号
//用到了:正向肯定预查?=与反向肯定预查?<=
NSString *sourceStr3 = @"我是一个[笑脸],[强],你是一个[自信]";
NSString *pattern = @"(?<=\\[).*?(?=\\])";
NSRegularExpression *regex3 = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
NSArray *results3 = [regex3 matchesInString:sourceStr3 options:NSMatchingReportProgress range:NSMakeRange(0, sourceStr3.length)];
for (NSTextCheckingResult *result in results3) {
NSLog(@"中括号内:%@", [sourceStr3 substringWithRange:result.range]);
}
/*打印结果:
2018-10-10 16:08:35.145447+0800 WEEX_TEST[15000:20387096] 中括号内:笑脸
2018-10-10 16:08:35.145550+0800 WEEX_TEST[15000:20387096] 中括号内:强
2018-10-10 16:08:35.145652+0800 WEEX_TEST[15000:20387096] 中括号内:自信
*/
分解:
(?<=\\[) //目标左侧是[
.* //查找任意数量的任意字符,也是我们想查询的目标
? //非贪婪。如果去掉?,会得到:笑脸],[强],你是一个[自信
(?=\\]) //目标右侧是]
升级一下:如果需求emoji表情为两个字,如何丢弃一个字的emoji呢?如果有些干扰的或者一半的中括号,如何规避掉呢?
NSString *sourceStr3 = @"我是一个[笑脸],[[强]],][][[∑®†]你是一个[自信]";
NSString *pattern = @"(?<=\\[)([^\\[\\]]{2,}?)(?=\\])";
NSRegularExpression *regex3 = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
NSArray *results3 = [regex3 matchesInString:sourceStr3 options:NSMatchingReportProgress range:NSMakeRange(0, sourceStr3.length)];
for (NSTextCheckingResult *result in results3) {
NSLog(@"中括号内:%@", [sourceStr3 substringWithRange:result.range]);
}
/*打印结果:
2018-10-10 16:52:30.027836+0800 WEEX_TEST[27737:20549839] 中括号内:笑脸
2018-10-10 16:52:30.027931+0800 WEEX_TEST[27737:20549839] 中括号内:∑®†
2018-10-10 16:52:30.027984+0800 WEEX_TEST[27737:20549839] 中括号内:自信
*/
用到了[^]符号,去否定[];用到了{2,},去匹配两位以上。其实{2,}后面的那个非贪婪?也可以去掉的,因为碰到[]就匹配结束了,不会出现贪婪∑®†]你是一个[自信
进来的情况。
iOS中正则的三种表现形式
-
[string rangeOfString: options:]
查找字符串范围,返回第一个匹配成功的range -
NSPredicate
谓词匹配数组与字符串。 -
NSRegularExpression
匹配。
1. [string rangeOfString: options:]
例查找第一个出现的数字:
NSString *sourceStr2 = @"123元45678秒09分";
NSString *regex2 = @"(?<=“).*(?=”)";
NSRange range2 = [sourceStr2 rangeOfString:regex2 options:NSRegularExpressionSearch];
if (range2.length > 0) {
NSLog(@"%@", [sourceStr2 substringWithRange:range2]);
}
//打印:123
1.1 出现的option参数
typedef NS_OPTIONS(NSUInteger, NSStringCompareOptions) {
NSCaseInsensitiveSearch = 1, //不区分大小写比较
NSLiteralSearch = 2, //逐字节比较 区分大小写
NSBackwardsSearch = 4, //从字符串末尾开始搜索
NSAnchoredSearch = 8, //搜索限制范围的字符串
NSNumericSearch = 64, //按照字符串里的数字为依据,算出顺序。例如 Foo2.txt < Foo7.txt < Foo25.txt
NSDiacriticInsensitiveSearch NS_ENUM_AVAILABLE(10_5, 2_0) = 128,//忽略 "-" 符号的比较
NSWidthInsensitiveSearch NS_ENUM_AVAILABLE(10_5, 2_0) = 256,//忽略字符串的长度,比较出结果
NSForcedOrderingSearch NS_ENUM_AVAILABLE(10_5, 2_0) = 512,//忽略不区分大小写比较的选项,并强制返回 NSOrderedAscending 或者 NSOrderedDescending
NSRegularExpressionSearch NS_ENUM_AVAILABLE(10_7, 3_2) = 1024 /*只能应用于 rangeOfString:..., stringByReplacingOccurrencesOfString:...和 replaceOccurrencesOfString:... 方法。
使用通用兼容的比较方法,如果设置此项,可以去掉 NSCaseInsensitiveSearch 和 NSAnchoredSearch*/
};
2. NSPredicate
==NSPredicate下,例子引自: sunny_zl==
其中,只有MATCHES时为正则匹配,这儿也把NSPredicate用法罗列下。
2.1 比较运算符
包括:
1. >=
2. <=
3. >
4. <
5. !=
6. ==
7. BETWEEN {下限,上限} //大于或等于下限
例:
NSNumber *testNumber = @123;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF = 123"];
if ([predicate evaluateWithObject:testNumber]) {
NSLog(@"testString:%@", testNumber);
}
//结果:testString:123
2.2 逻辑运算符
包括:
1. AND &&
2. OR ||
3. NOT !
例:
NSArray *testArray = @[@1, @2, @3, @4, @5, @6];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF > 2 && SELF < 5"];
NSArray *filterArray = [testArray filteredArrayUsingPredicate:predicate];
NSLog(@"filterArray:%@", filterArray);
//结果为:(3, 4)
2.3 字符串比较运算符
包括:
1. BEGINSWITH //以指定的字符串开头
2. ENDSWITH //以指定的字符串结尾
3. CONTAINS //包含指定的字符串
4. LIKE //是否匹配指定的字符串模板。?代表一个字符和*代表任意多个字符。如:name LIKE '*ac*'中间包含ac;name LIKE '?ac*'匹配第2个位置开始为ac
5. MATCHES //匹配指定的正则表达式
注:字符串比较都是区分大小写和重音符号的。
如:café
和cafe
是不一样的,Cafe
和cafe
也是不一样的。
cd符号:[c]是不区分大小写,[d]是不区分重音符号。如:
name LIKE[cd] 'cafe'
如:
//简单判断是否手机号
- (BOOL)checkPhoneNumber:(NSString *)phoneNumber {
NSString *regex = @"^[1][3-8]\\d{9}$";
NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
return [pred evaluateWithObject:phoneNumber];
}
2.4 集合运算符
包括:
1. ANY、SOME:集合中任意一个元素满足条件,就返回YES。
2. ALL:集合中所有元素都满足条件,才返回YES。
3. NONE:集合中没有任何元素满足条件就返回YES。如:NONE person.age < 18
4. IN只有当左边表达式或值出现在右边的集合中才会返回YES。
如:
//查找不在filterArray中元素
NSArray *filterArray = @[@"ab", @"abc"];
NSArray *array = @[@"a", @"ab", @"abc", @"abcd"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", filterArray];
NSLog(@"%@", [array filteredArrayUsingPredicate:predicate]);
//输出:( a, abcd )
2.5 谓词中使用占位符参数
包括:
%K:用于动态传入属性名
%@:用于动态设置属性值
$VALUE: 使用动态改变的属性值
如:
//定义一个property来存放属性名,定义一个value来存放值
NSString *property = @"name";
NSString *value = @"Jack";
//该谓词的作用是如果元素中property属性含有值value时就取出放入新的数组内,这里是name包含Jack
NSPredicate *pred = [NSPredicate predicateWithFormat:@"%K CONTAINS %@", property, value];
NSArray *newArray = [array filteredArrayUsingPredicate:pred];
NSLog(@"newArray:%@", newArray);
//------------- 传递VALUE值 -------------
// 创建谓词,属性名改为age,要求这个age包含$VALUE字符串,子谓词传递value值
NSPredicate *predTemp = [NSPredicate predicateWithFormat:@"%K > $VALUE", @"age"];
// 指定$VALUE的值为 25
NSPredicate *pred1 = [predTemp predicateWithSubstitutionVariables:@{@"VALUE" : @25}];
NSArray *newArray1 = [array filteredArrayUsingPredicate:pred1];
NSLog(@"newArray1:%@", newArray1);
2.6 基本用法
//字符串内查找 查找数字
NSString *regex = @"\d";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
BOOL match = [predicate evaluateWithObject:sourceString];
//数字比较 判断是否100-200间
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF BETWEEN {100, 200}"];
//过滤数组内字符串 在filterArray中
NSArray *filterArray = @[@"ab", @"abc"];
NSArray *array = @[@"a", @"ab", @"abc", @"abcd"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF IN %@", filterArray];
NSArray *newArray = [array filteredArrayUsingPredicate:predicate];
//过滤数组内字典 查找name张姓的
NSArray *array = @[@{@"name" : @"zhangsan", @"age" : @"10"},
@{@"name" : @"lisi", @"age" : @"11"},
@{@"name" : @"wangwu", @"age" : @"12"}];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS[c] 'zhang'"];
NSArray *results = [array filteredArrayUsingPredicate:predicate];
//过滤数组内对象,同数组内字典,不再举例
//对象的key比较 对象的age大于25
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age > 25"];
3. NSRegularExpression
3.1 查找匹配
1. 查找第一个匹配范围。
- (NSRange)rangeOfFirstMatchInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
2. 查找第一个匹配结果。
- (nullable NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
3. 查找满足条件总次数。
- (NSUInteger)numberOfMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
4. 查找所有匹配结果。
- (NSArray *)matchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
5. 查找并通过block遍历结果。
- (void)enumerateMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range usingBlock:(void (NS_NOESCAPE ^)(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL *stop))block;
还发现了一个方法:+ (NSString *)escapedPatternForString:(NSString *)string;
,这家伙是用来做什么的呢?它是帮助我们添加转义符的!
如:
NSString *ttt = @"\\d{4,}\n";
NSLog(@"%@", ttt);
NSLog(@"%@", [NSRegularExpression escapedPatternForString:ttt]);
/*打印结果:
2018-10-10 17:25:16.870223+0800 WEEX_TEST[37135:20678832] \d{4,}
2018-10-10 17:25:16.870438+0800 WEEX_TEST[37135:20678832] \\d\{4,\}
*/
3.2 查找替换
1. 字符串替换,返回一个新串
- (NSString *)stringByReplacingMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range withTemplate:(NSString *)templ;
2. 可变字符串替换,返回操作次数
- (NSUInteger)replaceMatchesInString:(NSMutableString *)string options:(NSMatchingOptions)options range:(NSRange)range withTemplate:(NSString *)templ;
3. 自定义替换功能(没搞懂,用了后没发现什么作用)
- (NSString *)replacementStringForResult:(NSTextCheckingResult *)result inString:(NSString *)string offset:(NSInteger)offset template:(NSString *)templ;
4. 转换为正则表达式字符串
+ (NSString *)escapedTemplateForString:(NSString *)string;
例子1,查找并替换数字:
NSString *sourceStr = @"123元45678秒09分";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\d+" options:NSRegularExpressionCaseInsensitive error:nil];
NSArray *results = [regex matchesInString:sourceStr options:NSMatchingReportProgress range:NSMakeRange(0, sourceStr.length)];
for (NSTextCheckingResult *result in results) {
NSLog(@"%@", [sourceStr substringWithRange:result.range]);
}
NSString *replaced = [regex stringByReplacingMatchesInString:sourceStr options:NSMatchingReportProgress range:NSMakeRange(0, sourceStr.length) withTemplate:@"<<-->>"];
NSLog(@"replaced:%@", replaced);
/*打印结果:
2018-10-10 17:40:30.122470+0800 WEEX_TEST[41545:20749671] 123
2018-10-10 17:40:30.122601+0800 WEEX_TEST[41545:20749671] 45678
2018-10-10 17:40:30.122712+0800 WEEX_TEST[41545:20749671] 09
2018-10-10 17:40:30.122858+0800 WEEX_TEST[41545:20749671] replaced:<<-->>元<<-->>秒<<-->>分
*/
例子2,可变字符串查找并替换
NSMutableString *sourceStr2 = [@"123元45678秒09分" mutableCopy];
NSInteger replaceNum = [regex replaceMatchesInString:sourceStr2 options:NSMatchingReportProgress range:NSMakeRange(0, sourceStr.length) withTemplate:@"<<-->>"];
NSLog(@"replaceNum:%ld replaced:%@", replaceNum, sourceStr2);
/*打印结果:
2018-10-10 17:45:56.019120+0800 WEEX_TEST[43137:20772997] replaceNum:3 replaced:<<-->>元<<-->>秒<<-->>分
*/
3.3 出现的option参数
以下说明引自:roc_lei
NSRegularExpressionOptions:
typedef NS_OPTIONS(NSUInteger, NSRegularExpressionOptions) {
NSRegularExpressionCaseInsensitive = 1 << 0, /*不区分字母大小写的模式*/
NSRegularExpressionAllowCommentsAndWhitespace = 1 << 1, /*忽略掉正则表达式中的空格和#号之后的字符*/
NSRegularExpressionIgnoreMetacharacters = 1 << 2, /*将正则表达式整体作为字符串处理*/
NSRegularExpressionDotMatchesLineSeparators = 1 << 3, /*允许.匹配任何字符,包括换行符 */
NSRegularExpressionAnchorsMatchLines = 1 << 4, /*允许^和$符号匹配行的开头和结尾*/
NSRegularExpressionUseUnixLineSeparators = 1 << 5, /*设置\n为唯一的行分隔符*/
NSRegularExpressionUseUnicodeWordBoundaries = 1 << 6 /*使用Unicode TR#29标准作为词的边界,否则所有传统正则表达式的词边界都有效*/
};
NSMatchingOptions:
typedef NS_OPTIONS(NSUInteger, NSMatchingOptions) {
NSMatchingReportProgress = 1 << 0, //找到最长的匹配字符串后调用block回调
NSMatchingReportCompletion = 1 << 1, //找到任何一个匹配串后都回调一次block
NSMatchingAnchored = 1 << 2, //从匹配范围的开始处进行匹配
NSMatchingWithTransparentBounds = 1 << 3, //允许匹配的范围超出设置的范围
NSMatchingWithoutAnchoringBounds = 1 << 4 //禁止^和$自动匹配行还是和结束
};
NSMatchingFlags:
typedef NS_OPTIONS(NSUInteger, NSMatchingFlags) {
NSMatchingProgress = 1 << 0, //匹配到最长串后被设置
NSMatchingCompleted = 1 << 1, //全部分配完成后被设置
NSMatchingHitEnd = 1 << 2, //匹配到设置范围的末尾时被设置
NSMatchingRequiredEnd = 1 << 3, //当前匹配到的字符串在匹配范围的末尾时被设置
NSMatchingInternalError = 1 << 4 //由于错误导致的匹配失败时被设置
};
iOS中正则使用实例
一般使用场景分为两种,全匹配与输入框内边输入边匹配。
实例这里,只演示简单正则使用规则,不做完全严格的匹配。
1. 全匹配
1.1 是否有效身份证号
//15或18位身份证:第一位为1-9,中间13或16位数字,最后一位为0-9xX
BOOL validateIdentityCard(NSString *identityCard){
NSString *regex2 = @"^[1-9](\\d{13}|\\d{16})[0-9xX]$";
NSPredicate *identityCardPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex2];
return [identityCardPredicate evaluateWithObject:identityCard];
}
1.2 是否有效手机号
BOOL validatePhoneNumber(NSString *phone) {
NSString *regex = @"^1[34578]\\d{9}$";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
return [predicate evaluateWithObject:phone];
}
1.3 是否有效6-16位密码
//6-16位密码,只允许输入大小写与数字,并且大小写与数字至少每种一个
BOOL validatePWD(NSString *pwd) {
NSString *regex = @"^(?=.*[0-9])(?=.*[A-Z])(?=.*[a-z])[a-zA-Z0-9]{6,16}$";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
return [predicate evaluateWithObject:pwd];
}
1.4 金额数字添加逗号分隔符
NSString *sourceText = @"1234567890";
NSString *pattern3 = @"\\B(?=(?:\\d{3})+(?!\\d))";
NSRegularExpression *regex3 = [NSRegularExpression regularExpressionWithPattern:pattern3 options:NSRegularExpressionCaseInsensitive error:nil];
NSArray *results3 = [regex3 matchesInString:sourceText options:NSMatchingReportProgress range:NSMakeRange(0, sourceText.length)];
NSString *replaced = [regex3 stringByReplacingMatchesInString:sourceText options:NSMatchingReportProgress range:NSMakeRange(0, sourceText.length) withTemplate:@","];
NSLog(@"replaced:%@", replaced);
/*打印结果:
2018-10-11 10:52:32.896514+0800 WEEX_TEST[38772:22081217] replaced:1,234,567,890
*/
1.5 查找中括号内emoji表情
1.6 限最多8位金额,最多两位小数,第一位不能为.
//控制8位金额,最多两位小数,只能输入数字与.,第一位不能为.或+-
NSString *toString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSString *stringRegex = @"(([0]|(0[.]\\d{0,2}))|([1-9]\\d{0,7}(([.]\\d{0,2})?)))?";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", stringRegex];
if ([predicate evaluateWithObject:toString]) {
return YES;
}
return NO;
2. 边输入边匹配
边输入边匹配的场景还是挺多的,比如限制输入框输入手机号码,输入金额,输入密码,或输入身份证号。
它与全匹配不同,需要在输入过程中,边输入边验证。当检测到不匹配时,拒绝用户输入,所以用:-textField:shouldChangeCharactersInRange:replacementString:
。还要支持回车
,退格
等。注意:正则检测的是替换后的string
。
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
//过滤字符
if ([string isEqualToString:@""] || //按退格可以改变
[string isEqualToString:@"\n"]) { //按回车可以改变
return YES;
}
//因为要匹配的是输入后字符,所以需要替换操作toString
NSString *toString = [textField.text stringByReplacingCharactersInRange:range withString:string];
//正则检测
NSString *stringRegex = @"(([0]|(0[.]\\d{0,2}))|([1-9]\\d{0,7}(([.]\\d{0,2})?)))?";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", stringRegex];
if ([predicate evaluateWithObject:toString]) {
return YES;
}
return NO;
}