Help us understand the problem. What is going on with this article?

NSStringから絵文字を外す

More than 5 years have passed since last update.

 日々、Xcode の機嫌を伺う読者諸賢の皆様、こんばんは。これは、Objective-C Advent Calendar 2012の3日目の記事です。以前、NSString な文字列から絵文字だけを除去する必要に駆られたことがあったので、不幸にも同じ事態に陥った方の為のメモを残しておきます。

サロゲートペア(代用対)

 NSString は UTF-16のラッパだそうで、その文字列にはデータ長が 16bit の文字と、サロゲートペアと呼ばれる 32bit の文字が混在しています。サロゲートペアは、上位 16bit が 0xD800~0xDBFF、下位 16bit が 0xDC00~0xDFFF の範囲に収まることになっていて、ある文字がサロゲートペアかどうかは、最初の 16bit を見ることで判別できます。

コードポイント(符号点)

 通常、Unicode は U+ から始まるコードポイントと呼ばれるもので表され、U+10000 未満に非サロゲートペア、以上にサロゲートペアが当てられています。例えば、非サロゲートペアである「A」は U+0041、「あ」は U+3042、サロゲートペアである「?」は U+1D11E、「?」は U+2F81A、といった具合です。

 プログラムで文字を扱うには、コードポイントではなくバイト列であることが望ましいです。非サロゲートペア、つまりコードポイントが U+10000 未満の文字なら数値そのまま、サロゲートペアなら以下の様な変換を噛ませることでバイト列を得ることができます。

コードポイントからバイト列
const int codepoint = ;
const unichar high = (codepoint - 0x10000) / 0x400 + 0xd800; // high surrogate
const unichar low = (codepoint - 0x10000) % 0x400 + 0xdc00; // low surrogate
バイト列からコードポイント
const unichar high = ; // high surrogate
const unichar low = ; // low surrogate
const int codepoint = ((high - 0xd800) * 0x400) + (low - 0xdc00) + 0x10000;

実装

 実装例です。for 文を回して characterAtIndex: メソッドで文字を拾おうとすると、サロゲートペアかどうかお構いなく文字列を分割してしまうので、enumerateSubstringsInRange:options:usingBlock: メソッドを使います。

@interface NSString (RemoveEmoji)

- (NSString*)removedEmojiString;

@end

@implementation NSString (RemoveEmoji)

- (NSString*)removedEmojiString {
   NSMutableString* __block buffer = [NSMutableString stringWithCapacity:[self length]];

    [self enumerateSubstringsInRange:NSMakeRange(0, [self length])
                             options:NSStringEnumerationByComposedCharacterSequences
                          usingBlock: ^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop) {         
         const unichar high = [substring characterAtIndex: 0];

         // Surrogate pair (U+1D000-1F77F)
         if (0xd800 <= high && high <= 0xdbff) {
             const unichar low = [substring characterAtIndex: 1];
             const int codepoint = ((high - 0xd800) * 0x400) + (low - 0xdc00) + 0x10000;

             [buffer appendString: (0x1d000 <= codepoint && codepoint <= 0x1f77f)? @"": substring];

         // Not surrogate pair (U+2100-27BF)
         } else {
             [buffer appendString: (0x2100 <= high && high <= 0x27bf)? @"": substring];
         }
     }];

    return buffer;
}

@end

とりあえず、除外する範囲を U+1D000-1F77F と U+2100-27BF としていますが、わりと適当に決めた範囲なので不足があると思います。絵文字の範囲は、正直よくわかりません。適宜変更してください(丸投げ)。

その他

 サロゲートペアは、上位サロゲートと下位サロゲートを並べて書式 @"%C%C" で表示や生成ができます。

NSLog(@"%C%C", [@"?" characterAtIndex:0], [@"?" characterAtIndex:1]);
NSString* g_clef = [NSString stringWithFormat: @"%C%C", [@"?" characterAtIndex:0], [@"?" characterAtIndex:1]];

終わりに

 日本語版の Wikipedia に、コードポイントの分類などが非常に見やすくまとまっていたので、参照してみてください。

Unicode - Wikipedia

追記

 CocoaPods に登録しました。どうぞ、可愛がってやってください。

woxtu/NSString+RemoveEmoji

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away