XMLには特別な意味を持つ文字があります。これらをそのまま書くとXMLパーサーが誤解してエラーになるため、エスケープ(別の表現に置き換え)が必要です。
APIのリクエストボディにコードスニペットやHTMLを含める場合、XMLエスケープを忘れると400エラーになります。
エスケープが必要な文字
| 文字 | エスケープ後 | 理由 |
|---|---|---|
< |
< |
タグの開始と誤解される |
> |
> |
タグの終了と誤解される |
& |
& |
エンティティの開始と誤解される |
" |
" |
属性値の区切りと誤解される |
' |
' |
属性値の区切りと誤解される |
エスケープが必要な例
<!-- NG: <-ch がタグの開始と解釈されてパースエラー --> <content>result := <-ch</content> <!-- OK --> <content>result := <-ch</content>
<!-- NG: & がエンティティの開始と解釈される --> <title>A & B</title> <!-- OK --> <title>A & B</title>
Python でのエスケープ
xml.sax.saxutils.escape()
標準ライブラリの xml.sax.saxutils にある escape() が <・>・& を変換します。
from xml.sax.saxutils import escape text = 'result := <-ch // チャネルから受け取る' print(escape(text)) # result := <-ch // チャネルから受け取る
" と ' も変換したい場合は entities 引数を使います。
from xml.sax.saxutils import escape text = 'He said "Hello & Goodbye"' escaped = escape(text, entities={'"': '"', "'": '''}) print(escaped) # He said "Hello & Goodbye"
逆変換(アンエスケープ)
unescape() でエスケープを元に戻せます。
from xml.sax.saxutils import unescape text = '<div>Hello & World</div>' print(unescape(text)) # <div>Hello & World</div>
xml.etree.ElementTree を使う場合
ET.SubElement や ET.tostring を使うと自動でエスケープしてくれるため、手動エスケープ不要です。
from xml.etree import ElementTree as ET root = ET.Element("entry") title = ET.SubElement(root, "title") title.text = "A & B <sample>" # 自動でエスケープされる print(ET.tostring(root, encoding="unicode")) # <entry><title>A & B <sample></title></entry>
JavaScript でのエスケープ
JavaScriptには標準のXMLエスケープ関数がないため、自作するか DOMParser を使います。
自作関数
function escapeXml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
console.log(escapeXml('result := <-ch'));
// result := <-ch
& を最初に変換しないと、後のエスケープ結果(< など)がさらに変換されてしまうため順番が重要です。
TextNode を使う方法(ブラウザ環境)
function escapeXml(str) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
Ruby でのエスケープ
require 'cgi' CGI.escapeHTML('<div>Hello & "World"</div>') # => "<div>Hello & "World"</div>"
Go でのエスケープ
html パッケージの EscapeString はXMLにも使えます。
import "html" s := html.EscapeString("<-ch // チャネル受信") fmt.Println(s) // <-ch // チャネル受信
encoding/xml パッケージでXMLを構造体から生成する場合は自動エスケープされます。
type Entry struct { Title string `xml:"title"` } entry := Entry{Title: "A & B <sample>"} out, _ := xml.Marshal(entry) fmt.Println(string(out)) // <Entry><title>A & B <sample></title></Entry>
よくあるエラーと原因
| エラー | 原因 |
|---|---|
400 XML Parse Failed |
<・& などが未エスケープのままXMLに含まれている |
XML syntax error |
属性値の " がエスケープされていない |
entity not defined |
& の後に認識できないエンティティ名が続いている |
まとめ
# Python(標準ライブラリ) from xml.sax.saxutils import escape escaped = escape(text)
// JavaScript const escaped = str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
# Ruby require 'cgi' escaped = CGI.escapeHTML(text)
// Go import "html" escaped := html.EscapeString(text)
&は必ず最初に変換する(順番を間違えると二重エスケープになる)- XMLライブラリ(Python の
ElementTree、Go のencoding/xml)を使えば自動エスケープされるため手動変換不要 - AtomPub API や RSS フィードなど、XMLをPOSTするときは必ずエスケープを確認する