Perl で HTML をパースするモジュール2021年12月19日 23時40分

この記事は HTML アドベントカレンダーの 19 日目の分、兼 Perl アドベントカレンダーの 19 日目の分です。


Perl で HTML をパースするモジュールはいくつもあります。

HTML::Parser

そのままの名前ですね。HTML コードをパースしていき、開始タグ、終了タグ、テキストなどを認識するとそれをイベントとして知らせてくれる、プッシュ型のパーサーです。

HTML の要素の内容モデルや、ある要素のタグが省略可能かといった知識は持っていません。あくまでもタグやテキストなどの出現を知らせるだけで、文書木を構築するわけではないからです。

逐次的なパースに対応しています。HTML 文書全体を表すコードを一気に入力として与えなくてもよく、HTTP 通信中に受け取った分からパースしていくといったことが可能です。

Web 製作者の意図を汲み取ろうと努めており、「壊れた」HTML コードでもパースできます。ただし、現在の HTML 標準 (いわゆる HTML5) のパース規則には対応しておらず、現在の主要 Web ブラウザのパース結果と異なる結果になることがあります。

HTML::PullParser

HTML::Parser をプル型のパーサーとして利用できるようにしたものです。HTML::Parser と同じ HTML-Parser ディストリビューションに含まれています (HTML::Parser をインストールしようとすると HTML::PullParser もインストールされます)。

HTML::TokeParser

HTML::PullParser を拡張し、開始タグだけを取り出す、テキストだけを取り出すといったことを簡単にできるようにしたものです。HTML::Parser と同じ HTML-Parser ディストリビューションに含まれています。

HTML::TreeBuilder

HTML をパースし、文書木を構築してくれます。DOM にアクセスするのと似た感覚で HTML 文書を扱えます。内部的なパースには HTML::Parser を使っています。

HTML の要素や内容モデルに関しては HTML 4 相当の知識しかないため、現在の HTML 標準に従った HTML コードを期待通りパースできないことがあります。ignore_unknown オプションを無効にしないと HTML5 で追加された要素が無視される (パース結果の文書木に現れない) 点は、特に注意が必要です。

HTML::TreeBuilder::XML

HTML をパースし、文書木を構築してくれます。内部的なパースには libxml2 ライブラリを使っています。

一部 HTML::TreeBuilder と同名のメソッドを持ちますが、完全に互換性があるわけではありません。

HTML::HTML5::Parser

HTML をパースし、文書木を構築してくれます。XS を使わず Pure-Perl で書かれています。

2013 年で更新が止まっているため、現在の HTML 標準には一部追従していない……と思っていたのですが、2021 年 9 月に更新されていました (この記事を書いている途中で気づきました)。現在の HTML 標準にどこまで適合しているかは未確認です。

HTML5::DOM

HTML をパースし、文書木を構築してくれます。内部的なパースには MyHTML ライブラリを使っています。

MyHTML ライブラリは開発が停止しており、後継の Lexbor ライブラリに引き継がれています。

終わりに

現在の HTML 標準に最も近いのは HTML5::DOM だと思っていたのですが、HTML::HTML5::Parser が更新されていくのなら今後はどうなるかわかりません。

HTML のフォーム送信データを書き換える2021年12月18日 22時28分

この記事は HTML アドベントカレンダーの 18 日目の分、兼 JavaScript アドベントカレンダーの 18 日目の分です。


HTML のフォーム送信処理が実行されるときに、JavaScript を使って送信データを書き換えるにはどうしたらよいでしょうか。まず思いつくのが submit イベント発生時にフォームコントロール要素の入力値 (value プロパティの値など) を書き換えることでしょう。別の手段として formdata イベントがあります。formdata イベントを利用すれば、フォームコントロール要素に手を加えずに送信データを書き換えることができます。

formdata イベントに紐づく FormDataEvent オブジェクトformData プロパティを持ち、その値はまさに今送信されようとしているデータを表した FormData オブジェクトとなっています。この FormData オブジェクトに対して appendsetdelete メソッドを呼び出すことで、送信されるデータを変更することができるのです。

以下の例では、フォームを送信する際に time というキーに対して現在日時を表す文字列が設定され、/search?q=keyword&time=2021-12-18T12%3A00%3A00.000Z といった URL に移動します。

<form action="/search"
      onformdata="event.formData.append('time', new Date().toISOString());">
  <p>
    <input type="search" name="q" aria-label="検索する語">
    <button type="submit">検索する</button>
  </p>
</form>

