【JavaScript】機種依存文字・特殊文字のチェックと除去方法完全ガイド|正規表現・Unicode対応

スポンサーリンク

機種依存文字とは?なぜ問題になるか

機種依存文字(きしゅいぞんもじ)とは、特定のOS・フォント・文字コードに依存し、環境によって正しく表示されなかったり文字化けを起こす文字のことです。Windows環境で入力した①②③などの囲み数字や、㈱・㍻などの記号は、macOSやLinux、古いシステムとのデータ連携時に問題を起こす代表例です。

機種依存文字が問題になるシーン

  • フォームからDBへ保存するとき文字化けが発生する
  • CSVエクスポート後にExcelで開くと文字が消える
  • APIへのリクエストでパースエラーが起きる
  • メール送信時に文字化けする(特にShift-JIS環境)

本記事では、JavaScriptで機種依存文字・特殊文字を検出・除去・変換する実践的な方法をコード付きで解説します。

代表的な機種依存文字一覧

以下はWindowsのShift-JIS環境で使われやすい機種依存文字の代表例です。

文字 名称 Unicodeコードポイント 問題点
①②③… 囲み数字 U+2460〜U+2468 Shift-JIS独自領域・環境依存
Ⅰ Ⅱ Ⅲ… ローマ数字 U+2160〜U+216B 半角数字・アルファベットと混同
㈱ ㈲ ㈹ 組文字 U+3231, U+3232 など 環境によって表示が崩れる
㍉ ㎝ ㎞ 単位記号 U+3374, U+339D など フォント非対応で豆腐表示
㍻ ㍼ ㍽ ㍾ 元号 U+337B〜U+337E 古い環境では未定義
* # @ など 全角英数字・記号 U+FF01〜U+FF5E 半角との混在でDB検索ミス
─ ━ │ ┃ 罫線素片 U+2500〜U+257F 環境差が大きい

正規表現で機種依存文字を検出する方法

Unicodeプロパティエスケープを使う(ES2018以降)

最もモダンな方法はUnicodeプロパティエスケープ(\p{})です。ChromeやFirefox、Node.js v10以降で利用できます。

// 囲み数字を検出(U+2460〜U+2473)
const text1 = '申込番号①と②を確認してください';
const hasEncircled = /[\u2460-\u2473]/u.test(text1);
console.log(hasEncircled); // true

// 全角英数字を検出(U+FF01〜U+FF5E)
const text2 = 'ユーザーID:ABC123';
const hasFullwidth = /[\uFF01-\uFF5E]/u.test(text2);
console.log(hasFullwidth); // true

// ローマ数字を検出(U+2160〜U+216B)
const text3 = '第ⅢフェーズのAPI連携';
const hasRoman = /[\u2160-\u216B]/u.test(text3);
console.log(hasRoman); // true

// まとめて機種依存文字を検出する関数
function hasMachineDependent(str) {
  return /[\u2460-\u2473\u2460-\u24FF\u2160-\u216B\u3231-\u3247\u337B-\u337E\u3374\u339D\xFF01-\uFF5E]/u.test(str);
}

console.log(hasMachineDependent('①②㈱'));  // true
console.log(hasMachineDependent('abc123')); // false

Unicodeブロック範囲で検出する

より広いUnicode範囲をカバーしたい場合は、対象ブロックを指定します。

// 機種依存文字ブロックの範囲指定
const MACHINE_DEPENDENT_RANGES = [
  [0x2460, 0x24FF], // 囲み英数字
  [0x2100, 0x214F], // レターライク記号
  [0x2160, 0x218F], // 数字形
  [0x3200, 0x32FF], // 囲み中字(㈱等)
  [0x3300, 0x33FF], // CJK互換文字(㎝等)
  [0xFF01, 0xFF5E], // 全角英数字・記号
  [0xFF65, 0xFFEF], // 半角カタカナ・記号
];

function detectMachineDependent(str) {
  const results = [];
  for (const char of str) {
    const cp = char.codePointAt(0);
    for (const [start, end] of MACHINE_DEPENDENT_RANGES) {
      if (cp >= start && cp <= end) {
        results.push({ char, codePoint: cp.toString(16).toUpperCase() });
        break;
      }
    }
  }
  return results;
}

const sample = '①製品名ABC㈱開発部';
const found = detectMachineDependent(sample);
console.log(found);
// [
//   { char: '①', codePoint: '2460' },
//   { char: 'A', codePoint: 'FF21' },
//   ...
// ]

機種依存文字を除去する方法

String.prototype.replaceで削除する

replace()に正規表現を渡すことで、機種依存文字をまとめて空文字に置換(削除)できます。

// 囲み数字を除去
function removeEncircledNumbers(str) {
  return str.replace(/[\u2460-\u2473]/gu, '');
}
console.log(removeEncircledNumbers('①申込 ②確認 ③送信'));
// ' 申込  確認  送信'

