HTML特殊文字変換

HTML 特殊文字を変換するツールのサンプルです。

JavaScript の replace() メソッドを使って HTML 特殊文字(< >& ' ")をそれぞれ &lt; &gt; &amp; &#39; &quot; に(またはその逆に)変換します。

また、空白行やコメント(// で始まる行や /* */ や <!-- -->で囲まれた部分)を削除することもできます。

コードも掲載しているのでローカルに保存して利用可能です。

作成日:2019年05月10日

特殊文字変換ツール

HTML ソースやコードを入力して「変換」ボタンをクリックすると特殊文字を変換(エスケープ)します。

特殊文字を通常の文字に変換する場合は「通常文字へ変換」を選択します。

結果 :


        

削除ツール

削除ツールをクリックすると、空行やコメントを削除するボタンを表示します。

但し、空行やコメントを削除する際には、特殊文字または通常文字への変換は行いません。

デフォルトでは // で始まるコメントや /* */ コメント、及び <!-- --> コメントを削除しますが、チェックボックスで種類を選択できます。

空行も削除にチェックを入れると、コメントの削除と同時に空行も削除します。

注意

コメントの記述されている位置やその前後のコードの内容により、コメントを正しく判定できない可能性があるのでご注意ください。

また、空行のように見えても空白文字が含まれる場合は空行とみなしません(削除しません)。

replace() で変換

HTML 特殊文字(< >& ' ")をそれぞれ &lt; &gt; &amp; &#39; &quot; に変換するには、JavaScript の replace() メソッドを利用することができます。

例えば、以下のような HTML がある場合、

<div>
  <textarea id="input" rows="10" cols="60"></textarea>
</div>
<div class="controls">
  <button id="convert">変換</button>
</div>
<div id="result-area">
  <pre id="result"></pre>
</div>

「変換」ボタンをクリックして textarea に入力されたコンテンツの HTML 特殊文字を変換して pre 要素に出力するには、 replace() メソッドを使って以下のように記述できます。

replace() の第1引数に検索する文字列の正規表現を指定し、第2引数に関数を指定しています。

この関数は第1引数に指定した検索する文字列にマッチするごとに呼び出され、その返り値が置き換えるテキストとして使用されます。

以下の場合、第1引数に文字クラス [] を使って < >& ' " を指定しているので、< >& ' " のいずれかの文字にマッチすると関数が呼び出されます。

関数では specialChars というマップ(対応表)のようなオブジェクトを定義しています。

例えば、< にマッチした場合、返り値(21行目)の specialChars[match]match には < が入るので、specialChars['<'] となり、対応する値(14行目)'&lt;' に変換されます。

// 変換ボタンの要素
const convertBtn = document.getElementById('convert');
// textarea の要素
const input = document.getElementById('input');
// 出力先の pre 要素
const result = document.getElementById('result');

// 変換ボタンにクリックイベントを設定
convertBtn.addEventListener('click', () => {
  // textarea のコンテンツ(value)を replace() で変換
  const resultValue = input.value.replace(/[<>&'"]/g, (match) => {
    // キーに特殊文字を、値にエスケープ後の文字列を持つオブジェクトを定義
    const specialChars = {
      '<': '&lt;',
      '>': '&gt;',
      '&': '&amp;',
      "'": '&#39;',
      '"': '&quot;'
    };
    // オブジェクトのキーには変数(match)を指定するので、値にアクセスするには specialChars[] を使用。ドット演算子(.)ではアクセスできない。
    return specialChars[match];
  });
  // 出力先の pre 要素のテキストを変換結果に書き換え
  result.textContent = resultValue;
});

replace() の第2引数の関数を使わずに、以下のように replace() をチェーンして変換することもできます。この場合、単純な正規表現で実装でき、各置換を個別に追加、削除、変更できるメリットがあります。

注意する点としては、チェーンして変換する場合は、最初に & を変換する必要があります(変換後の文字に & が含まれているため)。

また、この方法の場合、各置換が別々の処理として行われるため(文字列内の各対象を順に置換していくため)、文字列の長さが増えると処理時間も増加します(※ この例のような使用では処理時間の差はごく僅かであり、ほとんど違いはありません)。

convertBtn.addEventListener('click', () => {
  const resultValue = input.value.replace(/&/g, "&amp;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
  result.textContent = resultValue;
});

関連ページ:replace() 複数置換や動的な値を使った置換

サンプルコード

以下は上記「特殊文字変換ツール」のコードです。

新規に拡張子が .html の空のファイル(例 escape.html)を作成し、以下のコードをコピー(右上の Copy をクリック)して保存すれば、ローカルで使用できます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>特殊文字変換ツール</title>
  <style>
    body {
      margin: 50px auto;
      max-width: 840px;
    }

    h1 {
      font-size: 24px;
    }

    #input {
      width: 95%;
      margin: 10px auto;
      font-size: 13px;
      font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
    }

    #input:focus {
      outline-color: rgba(65, 95, 170, 0.6);
    }

    #result-area {
      margin-top: 30px;
    }

    #result-message {
      font-size: 14px;
      color: #355f15;
    }

    #result {
      width: 95%;
      border: 1px solid #999;
      border-radius: 4px;
      padding: 10px;
      background-color: rgba(192, 206, 241, 0.1);
      font-family: "Lucida Console", Monaco, monospace;
      white-space: pre-wrap;
      overflow: auto;
      font-size: 13px;
      min-height: 100px;
    }

    .controls {
      margin: 10px 0;
    }

    .controls label {
      font-weight: normal;
      font-size: 13px;
      margin-right: 5px;
    }

    .controls button {
      border: 1px solid #999;
      padding: .3rem .5rem;
      margin-right: 20px;
      cursor: pointer;
      background-color: #efefef;
      font-size: .875rem;
    }

    #convert,
    #copy-btn {
      padding: .5rem 1.5rem;
    }

    #clear-all {
      margin-left: 40px;
    }

    #copy-btn {
      width: 10rem;
    }

    input[name=convert-type]:checked+label {
      color: #048d46;
      font-weight: bold;
    }

    input[name=comment-type]+label {
      margin-right: 10px;
      vertical-align: middle;
    }

    input[name=comment-type]:checked+label {
      color: rgb(29, 50, 152);
      background-color: transparent;
    }

    #toggle-removal {
      position: relative;
      padding: 5px 15px 5px 2rem;
      font-size: .875rem;
      margin: 20px 0;
      cursor: pointer;
      background-color: #fff;
      border: 1px solid #999;
    }

    #toggle-removal::before,
    #toggle-removal::after {
      content: "";
      position: absolute;
      left: 10px;
      top: 0;
      bottom: 0;
      margin: auto 0;
      background-color: #333;
      background-color: #048d46;
      width: 14px;
      height: 3px;
      transition: transform 0.3s ease-in, opacity .5s;
    }

    #toggle-removal::after {
      transform: rotate(90deg);
    }

    #toggle-removal.minus::before {
      transform: rotate(180deg);
    }

    #toggle-removal.minus::after {
      transform: rotate(90deg);
      opacity: 0;
    }

    #removal-controls {
      height: 0;
      opacity: 0;
      overflow: hidden;
      transition: height 0.3s, opacity 0.3s;
    }

    #removal-controls.opened {
      height: 40px;
      opacity: 1;
    }

    .removal-toggle-wrapper {
      margin: 20px 0;
    }
  </style>
</head>
<body>
  <h1>特殊文字変換ツール</h1>
  <div>
    <textarea id="input" rows="20" cols="60"></textarea>
  </div>
  <div class="controls">
    <input type="radio" name="convert-type" id="encode" checked>
    <label for="encode"> 特殊文字を変換 </label>
    <input type="radio" name="convert-type" id="decode">
    <label for="decode"> 通常文字へ変換 </label>
  </div>
  <div class="controls">
    <button id="convert">変換</button>
    <button id="copy-btn">結果をコピー</button>
    <button id="clear-all">全てクリア</button>
    <button id="clear-input">入力 をクリア</button>
    <button id="clear-result">結果をクリア</button>
  </div>

  <div class="removal-btn-wrapper">
    <button id="toggle-removal" type="button">削除オプション</button>
  </div>
  <div id="removal-controls" class="controls">
    <button id="remove-empty-lines">空行を削除</button>
    <button id="remove-comments">コメントを削除</button>
    <input type="checkbox" name="comment-type" id="single-line" checked>
    <label for="single-line">//</label>
    <input type="checkbox" name="comment-type" id="multi-line" checked>
    <label for="multi-line">/* */</label>
    <input type="checkbox" name="comment-type" id="html" checked>
    <label for="html">&lt;!-- --&gt;</label>
    <input type="checkbox" name="comment-type" id="remove-empty">
    <label for="remove-empty">空行も削除</label>
  </div>

  <div id="result-area">
    <p>結果 : <span id="result-message"></span></p>
    <pre id="result"></pre>
  </div>
  <script>
   // 各要素とそのテキストを取得
   const convertBtn = document.getElementById('convert');
    const clearAllBtn = document.getElementById('clear-all');
    const clearInputBtn = document.getElementById('clear-input');
    const clearResultBtn = document.getElementById('clear-result');
    const input = document.getElementById('input');
    const copyBtn = document.getElementById('copy-btn');
    const copyBtnText = copyBtn.textContent;
    const result = document.getElementById('result');
    const resultMessage = document.getElementById('result-message');
    const resultMessageText = resultMessage.textContent;
    const removeCommentsBtn = document.getElementById('remove-comments');
    const removeEmptyLineBtn = document.getElementById('remove-empty-lines');

    // 変換ボタンにクリックイベントのリスナーを設定
    convertBtn.addEventListener('click', () => {
      // ラジオボタンの「特殊文字へ変換」が選択されている場合
      if (document.getElementById('encode').checked) {
        const resultValue = input.value.replace(/[<>&'"]/g, (match) => {
          const specialChars = {
            '<': '&lt;',
            '>': '&gt;',
            '&': '&amp;',
            "'": '&#39;',
            '"': '&quot;'
          };
          return specialChars[match];
        });
        result.textContent = resultValue;
        // テキストエリアと変換後の値が同じ場合
        if (input.value === resultValue) {
          resultMessage.textContent = '変換対象の文字はありません。'
        } else {
          // テキストエリアと変換後の値が異なる場合
          resultMessage.textContent = '特殊文字をエスケープしました。'
        }
      } else {
        // ラジオボタンの「通常文字へ変換」が選択されている場合
        const resultValue = input.value.replace(/&lt;|&gt;|&amp;|&#39;|&quot;/g, (match) => {
          const regularChars = {
            '&lt;': '<',
            '&gt;': '>',
            '&amp;': '&',
            "&#39;": "'",
            '&quot;': '"'
          };
          return regularChars[match];
        });
        result.textContent = resultValue;
        if (input.value === resultValue) {
          resultMessage.textContent = '変換対象の文字はありません。'
        } else {
          resultMessage.textContent = '通常文字へ変換しました。'
        }
      }
    });

    // テキストエリアへの入力が変更されたらメッセージを元に戻す
    input.addEventListener('input', () => {
      resultMessage.textContent = resultMessageText;
    });

    // 「全てクリア」をクリックした場合の処理
    clearAllBtn.addEventListener('click', () => {
      result.textContent = '';
      input.value = '';
      resultMessage.textContent = resultMessageText;
    });

    // 「入力をクリア」をクリックした場合の処理
    clearInputBtn.addEventListener('click', () => {
      input.value = '';
    });

    // 「結果をクリア」をクリックした場合の処理
    clearResultBtn.addEventListener('click', () => {
      result.textContent = '';
      resultMessage.textContent = resultMessageText;
    });

    // 「コピー」をクリックした場合の処理
    copyBtn.addEventListener('click', () => {
      copyToClipboard(copyBtn, result.textContent)
    }, false);

    // コピーの処理で呼び出す関数
    function copyToClipboard(btn, text) {
      if (!navigator.clipboard) {
        alert('お使いのブラウザではコピーできません。');
      }
      navigator.clipboard.writeText(text).then(
        () => {
          if (text === '') {
            btn.textContent = 'コピーは空です';
          } else {
            btn.textContent = 'コピーしました';
            const additionalResultMessage = '(以下をコピーしました)';
            if (resultMessage.textContent.search(new RegExp(additionalResultMessage)) === -1) {
              resultMessage.textContent += additionalResultMessage;
            }
          }
          resetCopyBtnText(btn, 1500);
        },
        (error) => {
          btn.textContent = '失敗';
          resetCopyBtnText(btn, 1500);
          console.log(error.message);
        }
      );
    };

    // コピーボタンのテキストを元に戻す関数
    function resetCopyBtnText(btn, delay) {
      setTimeout(() => {
        btn.textContent = copyBtnText;
      }, delay)
    }

    // 「コメントを削除」をクリックした場合の処理
    removeCommentsBtn.addEventListener('click', () => {
      let commentRemovalResult = input.value;
      const beforeCommentRemovalResult = input.value;
      const singleLineCheckBox = document.getElementById('single-line');
      const multiLineCheckBox = document.getElementById('multi-line');
      const htmlCheckBox = document.getElementById('html');
      const removeEmptyCheckBox = document.getElementById('remove-empty');
      if (singleLineCheckBox.checked) {
        commentRemovalResult = commentRemovalResult.replace(/^([^\S\r\n]*\/\/).*$\r?\n?/gm, "").replace(/(.*)\s\/\/.*/g, "$1");;
      }
      if (multiLineCheckBox.checked) {
        commentRemovalResult = commentRemovalResult.replace(/^(.*)\/\*[\s\S]*?\*\/($\r?\n?)?/gm, replaceComments);
      }
      if (htmlCheckBox.checked) {
        commentRemovalResult = commentRemovalResult.replace(/^(.*)<!\-\-[\s\S]*?\-\->($\r?\n?)?/gm, replaceComments);
      }
      let isCommentRemoved = false;
      if (beforeCommentRemovalResult !== commentRemovalResult) isCommentRemoved = true;
      let isEmptyRemoved = false;
      if (removeEmptyCheckBox.checked) {
        const beforeEmptyLineRemoval = commentRemovalResult;
        commentRemovalResult = commentRemovalResult.replace(/^\r?\n/gm, "");
        if (beforeEmptyLineRemoval !== commentRemovalResult) isEmptyRemoved = true;
      }
      result.textContent = commentRemovalResult;
      if (input.value === commentRemovalResult) {
        resultMessage.textContent = removeEmptyCheckBox.checked ? '削除するコメントや空行はありません。' : '削除するコメントはありません。';
      } else {
        if (isCommentRemoved) {
          resultMessage.textContent = isEmptyRemoved ? 'コメントと空行を削除しました。' : 'コメントを削除しました。';
        } else {
          resultMessage.textContent = '空行を削除しました。';
        }
      }
    });

    // replace() メソッドの第2引数のコールバック関数
    function replaceComments(match, p1, p2) {
      // p1 はコメントの前の文字列 ( .*)、p2 はコメントの後の改行文字列 ($\r?\n?)
      // コメントの後に改行がない場合(p2 は undefined)
      if (!p2) p2 = '';
      // コメントの前が空白文字の場合
      if (!p1.trim()) {
        if (p2) {
          return '';
        }
        return p1;
      } else {
        return p1 + p2;
      }
    }

    // 「空行を削除」をクリックした場合の処理
    removeEmptyLineBtn.addEventListener('click', () => {
      const emptyLineRemovalResult = input.value.replace(/^\r?\n/gm, "");
      result.textContent = emptyLineRemovalResult;
      if (input.value === emptyLineRemovalResult) {
        resultMessage.textContent = '空行はありません。'
      } else {
        resultMessage.textContent = '空行を削除しました。';
      }
    });

    //「削除オプション」をクリックした場合の処理
    const toggleIcons = document.getElementsByClassName('toggle-icon');
    const removalControls = document.getElementById('removal-controls');
    const toggleRemoval = document.getElementById('toggle-removal');
    toggleRemoval.addEventListener('click', (e) => {
      toggleRemoval.classList.toggle('minus');
      removalControls.classList.toggle('opened');
    });
  </script>
</body>
</html>

その他のツール: