本文へスキップ

Unicodeの空白文字・見えない文字一覧と検出方法

14

はじめに

文字列には、半角スペースや全角スペース以外にも、画面上では見分けにくい文字があります。 コピー&ペーストした値が一致しない、入力チェックを通り抜ける、CSVやソースコードの解析に 失敗するといった問題の原因になります。

ただし、「空白文字」と「表示されない文字」は同じものではありません。

  • UnicodeのWhite_Spaceプロパティを持つ空白文字は25個
  • ゼロ幅スペースや結合・方向制御文字は、見えなくてもWhite_Spaceではない
  • 点字空白やハングルフィラーは空白に見えるが、文字カテゴリ上は空白ではない

この記事では、Unicode 17.0の定義を基準に、用途別の一覧とJavaScriptでの調べ方をまとめます。

Unicodeで定義された空白文字一覧

Unicode Character DatabaseのWhite_Spaceプロパティに含まれる25コードポイントです。 「コピー」ボタンはコード表記ではなく、実体の1文字をクリップボードへ保存します。

UnicodeJavaScript表記名前用途・特徴コピー
U+0009\u0009CHARACTER TABULATION水平タブ
U+000A\u000ALINE FEED改行(LF)
U+000B\u000BLINE TABULATION垂直タブ
U+000C\u000CFORM FEED改ページ
U+000D\u000DCARRIAGE RETURN復帰(CR)
U+0020\u0020SPACE半角スペース
U+0085\u0085NEXT LINE次行制御
U+00A0\u00A0NO-BREAK SPACE改行を禁止するスペース(NBSP)
U+1680\u1680OGHAM SPACE MARKオガム文字用のスペース
U+2000\u2000EN QUAD1en幅
U+2001\u2001EM QUAD1em幅
U+2002\u2002EN SPACEenスペース
U+2003\u2003EM SPACEemスペース
U+2004\u2004THREE-PER-EM SPACE1/3em幅
U+2005\u2005FOUR-PER-EM SPACE1/4em幅
U+2006\u2006SIX-PER-EM SPACE1/6em幅
U+2007\u2007FIGURE SPACE数字と同じ固定幅
U+2008\u2008PUNCTUATION SPACE句読点と同程度の幅
U+2009\u2009THIN SPACE細いスペース
U+200A\u200AHAIR SPACETHIN SPACEより細いスペース
U+2028\u2028LINE SEPARATOR行区切り
U+2029\u2029PARAGRAPH SEPARATOR段落区切り
U+202F\u202FNARROW NO-BREAK SPACE細い改行禁止スペース
U+205F\u205FMEDIUM MATHEMATICAL SPACE数式用の中幅スペース
U+3000\u3000IDEOGRAPHIC SPACE全角スペース

最新の定義はUnicode公式の PropList.txtで確認できます。

よく問題になる「見えないが空白ではない文字」

次の文字は表示されない、または非常に見分けにくいものの、UnicodeのWhite_Spaceには 含まれません。文字の結合、改行位置、双方向テキスト、数式などに意味を持つため、 無条件に削除すると表示や言語処理を壊す場合があります。

UnicodeJavaScript表記名前主な役割コピー
U+00AD\u00ADSOFT HYPHEN必要な場合だけ表示されるハイフン
U+034F\u034FCOMBINING GRAPHEME JOINER結合文字の境界制御
U+061C\u061CARABIC LETTER MARKアラビア文字の方向制御
U+180E\u180EMONGOLIAN VOWEL SEPARATORモンゴル文字の字形制御
U+200B\u200BZERO WIDTH SPACE幅を持たない改行候補
U+200C\u200CZERO WIDTH NON-JOINER文字の結合を抑止
U+200D\u200DZERO WIDTH JOINER文字や絵文字の結合
U+200E\u200ELEFT-TO-RIGHT MARK左から右の方向指定
U+200F\u200FRIGHT-TO-LEFT MARK右から左の方向指定
U+2060\u2060WORD JOINER幅を持たず改行を禁止
U+2061\u2061FUNCTION APPLICATION関数適用
U+2062\u2062INVISIBLE TIMES乗算
U+2063\u2063INVISIBLE SEPARATOR区切り
U+2064\u2064INVISIBLE PLUS加算
U+2066\u2066LEFT-TO-RIGHT ISOLATE左から右の方向分離
U+2067\u2067RIGHT-TO-LEFT ISOLATE右から左の方向分離
U+2068\u2068FIRST STRONG ISOLATE先頭の強い文字に従って方向を分離
U+2069\u2069POP DIRECTIONAL ISOLATE方向分離を終了
U+FEFF\uFEFFZERO WIDTH NO-BREAK SPACE / BOM先頭ではBOM、途中では非推奨のZWNBSP

