HTML のエスケープ、標準の関数の答えが言語ごとに違った ― よく使う書き方を10言語で①

C#
B!

利用者が入力した文字を HTML に埋め込む前に、< や & を &lt; &amp; に置き換える。どの言語でも書く処理です。決まりを「& < > " ' の5つを &amp; &lt; &gt; &quot; &#39; にする」と決めて、各言語の標準の関数で試しました。

この決まりの形とそのまま一致したのは、**標準のエスケープ関数がある5言語のうち、Ruby の CGI.escapeHTML だけ**でした。

何をしたか

CODE Polyglot の「よく使う書き方」 で、正解表8件を先に決め、10言語で書きました。あわせて、仕事でよく見かける書き方(標準の関数をそのまま呼ぶ形など)を7言語で用意し、同じ正解表で動かしています。毎朝の検証と同じ Linux の環境です。

‘ と ” の書き方が、言語ごとに違う

It's "OK"(c003)を各言語の標準の関数に渡した結果です。

書き方出力
決まりIt&#39;s &quot;OK&quot;
Ruby CGI.escapeHTMLIt&#39;s &quot;OK&quot;(一致)
PHP htmlspecialcharsIt&#039;s &quot;OK&quot;
Python html.escapeIt&#x27;s &quot;OK&quot;
Go html.EscapeStringIt&#39;s &#34;OK&#34;

&#39; も &#039; も &#x27; も、ブラウザでは同じ ' として表示されます。ですから、表示が崩れるわけではありません。困るのは、出力を文字列として比べるときです。テストの期待値、キャッシュのキー、2つのシステムの出力の突き合わせで、同じ意味なのに「違う」と判定されます。

PHP は 8.1 から、htmlspecialchars に何も指定しなくても ' を変えるようになりました。古い PHP では ' は変わらなかったので、PHP の版を上げただけで出力が変わります。

C# の WebUtility.HtmlEncode は、5文字は決まりどおりでした。ただし こんにちは 🍣(c006)の絵文字を &#127843; に変えました。

自分で置き換えるなら、& を最初に

JavaScript・Java・Kotlin・Rust・Swift には、標準のエスケープ関数がありません。自分で置き換えることになりますが、ここに2つ落とし穴がありました。

1つ目は順番です。 replace を5回つないで & を最後に変えると、先に作った &lt; の & まで変わり、<b> が &amp;lt;b&amp;gt; になります(Java・Kotlin の例。8件中3件しか合いませんでした)。

**2つ目は JavaScript の replace です。** 文字列を渡すと、最初の1つしか変えません。

s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")  // ほか2つも同じ形

<b>太字</b>(c001)は &lt;b&gt;太字</b> になり、閉じタグが残りました。8件中4件しか合いません。Java の replace や C# の Replace はすべて置き換えるので、同じ名前の関数でも動きが違います。

どう書いたか

1回だけ見て置き換える関数を使うと、順番を気にしなくて済みます。Python ならこうです。

TABLE = str.maketrans({"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"})
print(s.translate(TABLE))

PHP の strtr(配列を渡す形)と Go の strings.NewReplacer も、同じく1回で置き換えます。JavaScript は s.replace(/[&<>"']/g, c => map[c]) の形にしました。10言語のコードは、どれも8件すべてで決まりどおりの答えを出しています。

まとめ

  • 標準のエスケープ関数は、' と " の書き方が言語ごとに違う。ブラウザの表示は同じでも、文字列として比べると違う
  • 置き換えをつなぐなら & を最初に。JavaScript の replace(文字列, …) は最初の1つしか変えない
  • エスケープは「HTML に埋め込む直前に1回だけ」。テンプレートが自動でエスケープするなら、自分では変えない

10言語のコードと、全8件の結果は CODE Polyglot「HTML の特殊文字をエスケープする」 に載せています。毎朝、実際に動かして確かめています。

連載「よく使う書き方を10言語で」(全6回)
  1. 第1回:HTML のエスケープ、標準の関数の答えが言語ごとに違った(この記事)
  2. 第2回:「2026-02-30T09:00:00Z」を、4言語の標準のパーサーが3月2日として読んだ
  3. 第3回:「getHTTPResponseCode」をスネークケースに、よく見かける書き方は10言語中9言語で崩れた
  4. 第4回:アクセスログを空白で区切って集計したら、10言語すべてが壊れた行を数えた
  5. 第5回:重複を消したら順番が変わった、Swift は「が」を1つにまとめた
  6. 第6回:İstanbul の slug が5通りに割れた、日本語のタイトルはそのまま URL に入った
B!
← 一覧へ戻る