読者です 読者をやめる 読者になる 読者になる

html5libで特定のHTML要素・属性を除去する

Python
  • 出力値をHTMLエスケープする
    • ただし、aタグの使用は許可する
    • aタグであっても、href属性以外の属性は除去する
    • href属性であってもhttp, https以外のプロトコルは除去する

という処理を行う必要があり自前でコードを書いてみたのだが、予想通り大変だった。有効なテストケースが思い浮かばないので不安一杯。

やはり定評のあるライブラリを使った方がいいだろうか。

BeautifulSoupでもlxmlでも上記の要件を満たす処理は書けるが、この場合、html5libのHTMLSanitilizerを使うとよさげ。

HTML5をうたっているが、ブログ記事に一部のタグ/CSSを許可するといった用途ならば、特にHTML5ということを気にせずに使えると思う。

基本的な使い方はドキュメントにある通り。非常に洗練されたAPIなのですぐに基本的な使い方は分かる。(が、もう少し詳しいドキュメントが欲しい。)

lxmlを使ってツリーオブジェクトを構築する例。上述の要件を満たすようにHTMLSanitilizerクラスを継承したクラスをtokenzierに指定する。

from html5lib import sanitizer, treebuilders, HTMLParser
from lxml import etree

class MySanitizer(sanitizer.HTMLSanitizer):
    allowed_elements   = ["a"]
    allowed_attributes = ["href"]
    allowed_protocols  = ["http", "https"]

tree = treebuilders.getTreeBuilder("lxml")
parser = HTMLParser(tokenizer=MySanitizer, tree=tree)
doc = parser.parse("""<a href="/" class="spam"><span>spam</span></a>
<a href="ftp:///example.com/">FTP</a>
<a href="mailto:test@example.com">mailto</a>
""")

print etree.tostring(doc)

出力は次のようになる。

<html><head/><body><a href="/">&lt;span&gt;spam&lt;/span&gt;</a>
<a>FTP</a>
<a>mailto</a>
</body></html>

body以下の要素を刈り取れば目的は達成できるが、属性なしのaタグは消去したい。html5lib.filters.sanitizerかhtml5lib.serializerあたりを使えばできそうな匂いがするので、もう少し調べてみる。