HTML の img 要素と image タグ2021年12月20日 23時40分

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


HTML のタグといえば、要素の開始位置、終了位置、そして属性を指定する記述のことですね。開始タグは小なり記号 (<) の後に要素名が続きます。しかしながら、タグに既述した名前とは別の名前の要素が生成される場合があります。

以下の JavaScript コードを実行すると、JavaScript コンソールには (IMAGE ではなく) IMG と出力されます。タグに記述された名前は image なのに、img 要素が生成されているのです。

const div = document.createElement('div');
div.innerHTML = '<image src="" alt="">';
console.log(div.firstChild.tagName); // => "IMG"

実はこれは HTML のパース処理における例外的な扱いで、開始タグの名前が image のとき、内部的にはパースエラーとしつつ名前を img に読み替えることになっています。Web 製作者の記述ミスを救済しようとする慈悲の心の現れですね。

あくまでも HTML のパース処理における例外なので、JavaScript で document.createElement メソッドを使って要素を生成するときには適用されません。以下の例では "[object HTMLUnknownElement]" という文字列が出力されるはずです。(しかし Firefox では "[object HTMLElement]" という文字列が出力されます。)

const image = document.createElement('image');
console.log(image.toString());

参考文献

Perl で関数内に関数を定義する2021年12月20日 21時08分

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


Perl コードを書いていて、関数内で関数を定義したいと思ったことはありませんか? 普通に sub foo { ... } の中に sub bar { ... } を書けばよいのでは思われるかもしれませんが、それだと関数が局所的にならず外部に露出してしまいます。

package MyPackage {
    sub foo {
        sub bar { 42 }
        return bar();
    }
}
# foo 関数の外部からも bar 関数を呼び出せる。
say MyPackage::bar(); # => 42

無名関数のコードリファレンスを使うこともできますが、見た目がちょっと煩雑ですね。

package MyPackage {
    sub foo {
        my $bar = sub { 42 };
        return $bar->();
    }
}

そこで lexical subroutines の出番です。my sub foo { ... } または state sub foo { ... } と書くことで、局所的な関数を定義できます。Perl 5.18 で試験的に導入された機能で、Perl 5.26 から本採用となり警告なしで使えるようになりました。

package MyPackage {
    sub foo {
        my sub bar { 42 }
        return bar();
    }
}
# foo 関数の外部からは bar 関数を呼び出せない。
say MyPackage::bar(); # => Undefined subroutine &MyPackage::bar called

lexical subroutinessort 関数に渡すこともできます。

my sub compare { $b <=> $a }
my $array_1 = [ sort compare 1, 8, 2, 3, 5 ];
my $array_2 = [ sort compare 4, 7, 6, 0, 9 ];
say @$array_1; # => 85321
say @$array_2; # => 97640

注意点として、Perl の lint である Perl-Critic には、バージョン 1.140 時点で lexical subroutines に誤った警告を出してしまうという問題があります。

筆者はいずれの問題に対しても修正 pull request を提出しています。Perl-Critic の誤検知がなくなり、気兼ねなく lexical subroutines が使えるようになるとよいですね。

ECMAScript 仕様の連鎖生成規則2021年12月19日 23時58分

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


JavaScript の言語仕様は ECMAScript として策定されています。ECMAScript の仕様書を読んでいくにあたって注意すべき点として、連鎖生成規則 (chain production) に対する挙動の記述が省略されているかもしれないということが挙げられます。筆者は当初このことに気づかず、「この生成規則に対する Runtime Semantics が定義されていないけど、どうなっているんだろう?」と悩みました。

連鎖生成規則とは、finally 節に対する生成規則

Finally :
finally Block

のように、生成規則の右辺に非終端記号がひとつだけ存在するものです。終端記号はいくつあっても (ひとつもなくても) 構いません。この場合、Finally 生成規則に対する Runtime Semantics は、その右辺に存在する非終端記号 Block に対する Runtime Semantics がそのまま呼び出されるというものになります。

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 要素上で) 発生します。