iOS
Swift
swift4

漢字をひらがな/カタカナに変換する

More than 1 year has passed since last update.

以前書いた記事では、文字列を全角/半角に変換するのを紹介しましたが、今回は「漢字」をひらがなあるいはカタカナに変換する処理を紹介します。
ユーザーが入力した住所の文字列をカタカナに自動的に変換する必要があったので調べつつ実装してみました。

その前に

Stringには、 applyingTransform(_:reverse) という関数があって、ある程度これを使うことで変換ができるのですが、
今回のように漢字をひらがな/カタカナに変換する用途では使えるものがありませんでした。
toLatin あったので、 toLatin からの latinToHiragana を試しましたがイマイチ...。

let hiragana = "佐藤"
    .applyingTransform(.toLatin, reverse: false)?
    .applyingTransform(.latinToHiragana, reverse: false)
print(hiragana)
// "ずお̌ て́んぐ"

...なので、CoreFoundationを使って変換する処理を実装することにしました。

実装

final class TextConverter {
    private init() {}
    enum JPCharacter {
        case hiragana
        case katakana
        fileprivate var transform: CFString {
            switch self {
            case .hiragana:
                return kCFStringTransformLatinHiragana
            case .katakana:
                return kCFStringTransformLatinKatakana
            }
        }
    }

    static func convert(_ text: String, to jpCharacter: JPCharacter) -> String {
        let input = text.trimmingCharacters(in: .whitespacesAndNewlines)
        var output = ""
        let locale = CFLocaleCreate(kCFAllocatorDefault, CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault, "ja" as CFString))
        let range = CFRangeMake(0, input.utf16.count)
        let tokenizer = CFStringTokenizerCreate(
            kCFAllocatorDefault,
            input as CFString,
            range,
            kCFStringTokenizerUnitWordBoundary,
            locale
        )

        var tokenType = CFStringTokenizerGoToTokenAtIndex(tokenizer, 0)
        while (tokenType.rawValue != 0) {
            if let text = (CFStringTokenizerCopyCurrentTokenAttribute(tokenizer, kCFStringTokenizerAttributeLatinTranscription) as? NSString).map({ $0.mutableCopy() }) {
                CFStringTransform(text as! CFMutableString, nil, jpCharacter.transform, false)
                output.append(text as! String)
            }
            tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)
        }
        return output
    }
}

仕組みとしては、トークナイザに文字を食わせて、1つずつトークンを取り出して、latin文字として取り出した後に、kCFStringTransformLatinHiraganaあるいはkCFStringTransformLatinKatakanaを使って変換し、文字を連結して最後に返却しています。

また、今回はclassにしましたが、適宜Stringのextensionにしても良いかもしれません。

(半角)スペースや改行文字の削除

また、最初に入力文字のうち、スペースや改行文字を弾くようにしています。
文字の組み合わせによっては半角スペースがあるとおかしな変換をすることがあったので、事前に弾くことにしました。

# スペース除外なし
TextConverter.convert("岸本 花子", to: .katakana)
// ア̀ン ベ̌ン ヘゥアー ゼィ
# スペース除外あり
TextConverter.convert("岸本 花子", to: .katakana)
// キシモトハナコ

rangeの扱い

CFRangeを作るときは、.countではなく、.utf16.ountを使っています。
これによって絵文字が文字化けするのを防ぎます。(がっつり検証してるわけじゃないので場合によってはやばいかも)

実行結果

let inputs: [String] = [
    "佐藤 太郎",
    "さとうたろう",
    "東京都",
    "渋谷区",
    "😇🎂👨‍👩‍👧"
]

inputs.map { TextConverter.convert($0, to: .katakana) }.forEach {
    print($0)
}
"サトウタロウ"
"サトウタロウ"
"トウキョウト"
"シブヤク"
"😇🎂👨‍👩‍👧"

:clap::clap: