HTML のフォームでアップロードしてほしいファイル種別を指定する2021年12月21日 23時14分

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


HTML のフォームでファイルを送信するには、ファイルアップロードコントロール <input type="file"> を使います。このとき、accept 属性に MIME タイプまたは拡張子を指定することで、アップロードしてほしいファイルの種別を指定できます。PNG 画像または JPEG 画像をアップロードしてほしければ、以下のようにカンマ区切りで記述します。

<input type="file" accept="image/png, image/jpeg, .png, .jpg, .jpeg">

特殊な値として以下の三つが定義されています。これらの値も他の値と組み合わせて指定できます。

image/*
何らかの画像ファイルをアップロードしてほしいときに指定する。
video/*
何らかの動画ファイルをアップロードしてほしいときに指定する。
audio/*
何らかの音声ファイルをアップロードしてほしいときに指定する。

個別のファイル種別を指定するときは、MIME タイプと拡張子を両方組み合わせて指定することが勧められています。MIME タイプまたは拡張子、どちらかのみでファイル種別を管理しているシステムへの配慮からでしょうか。

MIME タイプにパラメータは指定できません。UTF-8 で符号化された HTML 文書のみアップロードしてほしいからといって、accept="text/html; charset=UTF-8" とは書けないのです。

accept 属性で指定できるのはあくまでもファイル名などに基づく表面的なファイル種別であり、Web ブラウザがファイルフォーマットをきちんと検証してくれるわけではありません。サーバー側でのファイルフォーマット検証を忘れないようにしましょう。

<input type="file"> にはほかにも、複数ファイル選択を可能にする multiple 属性や、デバイスのメディアキャプチャ機能を直接利用可能にする capture 属性を指定できます。capture 属性に関しては「captureでカメラ起動(知名度が低いウェブ標準ひとりAdvent Calendar 2021 – 17日目) | Ginpen.com」で紹介されているのでそちらも参照してください。なお、capture 属性はまだ HTML 標準に取り込まれていませんが、HTML Media Capture として W3C 勧告になっています。

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 が更新されていくのなら今後はどうなるかわかりません。