HTML の属性値とテキストのパース結果の違い2021年12月14日 23時24分

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


HTML の属性値のパース規則は、(一般的な要素の内容として出現する) テキストのパース規則とは少し異なります。

わかりやすいところでは、属性値においては小なり記号 (<) をそのまま書けます。以下の HTML コードにおいて、span 要素の title 属性の値も内容のテキストも結果的には " < " という値になりますが、テキストに関しては内部的にパースエラー (invalid-first-character-of-tag-name; タグの名前の 1 文字目が無効) が発生しています。

<span title=" < "> < </span>

文字参照の扱いにも差異があります。以下のように amp という文字参照の名前の後に ASCII の英数字が続く場合、属性値においては "&ampm" という値に解釈されます。一方、テキストにおいては &amp が不完全な文字参照とみなされ、内部的にパースエラー (missing-semicolon-after-character-reference; 文字参照の後にセミコロンがない) が発生しつつ "&m" という値に解釈されます。

<span title="&ampm">&ampm</span>

この解釈の違いは、文字参照の名前の後に等号 (=) が来る場合も同様です。以下の例で、属性値は "&amp=" という値と解釈され、内容のテキストは内部的にパースエラーが発生しつつ "&=" という値に解釈されます。

<span title="&amp=">&amp=</span>

文字参照の名前の後に ASCII の英数字でも等号でもない文字が来る場合は、属性値でもテキストでも内部的にパースエラーが発生しつつ文字参照として扱われます。以下の例では共に "&*" という値に解釈されます。

<span title="&amp*">&amp*</span>

このような属性値におけるアンパサンドの特殊な扱いにより、クエリパラメータ ampampm を含むような URL https://www.example.com/foo?bar=42&amp=23&ampm=12a 要素の href 属性の値にそのまま (& を文字参照にせずに) 書いたとしても、リンク先 URL は (https://www.example.com/foo?bar&=23&m=12 ではなく) https://www.example.com/foo?bar=42&amp=23&ampm=12 になります。

<a href="https://www.example.com/foo?bar=42&amp=23&ampm=12">...</a>

しかしながら、このような特殊な扱いに依存した記述は意図しない挙動をもたらすことがあるため (属性値の URL をテキスト部分にコピペしたら値が変わってしまうなど)、属性値だろうがテキストだろうが & を記述したいときは文字参照 &amp; を使うことをお勧めします。

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 要素内にしか出現しえない要素」から始まるのなら、その要素の開始タグから記述を開始できます。

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 の文書型宣言に含まれる要素名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月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 要素上で) 発生します。