特にU+200Bは名前にSPACEを含みますが、Unicodeでは幅のある空白ではなく、改行候補を 示すための書式文字です。詳細はUnicode 17.0の Zero Width Spaceの説明を 参照してください。

空白に見える別の文字

次の文字は空白のような字形になることがありますが、White_Spaceではありません。 ユーザー名や識別子の検証では、単純な空白除去だけでは検出できない点に注意が必要です。 「コピー」ボタンを押すと、コード表記ではなく実体の1文字をコピーできます。

UnicodeJavaScript表記名前Unicodeカテゴリコピー
U+115F\u115FHANGUL CHOSEONG FILLERLetter
U+1160\u1160HANGUL JUNGSEONG FILLERLetter
U+2800\u2800BRAILLE PATTERN BLANKSymbol
U+3164\u3164HANGUL FILLERLetter
U+FFA0\uFFA0HALFWIDTH HANGUL FILLERLetter

JavaScriptで空白文字を検出・置換する

UnicodeのWhite_Spaceを検出する

Unicodeプロパティエスケープを使うと、Unicodeの定義を直接指定できます。

const hasWhiteSpace = /\p{White_Space}/u.test("foo\u00A0bar");

console.log(hasWhiteSpace); // true

連続する空白を半角スペース1個へ統一する例です。

const normalizeWhiteSpace = (text) =>
  text.replace(/\p{White_Space}+/gu, " ").trim();

console.log(normalizeWhiteSpace("foo\u3000\u00A0bar")); // "foo bar"

すべての空白を削除する処理は、自然文の単語境界や改行も失います。IDやコードなど、 空白を許可しない値にだけ適用してください。

\s\p{White_Space}trim()の違い

JavaScriptの\strim()はECMAScript仕様の空白を使います。Unicodeの White_Spaceとほぼ同じですが、完全には一致しません。

対象U+0085 NEXT LINEU+FEFF BOM / ZWNBSPU+200B ZERO WIDTH SPACE
/\p{White_Space}/u対象対象外対象外
/\s/u対象外対象対象外
trim()対象外文字列の両端なら対象対象外

ECMAScriptの定義は TC39のWhite Space仕様String.prototype.trim仕様で 確認できます。

ゼロ幅スペースなどを検出する

削除対象を用途に合わせて明示します。次の例は、コピー&ペーストで混入しやすい ゼロ幅スペース、ワードジョイナー、BOMだけを対象にしています。

const commonZeroWidthChars = /[\u200B\u2060\uFEFF]/u;
const removeCommonZeroWidthChars = (text) =>
  text.replace(/[\u200B\u2060\uFEFF]/gu, "");

console.log(commonZeroWidthChars.test("foo\u200Bbar")); // true
console.log(removeCommonZeroWidthChars("foo\u200Bbar")); // "foobar"

U+200CU+200Dは一部言語の表記や絵文字シーケンスに必要です。双方向制御文字も 正当な用途があるため、入力元と許可する文字を決めずに一括削除しないでください。

文字列に含まれるコードポイントを調べる

見た目が同じ文字列を比較するときは、各文字をU+XXXX形式へ変換すると原因を確認できます。 サロゲートペアを含む文字にも対応した例です。

const toCodePoints = (text) =>
  Array.from(text, (char) => {
    const hex = char.codePointAt(0).toString(16).toUpperCase();
    return `U+${hex.padStart(4, "0")}`;
  });

console.log(toCodePoints("A\u200BB"));
// ["U+0041", "U+200B", "U+0042"]

不可視文字の位置も表示する場合は、インデックスを付けます。

const inspectText = (text) =>
  Array.from(text, (char, index) => ({
    index,
    codePoint: toCodePoints(char)[0],
    visible: /\p{White_Space}/u.test(char) ? "␠" : char,
  }));

console.table(inspectText("foo\u00A0bar"));

実装時の判断基準

  • 表示用の文章では、意味のある結合・方向制御文字を保持する
  • ユーザー名やIDでは、許可する文字をホワイトリストで定義する
  • CSVやログの調査では、削除前にコードポイントを可視化する
  • 空白の正規化と不可視書式文字の除去を別の処理にする
  • 絵文字を扱う場合は、特にU+200Dを削除しない

「見えないから不要」とは限りません。問題の文字を特定したうえで、入力の用途に必要かどうかを 判断するのが安全です。

参考資料

おすすめの記事

最新の記事

著者について

404

s-yoshiki

ただの備忘録です。

※外部送信に関する公表事項