formdata イベントは、form 要素オブジェクトを引数にして FormData コンストラクタを呼び出したとき (new FormData(form)) にも (引数として渡された form 要素上で) 発生します。

HTML の文書型宣言に含まれる要素名2021年12月17日 22時54分

この記事は HTML アドベントカレンダーの 17 日目の分です。


HTML 文書は文書型宣言 <!DOCTYPE html> から始めることになっています。文書型宣言は、現在では Web ブラウザにその文書を標準準拠モードで処理してもらうための目印としてしか使われていません。しかし、XHTML や HTML 4 以前 (SGML に基づく HTML 仕様) では、その文書が従うべき規則 (どの要素はどんな内容しか持てないとか、どの要素はどの属性しか持てないとか) を参照するものでした。

SGML (および XML) の文書型宣言において、<!DOCTYPE の次に記述される名前はルート要素の名前を表しています。<!DOCTYP html ...> と書かれていれば、SGML パーサーはその文書のルート要素が html 要素であるという前提で文書のパースを進めていくことになります。文書型宣言における要素名は、文脈自由文法における開始記号に相当するものと言えるでしょう。

HTML 2.0 の時代から、htmlheadbody の各要素は開始タグも終了タグも省略できました。<title> タグから記述が始まる HTML 文書があったとして、もし SGML パーサーがルート要素の要素型を知らなかったら、title 要素をルート要素として扱ってしまいかねません。html 要素がルート要素と知っているからこそ、html 要素と head 要素が存在しているがそれらの開始タグが省略されおり、head 要素の子として title 要素が出現しているのだということがはっきりわかるわけです。

参考文献

HTML のフォーム送信に使われたボタンを判別する2021年12月16日 23時00分

この記事は HTML アドベントカレンダーの 16 日目の分、兼 JavaScript アドベントカレンダーの 16 日目の分です。


HTML のフォームには複数の送信ボタンを設置できます。ある送信ボタンを実行してフォームを送信すると、その送信ボタンの name 属性と (送信ボタンが button 要素なら) value 属性の値も送信データに含まれます。

どの送信ボタンを実行してフォームを送信したのか、JavaScript からは submit イベントに紐づく SubmitEvent オブジェクトsubmitter プロパティで判別できます。以下の例では、「公開する」または「削除する」ボタンを実行すると、JavaScript コンソールにそのボタンが出力されます。

<form
    method="post"
    action="/edit"
    onsubmit="console.log(event.submitter); return false;">
  <p>
    <button type="submit" name="publish" value="1">公開する</button>
    <button type="submit" name="delete" value="1">削除する</button>
  </p>
</form>

submitter プロパティが実装される前は、各ボタンの click イベントを基に、どのボタンが最後に実行されたのか記憶しておく必要がありました。そこからするとずいぶん楽になりましたね。

HTML の iframe 要素の srcdoc 属性で埋め込まれる文書の URL2021年12月15日 23時21分

この記事は HTML アドベントカレンダーの 15 日目の分です。


iframe 要素 のsrcdoc 属性の値に HTML 文書のソースコードを記述すると、その HTML 文書を埋め込んだフレームが生成されます。ここで、埋め込まれた HTML 文書の URL はどうなっているでしょうか。

<!-- iframe 要素を含む文書の URL は https://www.example.com/ であるとする。 -->
<iframe srcdoc="
  <a href=&quot;foo?bar=42&amp;amp;baz=23&quot;>Link</a>
  <script>
    console.log(location.href);
    // => "about:srcdoc"

    console.log(document.querySelector('a').href);
    // => "https://www.example.com/foo?bar=42&baz=23"
  </script>
"></iframe>

埋め込まれた文書の URL は about:srcdoc ですが、その文書内のリンクにおいては「iframe 要素を含む文書の URL」を基底 URL として相対 URL が解決されます。

base 要素を使えば文書の URL と異なる URL を基底 URL として使えますが、srcdoc 属性を使って埋め込まれた文書では base 要素がなくてもそのような状態になっているのですね。<iframe src="about:blank"> で埋め込まれた文書も同様の状態になります。

ちなみに、srcdoc 属性で埋め込まれた HTML 文書では文書型宣言 (<!DOCTYPE html>) が省略可能であり、省略した場合も標準準拠モードで描画されます。また、title 要素も省略可能となります。htmlheadbody の各要素の開始タグおよび終了タグはもともと省略可能なため、body 要素の内容が「body 要素内にしか出現しえない要素」から始まるのなら、その要素の開始タグから記述を開始できます。