// 全角英数字を除去
function removeFullwidthAlnum(str) {
  return str.replace(/[\uFF01-\uFF5E]/gu, '');
}
console.log(removeFullwidthAlnum('ID:ABC、パスワード:123'));
// 'ID:、パスワード:'

// 広範囲の機種依存文字を一括除去
function removeMachineDependent(str) {
  return str.replace(
    /[\u2460-\u24FF\u2100-\u214F\u2160-\u218F\u3200-\u33FF\uFF01-\uFF5E\uFF65-\uFFEF]/gu,
    ''
  );
}

const dirty = '①ABC㈱㎝㍻テスト';
console.log(removeMachineDependent(dirty)); // 'テスト'

注意:単純削除するとテキストが意味不明になることがあります。変換(後述)のほうがユーザーフレンドリーです。

機種依存文字を対応する標準文字に変換する方法

削除ではなく読みやすい文字に変換するとユーザー体験が向上します。

// 囲み数字 → (1) 形式に変換
const ENCIRCLED_MAP = {};
for (let i = 0; i < 20; i++) {
  ENCIRCLED_MAP[String.fromCodePoint(0x2460 + i)] = `(${i + 1})`;
}
// ①→(1), ②→(2), ..., ⑳→(20)

function convertEncircled(str) {
  return str.replace(/[\u2460-\u2473]/gu, (ch) => ENCIRCLED_MAP[ch] || ch);
}
console.log(convertEncircled('手順①②③に従ってください'));
// '手順(1)(2)(3)に従ってください'

// ローマ数字 → アラビア数字に変換
const ROMAN_MAP = {
  'Ⅰ': '1', 'Ⅱ': '2', 'Ⅲ': '3', 'Ⅳ': '4', 'Ⅴ': '5',
  'Ⅵ': '6', 'Ⅶ': '7', 'Ⅷ': '8', 'Ⅸ': '9', 'Ⅹ': '10',
  'ⅰ': '1', 'ⅱ': '2', 'ⅲ': '3', 'ⅳ': '4', 'ⅴ': '5',
};

function convertRoman(str) {
  return str.replace(/[\u2160-\u2169\u2170-\u2179]/gu, (ch) => ROMAN_MAP[ch] || ch);
}
console.log(convertRoman('第Ⅲ章 → 第ⅱ節'));
// '第3章 → 第2節'

// 組文字変換
const KUMOJI_MAP = {
  '㈱': '(株)', '㈲': '(有)', '㈹': '(代)', '㈻': '(学)',
};

function convertKumoji(str) {
  return str.replace(/[\u3231\u3232\u3239\u323B]/gu, (ch) => KUMOJI_MAP[ch] || ch);
}
console.log(convertKumoji('㈱コーディング㈲テスト'));
// '(株)コーディング(有)テスト'

全角英数字を半角に変換する方法

フォーム入力では全角英数字(A、1、!等)と半角が混在しがちです。一括で半角に正規化しましょう。

// 全角英数字・記号 → 半角に変換
function toHalfWidth(str) {
  return str.replace(/[\uFF01-\uFF5E]/gu, (ch) =>
    String.fromCharCode(ch.charCodeAt(0) - 0xFEE0)
  );
}

// 全角スペース → 半角スペース
function normalizeSpace(str) {
  return str.replace(/\u3000/g, ' ');
}

// まとめた正規化関数
function normalizeFullwidth(str) {
  let result = toHalfWidth(str);    // 全角英数記号 → 半角
  result = normalizeSpace(result);   // 全角スペース → 半角
  return result;
}

console.log(normalizeFullwidth('ABC 123 !?'));
// 'ABC 123 !?'

console.log(normalizeFullwidth('email:test@example.com'));
// 'email:test@example.com'

// 全角カタカナ → 半角カタカナ(用途が限定的なので注意)
function toHalfKana(str) {
  const map = {
    'ア': 'ア', 'イ': 'イ', 'ウ': 'ウ', 'エ': 'エ', 'オ': 'オ',
    'カ': 'カ', 'キ': 'キ', 'ク': 'ク', 'ケ': 'ケ', 'コ': 'コ',
    // ... 必要に応じて追加
    'ー': 'ー', '。': '。', '「': '「', '」': '」', '、': '、',
  };
  return str.replace(/[ア-ン。「」、ー]/g, (ch) => map[ch] || ch);
}

フォームバリデーション実装例(リアルタイムチェック)

入力フォームで機種依存文字をリアルタイムに検出・警告する実装例です。

// HTML例(イメージ)
// <input type="text" id="name-input" />
// <p id="name-warning" style="color:red; display:none;">機種依存文字が含まれています</p>
// <button id="submit-btn">送信</button>

const MACHINE_DEPENDENT_PATTERN = /[\u2460-\u24FF\u2160-\u218F\u3200-\u33FF\uFF01-\uFF5E]/u;

function setupMachineDependentValidator(inputId, warningId) {
  const input = document.getElementById(inputId);
  const warning = document.getElementById(warningId);

  if (!input || !warning) return;

  input.addEventListener('input', () => {
    const val = input.value;
    if (MACHINE_DEPENDENT_PATTERN.test(val)) {
      warning.style.display = 'block';
      input.classList.add('input-error');
    } else {
      warning.style.display = 'none';
      input.classList.remove('input-error');
    }
  });
}

// フォーム送信時のチェック
function validateForm(formData) {
  const errors = [];
  for (const [key, value] of Object.entries(formData)) {
    if (MACHINE_DEPENDENT_PATTERN.test(value)) {
      errors.push(`${key}に機種依存文字が含まれています`);
    }
  }
  return errors;
}

// 使用例
setupMachineDependentValidator('name-input', 'name-warning');

document.getElementById('submit-btn')?.addEventListener('click', (e) => {
  const formData = {
    name: document.getElementById('name-input')?.value || '',
  };
  const errors = validateForm(formData);
  if (errors.length > 0) {
    e.preventDefault();
    alert(errors.join('\n'));
  }
});

ポイント:送信前チェックだけでなく、inputイベントでリアルタイムにフィードバックすることでユーザーが気付きやすくなります。

Node.jsでBufferを使った文字コード確認

Node.js環境ではBufferを使って、文字列のバイト列を確認し、機種依存文字かどうかを診断できます。

// Node.js環境
// 文字のUTF-8バイト列を確認する
function showCharBytes(str) {
  for (const char of str) {
    const buf = Buffer.from(char, 'utf8');
    const hex = [...buf].map(b => b.toString(16).padStart(2, '0')).join(' ');
    const cp = char.codePointAt(0).toString(16).toUpperCase().padStart(4, '0');
    console.log(`U+${cp} "${char}" → UTF-8: ${hex}`);
  }
}

showCharBytes('①A㈱');
// U+2460 "①" → UTF-8: e2 91 a0
// U+FF21 "A" → UTF-8: ef bc a1
// U+3231 "㈱" → UTF-8: e3 88 b1

// Shift-JIS変換が可能かチェック(iconv-lite使用例)
// npm install iconv-lite
const iconv = require('iconv-lite');

function canEncodeAsShiftJIS(str) {
  const buf = iconv.encode(str, 'Shift_JIS');
  const decoded = iconv.decode(buf, 'Shift_JIS');
  return decoded === str;
}

// 注: iconv-liteがない場合はNode.js標準のTextEncoderでUTF-8確認
function getUTF8ByteLength(str) {
  return new TextEncoder().encode(str).length;
}
console.log(getUTF8ByteLength('①'));  // 3 (UTF-8では3バイト)
console.log(getUTF8ByteLength('a'));  // 1

よくある質問(FAQ)

Q正規表現の/u フラグは何をするのですか?
A/uフラグはUnicodeモードを有効にします。\u{XXXX}形式のコードポイント指定や、サロゲートペア(絵文字など4バイト文字)の正しいマッチが可能になります。ES2015以降で使えます。
Q全角カタカナ(ア・イ・ウ)は機種依存文字ですか?
A全角カタカナはUnicodeの「CJK統合漢字」ブロック(U+30A0〜U+30FF)に収録されており、機種依存文字ではありません。ただし半角カタカナ(アイウ・U+FF65〜U+FF9F)は旧来のShift-JISとの互換性のために存在する特殊な文字で、環境によっては問題を起こします。
Q絵文字(?など)は機種依存文字として除去すべきですか?
A絵文字はUnicodeサロゲートペアに属する文字で、環境差が大きいです。DBに保存する場合はutf8mb4コレーション必須です。用途に応じて除去するか、そのまま許容するか判断してください。除去するには/\p{Emoji}/guが使えます(v8エンジン対応)。
Qフォームの機種依存文字チェックはサーバー側でもやるべきですか?
Aはい、必ずサーバー側(PHP/Node.js等)でも検証してください。JavaScriptはブラウザ側のバリデーションであり、DevToolsなどで簡単にスキップできます。二重バリデーションが鉄則です。
Q全角英数字を半角に変換するライブラリはありますか?
Azen-to-hannormalize-japaneseといったnpmパッケージがあります。シンプルな変換なら本記事のtoHalfWidth()関数のようにcharCodeAt()から0xFEE0を引くだけで実装できます。

関連記事

まとめ:機種依存文字チェックのベストプラクティス

  • 検出にはUnicodeブロック範囲の正規表現(/uフラグ必須)を使う
  • 単純削除よりも標準文字への変換(①→(1)、全角→半角)がユーザーフレンドリー
  • 全角英数字の半角変換はcharCodeAt() - 0xFEE0で実装できる
  • フォームバリデーションはフロントエンド(JS)とサーバーサイドの両方で行う
  • Node.jsではBufferTextEncoderを使ったバイト列確認が有効