NSString 中文的一些处理

开发中,我们有时需要对中文进行一些处理,如获取字符串中的所有中文字符等。对于中文的处理,首要是进行中文范围的确定。由于中文是以Unicode的形式进行编码存储,我们可通过对中文Unicode编码区域的确定,进而确定中文的范围。这方面,已经有很多统一标准(如CJK Unified Ideographs等),我们只需进行一些综合整理即可:

1
2
3
4
5
6
7
8
9
CJK统一汉字 4E00-9FFF
扩展A区用字 3400-4DBF
扩展B区用字 20000-2A6DF
扩展C区用字 2A700-2B73F
扩展D区用字 2B740-2B81F
扩展E区用字 2B820-2CEAF

CJK兼容汉字 F900-FAFF
扩展B区兼容用字 2F800-2FA1F

对于CJK,可通过维基百科补充了解,在此不赘述。当然,对于中文的界定,除了中文文字外,我们还需补充中文标点、中文部首、中文笔划及中文构字描述符。这几个部分与中文文字共同构成了中文,因而,我们也需确定其编码区域:
中文标点的编码区域:

1
2
3
4
5
CJK标点符号 U3000-U303F
中文竖排标点 UFE10-UFE1F
CJK兼容符号(竖排变体、下划线、顿号)UFE30-UFE4F
中文标点 UFE50-UFE6F
全角ASCII、全角中英文标点、半宽片假名、半宽平假名、半宽韩文字母 UFF00-UFFEF

中文部首的编码区域:

1
2
康熙部首 2F00–2FDF
部首补充 2E80–2EFF

中文笔划的编码区域:

1
笔划 31C0–31EF

中文构字描述符的编码区域:

1
构字描述符 2FF0–2FFF

中文范围确定后,则是进行文字与文字范围的匹配处理,这里我们可以用正则表达式进行处理:
中文文字范围正则表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (NSString *)_ch_ChineseCharactersRegex {
static NSString *regex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableString *buffer = [NSMutableString stringWithString:@"\u4E00-\u9FFF"]; // CJK统一汉字
[buffer appendString:@"\u3400-\u4DBF"]; // 扩展A区用字
[buffer appendString:@"\uF900-\uFAFF"]; // CJK兼容汉字
[buffer appendString:@"\\U00020000-\\U0002A6DF"]; // 扩展B区用字
[buffer appendString:@"\\U0002A700-\\U0002B73F"]; // 扩展C区用字
[buffer appendString:@"\\U0002B740-\\U0002B81F"]; // 扩展D区用字
[buffer appendString:@"\\U0002B820-\\U0002CEAF"]; // 扩展E区用字
[buffer appendString:@"\\U0002F800-\\U0002FA1F"]; // 扩展B区兼容用字
regex = buffer.copy;
});
return regex;
}

中文标点范围正则表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (NSString *)_ch_ChinesePunctuationsRegex {
static NSString *regex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableString *buffer = [NSMutableString stringWithString:@"\u3000-\u303F"]; // CJK标点符号
[buffer appendString:@"\uFE10-\uFE1F"]; // 中文竖排标点
[buffer appendString:@"\uFE30-\uFE4F"]; // CJK兼容符号(竖排变体、下划线、顿号)
[buffer appendString:@"\uFE50-\uFE6F"]; // 中文标点
[buffer appendString:@"\uFF00-\uFFEF"]; // 全角ASCII、全角中英文标点、半宽片假名、半宽平假名、半宽韩文字母
regex = buffer.copy;
});
return regex;
}

中文部首范围正则表达式:

1
2
3
4
5
6
7
8
9
10
- (NSString *)_ch_ChineseRadicalsRegex {
static NSString *regex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableString *buffer = [NSMutableString stringWithString:@"\u2F00-\u2FDF"]; // 康熙部首
[buffer appendString:@"\u2E80-\u2EFF"]; // 部首补充
regex = buffer.copy;
});
return regex;
}

中文笔划范围正则表达式:

1
2
3
4
5
6
7
8
9
- (NSString *)_ch_ChineseStrokesRegex {
static NSString *regex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableString *buffer = [NSMutableString stringWithString:@"\u31C0-\u31EF"]; // 笔划
regex = buffer.copy;
});
return regex;
}

中文构字描述符范围正则表达式:

1
2
3
4
5
6
7
8
9
- (NSString *)_ch_ChineseIdeographicDescriptionsRegex {
static NSString *regex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableString *buffer = [NSMutableString stringWithString:@"\u2FF0-\u2FFF"]; // 构字描述符
regex = buffer.copy;
});
return regex;
}

同时,我们可以为NSString添加扩展方法,方便后续正则处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
将字符串中满足正则表达式的字符替换为指定字符

@param regex 正则表达式
@param options 设置匹配方式
@param replacement 替换字符
@return 新字符串
*/
- (NSString *)_ch_stringByReplacingRegex:(NSString *)regex options:(NSRegularExpressionOptions)options withString:(NSString *)replacement {
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:nil];
if (!pattern) return self;

return [pattern stringByReplacingMatchesInString:self options:0 range:NSMakeRange(0, self.length) withTemplate:replacement];
}

/**
字符串是否匹配正则表达式

@param regex 正则表达式
@param options 设置匹配方式
@return 匹配返回YES, 否则返回NO
*/
- (BOOL)_ch_matchesRegex:(NSString *)regex options:(NSRegularExpressionOptions)options {
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:NULL];
if (!pattern) return NO;

return ([pattern numberOfMatchesInString:self options:0 range:NSMakeRange(0, self.length)] > 0);
}

之后,则是进行API的设计。基于中文字符的特殊性(中文文字、中文标点和中文部首等),以及API灵活性设计的需求,我们可以通过Options对中文进行类型区分,满足不同的API使用需求:

1
2
3
4
5
6
7
8
typedef NS_OPTIONS(NSUInteger, CHNSStringChineseType) {   // 中文字符类型
CHNSStringChineseTypeCharacter = 1 << 0, // 中文文字
CHNSStringChineseTypePunctuation = 1 << 1, // 中文标点
CHNSStringChineseTypeRadical = 1 << 2, // 中文部首
CHNSStringChineseTypeStroke = 1 << 3, // 中文笔划
CHNSStringChineseTypeIdeographicDescription = 1 << 4, // 中文构字描述符
CHNSStringChineseTypeAll = CHNSStringChineseTypeCharacter | CHNSStringChineseTypePunctuation | CHNSStringChineseTypeRadical | CHNSStringChineseTypeStroke | CHNSStringChineseTypeIdeographicDescription
};

针对不同中文字符类型,提供对应的正则表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (NSString *)_ch_ChineseRegex:(CHNSStringChineseType)type {
NSMutableString *regex = [NSMutableString stringWithString:@""];

CHNSStringChineseType aType = type ? type : CHNSStringChineseTypeAll;
if (aType & CHNSStringChineseTypeCharacter) {
[regex appendString:[self _ch_ChineseCharactersRegex]];
}

if (aType & CHNSStringChineseTypePunctuation) {
[regex appendString:[self _ch_ChinesePunctuationsRegex]];
}

if (aType & CHNSStringChineseTypeRadical) {
[regex appendString:[self _ch_ChineseRadicalsRegex]];
}

if (aType & CHNSStringChineseTypeStroke) {
[regex appendString:[self _ch_ChineseStrokesRegex]];
}

if (aType & CHNSStringChineseTypeIdeographicDescription) {
[regex appendString:[self _ch_ChineseIdeographicDescriptionsRegex]];
}

if (!regex.length) return regex;

[regex insertString:@"[" atIndex:0];
[regex appendString:@"]"];
return regex.copy;
}

提供功能性API供外界使用:
字符串是否为单个中文字符:

1
2
3
4
5
6
7
8
- (BOOL)ch_isChinese:(CHNSStringChineseType)type {
NSMutableString *regex = [NSMutableString stringWithString:[self _ch_ChineseRegex:type]];
if (!regex || !regex.length) return NO;

[regex insertString:@"^" atIndex:0];
[regex appendString:@"$"];
return [self _ch_matchesRegex:regex.copy options:0];
}

字符串是否包含中文字符:

1
2
3
4
5
6
7
8
9
- (BOOL)ch_containsChinese:(CHNSStringChineseType)type {
__block BOOL isContained = NO;
[self ch_enumerateChinese:type inRange:NSMakeRange(0, self.length) usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
isContained = YES;
*stop = YES;
}];

return isContained;
}

截取指定范围内,字符串中的所有中文字符:

1
2
3
4
5
6
7
8
9
10
- (NSString *)ch_substringWithChinese:(CHNSStringChineseType)type inRange:(NSRange)range {
NSString *substring = [self substringWithRange:range];
if (!substring || !substring.length) return substring;

__block NSMutableString *buffer = [NSMutableString stringWithString:@""];
[substring ch_enumerateChinese:type inRange:NSMakeRange(0, substring.length) usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[buffer appendString:substring];
}];
return buffer.copy;
}

根据遍历范围,遍历字符串中的所有中文字符:

1
2
3
4
5
6
7
8
9
- (void)ch_enumerateChinese:(CHNSStringChineseType)type
inRange:(NSRange)range
usingBlock:(void (^)(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop))block {
[self enumerateSubstringsInRange:range options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
if ([substring _ch_matchesRegex:[self _ch_ChineseRegex:type] options:0]) {
!block ?: block(substring, substringRange, enclosingRange, stop);
}
}];
}

将字符串中的所有中文字符替换为指定字符:

1
2
3
- (NSString *)ch_stringByReplacingChinese:(CHNSStringChineseType)type withString:(NSString *)replacement {
return [self _ch_stringByReplacingRegex:[self _ch_ChineseRegex:type] options:0 withString:replacement];
}

筛除字符串中所有的中文字符:

1
2
3
- (NSString *)ch_stringByTrimmingChinese:(CHNSStringChineseType)type {
return [self ch_stringByReplacingChinese:type withString:@""];
}

至此,API设计基本完成,可满足一般字符串的中文处理需求。

附,示例代码:https://github.com/ColinHwang/NSString-CHChinese

参考资料:

[1]维基百科.中日韩统一表意文字[EB/OL].https://zh.wikipedia.org/wiki/%E4%B8%AD%E6%97%A5%E9%9F%93%E7%B5%B1%E4%B8%80%E8%A1%A8%E6%84%8F%E6%96%87%E5%AD%97 ,2017-6-9.
[2]维基百科.维基百科:Unicode扩展汉字[EB/OL].https://zh.wikipedia.org/wiki/Wikipedia:Unicode%E6%89%A9%E5%B1%95%E6%B1%89%E5%AD%97 ,2017-6-9.
[3]维基百科.中日韩兼容表意文字[EB/OL].https://zh.wikipedia.org/wiki/%E4%B8%AD%E6%97%A5%E9%9F%93%E7%9B%B8%E5%AE%B9%E8%A1%A8%E6%84%8F%E6%96%87%E5%AD%97 ,2017-6-9.
[4]维基百科.中日韩兼容表意文字补充区[EB/OL].https://zh.wikipedia.org/wiki/%E4%B8%AD%E6%97%A5%E9%9F%93%E7%9B%B8%E5%AE%B9%E8%A1%A8%E6%84%8F%E6%96%87%E5%AD%97%E8%A3%9C%E5%85%85%E5%8D%80 ,2017-6-9.
[5]小莫M.Unicode中关于中文和其他特殊字符的编码范围[EB/OL].http://blog.csdn.net/monitor1394/article/details/7255767 ,2012-2-13.
[6]Apple Inc.NSRegularExpression Reference Class[EB/OL].https://developer.apple.com/reference/foundation/nsregularexpression ,2017-6-9.