🖊️

【Swift】ひらがな→カタカナ変換を(applyingTransformを使わずに)実装してみた

2024/10/23に公開

はじめに

Swift の String 型には applyingTransform(_:reverse:) という便利なメソッドがあり、これを使うことで基本的にはひらがなをカタカナに変換することが出来ます。

"あいうえお".applyingTransform(.hiraganaToKatakana, reverse: false)!
// アイウエオ

ただこのメソッドは ぁ(U+3041)から ゔ(U+3094) までのひらがなについては変換してくれますが、

  • ゕ(U+3095) (HIRAGANA LETTER SMALL KA)
  • ゖ(U+3096) (HIRAGANA LETTER SMALL KE)

の 2 文字については変換してくれないようです。ヵ(U+30F5)ヶ(U+30F6)になるわけでもありません。

"ゕゖ".applyingTransform(.hiraganaToKatakana, reverse: false)!
// ゕゖ

https://www.unicode.org/charts/PDF/U3040.pdf
https://www.unicode.org/charts/PDF/U30A0.pdf

まぁこれらの文字はかなり特殊な扱いのようですし、iOS のキーボードでも基本的には打ち込めないようなので気にする必要はないかもしれませんが、仕様上これらも変換対象として扱いたかったので自前で実装してみました。
また、この手の日本語を扱うような Apple 標準 API は思わぬところで誤動作を起こすことが経験上良くあり、個人的には正直あまり信用出来ないというのもあります。

実装

String extension の形で実装してみました。

import Foundation

public extension String {
    func hiraganaToKatakana() -> String {
        func isHiragana(_ scalar: UnicodeScalar) -> Bool {
            return scalar.value >= 0x3041 && scalar.value <= 0x3096
        }

        let convertedScalars = self.unicodeScalars.map { scalar in
            guard isHiragana(scalar) else { return scalar } // ひらがな以外はそのまま返す

            if let converted = UnicodeScalar(scalar.value + 0x60) {
                return converted
            } else {
                // UnicodeScalar.init(_:)の引数に (0...0xD7FF) もしくは (0xE000...0x10FFFF) 以外の値を渡すと nil が返却されるようなので、今回(0x3041...0x3096)は nil が返却されることはないはず
                // https://developer.apple.com/documentation/swift/unicode/scalar/init(_:)-96l5f#parameters
                fatalError("Failed to convert: \(scalar)")
            }
        }
        return String(String.UnicodeScalarView(convertedScalars))
    }
}

Unicode のコードポイントにおいて、ひらがなの各文字に対して0x60を足すと、ちょうどカタカナの対応する文字になるように配置されています。
これを利用して、文字列中の UnicodeScalar に対して、ひらがな(0x3041...0x3096)である場合は 0x60 を足していき、最後に String に戻すという処理になります。

fatalError()を投げている部分がありますが、コメントにもあるように、今回の場合はここを通ることは無いという認識です。

動作確認

呼び出してみると、ゕ(U+3095)ゖ(U+3096)もしっかり変換されていることが分かります。

"ゕゖ".hiraganaToKatakana()
// ヵヶ

もちろん、ぁ(U+3041)から ゔ(U+3094)までも問題無く変換してくれます。

"ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔ".hiraganaToKatakana()
// ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴ

ちなみに結合文字列についても問題無く変換してくれます。UnicodeScalar として処理しているため、濁点等の結合文字はそのままに、基底文字だけをカタカナに変換してくれるためです。

"\u{304B}\u{3099}".hiraganaToKatakana() // 「か」+「゛」(濁点)
// ガ

"\u{306F}\u{309A}".hiraganaToKatakana() // 「は」+「゜」(半濁点)
// パ

もちろん、ひらがな以外は変換せずにそのまま返します。

"アイウエオ".hiraganaToKatakana()
// アイウエオ

"abc".hiraganaToKatakana()
// abc

"123".hiraganaToKatakana()
// 123

"!\"#$&'()+,./:;<=?@[\\]^_`{|}~".hiraganaToKatakana()
// !"#$&'()+,./:;<=?@[\]^_`{|}~

サロゲートペアを使うような文字も問題なさそうです。

"😄😆😅😓😢".hiraganaToKatakana()
// 😄😆😅😓😢

"𠀋𡈽𡌛𡑮𡢽".hiraganaToKatakana()
// 𠀋𡈽𡌛𡑮𡢽

終わりに

意外と簡単に変換処理が実装出来たように思いますが、文字コードの世界はとてつもなく深いので、もしかしたら何か間違いがあるかもしれません。その際は優しくご指摘いただけると幸いです。

株式会社ワンポイントファイブ

Discussion