
こんにちは!普段フロントエンドで開発しているK.Sです☃️
皆さんは「PCではファイルがアップロードできるのにiPadではできない!」という不具合に遭遇した経験はありますか?私はあります😅
今回は実際にプロジェクトで直面した「NFC/NFD問題」について、その時の状況を交えながら原因と解決方法をご紹介したいと思います。
こちらの記事を参考にさせていただきました。
発生したときの状況
私が携わっていた製品の一部機能では、PC用の画面に加え、iPadやAndroidなどのタブレット端末向けの画面が用意されていました。
その機能の一つにファイルをアップロードできるものがあったのですが、iPadだけが特定のファイルのアップロードに失敗するという不具合が生じていました。
なぜ起こったのか?
原因の調査のためにファイル名の文字数を変えるなどしてアップロードしていたところ、ふと特定の単語が含まれているとアップロードに失敗することに気が付きました。
そして突き詰めると、原因がはっきりとしました。
そう、「濁音」だったのです。
Unicode正規化
Unicode正規化とは、同じ意味を表す複数のUnicodeの表現を、一定の形に揃える処理のことを言います。
例えばがという文字の場合以下のように二つの表現があります。
| 種類 | 表現 | Unicodeコード列 | 文字構成 |
|---|---|---|---|
| NFD (Normalization Form Canonical Decomposition) | か + ゛ |
U+304B + U+3099 |
「か」+結合文字(濁点) |
| NFC (Normalization Form Canonical Composition) | が |
U+304C |
(単一の文字) |
NFD形式はDecomposition(分解)とある通り、文字の分解を行うものです。結合文字列と呼ばれます。
それに対し、NFC形式はComposition(合成)とある通り、文字の合成を行うものです。合成済み文字と呼ばれます。NFD(分解)の後、再度合成するのがNFCになります。
この二つのがは見た目は同じものの、中身のコード列が異なるため別の文字と評価されます。
const a = 'が'; // U+304C const b = 'か\u3099'; // U+304B + U+3099 console.log(a === b); // false
PC(Windows)やその他の多くではNFC形式ですが、MacOSやiPadOSではNFD形式を採用しています。
そのため、iPadから濁音を含むファイルをアップロードした際にバックエンドでエラーが発生していました。
行ったこと
NFD形式のファイル名がアップロードされるとNFC形式に変換するようにしました。
実際のコードはお見せできませんが、以下のようにnormalize関数を使用することで、NFC形式へ変換することが可能です。
const normalizeFileName = (file: File): File => { const normalizedName = file.name.normalize('NFC'); return new File([file], normalizedName, { type: file.type, lastModified: file.lastModified, }); };
これで無事iPadでもファイルをアップロードできるようになりました!
余談
ちなみに、今回ご紹介したNFC/NFDのほかに、
「見た目や意味が同じ文字を統一的に扱いたい場面に使用できる形式」もあります。
それがNFKDとNFKCです。
以下は半角ガの例です。半角ガは全角ガとして扱うことができます。
| 種類 | 表現 | Unicodeコード列 | 文字構成 |
|---|---|---|---|
| NFKD (Normalization Form Compatibility Decomposition) | 全角カ + ゙ |
U+30AB + U+3099 |
「カ」+結合文字(濁点) |
| NFKC (Normalization Form Compatibility Composition) | 全角ガ |
U+30AC |
(単一の文字、全角カタカナ) |
互換等価性(異なる表示形式を持つが、機能的には同等であることを示す性質)によって、半角カナガは全角カ+濁点に分解(NFKD)され、合成(NFKC)が行われます。
以下はNFKC形式を利用した例です。 フォームや検索ボックスなどのユーザー入力で入力値を統一して検索したいときに便利です。
const normalizeInput = (value: string) => { return value.normalize('NFKC'); }; // 商品データ const products = ['アップル', 'バナナ', 'グレープ']; // ユーザー入力(例:半角カナで入力) const userInput = 'グレープ'; // 正規化して検索 const found = products.find((item) => item === normalizeInput(userInput)); console.log(found); // => "グレープ"
まとめ
今回はNFC/NFD問題についてご紹介しました。ファイル名の濁音が原因なんてなかなか気づけないですよね・・・🫠
このブログが参考になれば幸いです。ありがとうございました!
おわりに
KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp