利用者が入力した文字を HTML に埋め込む前に、< や & を < & に置き換える。どの言語でも書く処理です。決まりを「& < > " ' の5つを & < > " ' にする」と決めて、各言語の標準の関数で試しました。
この決まりの形とそのまま一致したのは、**標準のエスケープ関数がある5言語のうち、Ruby の CGI.escapeHTML だけ**でした。
何をしたか
CODE Polyglot の「よく使う書き方」 で、正解表8件を先に決め、10言語で書きました。あわせて、仕事でよく見かける書き方(標準の関数をそのまま呼ぶ形など)を7言語で用意し、同じ正解表で動かしています。毎朝の検証と同じ Linux の環境です。
‘ と ” の書き方が、言語ごとに違う
It's "OK"(c003)を各言語の標準の関数に渡した結果です。
| 書き方 | 出力 |
|---|---|
| 決まり | It's "OK" |
Ruby CGI.escapeHTML | It's "OK"(一致) |
PHP htmlspecialchars | It's "OK" |
Python html.escape | It's "OK" |
Go html.EscapeString | It's "OK" |
' も ' も ' も、ブラウザでは同じ ' として表示されます。ですから、表示が崩れるわけではありません。困るのは、出力を文字列として比べるときです。テストの期待値、キャッシュのキー、2つのシステムの出力の突き合わせで、同じ意味なのに「違う」と判定されます。
PHP は 8.1 から、htmlspecialchars に何も指定しなくても ' を変えるようになりました。古い PHP では ' は変わらなかったので、PHP の版を上げただけで出力が変わります。
C# の WebUtility.HtmlEncode は、5文字は決まりどおりでした。ただし こんにちは 🍣(c006)の絵文字を 🍣 に変えました。
自分で置き換えるなら、& を最初に
JavaScript・Java・Kotlin・Rust・Swift には、標準のエスケープ関数がありません。自分で置き換えることになりますが、ここに2つ落とし穴がありました。
1つ目は順番です。 replace を5回つないで & を最後に変えると、先に作った < の & まで変わり、<b> が &lt;b&gt; になります(Java・Kotlin の例。8件中3件しか合いませんでした)。
**2つ目は JavaScript の replace です。** 文字列を渡すと、最初の1つしか変えません。
s.replace("&", "&").replace("<", "<").replace(">", ">") // ほか2つも同じ形
<b>太字</b>(c001)は <b>太字</b> になり、閉じタグが残りました。8件中4件しか合いません。Java の replace や C# の Replace はすべて置き換えるので、同じ名前の関数でも動きが違います。
どう書いたか
1回だけ見て置き換える関数を使うと、順番を気にしなくて済みます。Python ならこうです。
TABLE = str.maketrans({"&": "&", "<": "<", ">": ">", '"': """, "'": "'"})
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 の特殊文字をエスケープする」 に載せています。毎朝、実際に動かして確かめています。
- 第1回:HTML のエスケープ、標準の関数の答えが言語ごとに違った(この記事)
- 第2回:「2026-02-30T09:00:00Z」を、4言語の標準のパーサーが3月2日として読んだ
- 第3回:「getHTTPResponseCode」をスネークケースに、よく見かける書き方は10言語中9言語で崩れた
- 第4回:アクセスログを空白で区切って集計したら、10言語すべてが壊れた行を数えた
- 第5回:重複を消したら順番が変わった、Swift は「が」を1つにまとめた
- 第6回:İstanbul の slug が5通りに割れた、日本語のタイトルはそのまま URL に入った
Analyzegear


