Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10536] ファイルの概要

このコミットは、Go言語の標準ライブラリ html パッケージにおけるリファクタリングです。具体的には、HTMLドキュメントのDOCTYPE(Document Type Declaration)の解析に関連するロジックを、既存の parse.go ファイルから doctype.go という新しいファイルに分離しています。これにより、コードのモジュール性が向上し、保守性が高まります。

コミット

929290d5a0e276bf6fde0c6d3c41b9611231fbb5

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/929290d5a0e276bf6fde0c6d3c41b9611231fbb5

元コミット内容

html: spin doctype.go out of parse.go.

R=andybalholm
CC=golang-dev
https://golang.org/cl/5445049

変更の背景

この変更の主な背景は、コードの整理とモジュール化です。src/pkg/html/parse.go はHTMLのパース処理全般を扱うファイルであり、その中にDOCTYPEの解析ロジックが含まれていました。DOCTYPEの解析はHTMLパースの中でも特定の独立した機能であるため、これを専用の doctype.go ファイルに分離することで、以下の利点が得られます。

  • コードの可読性向上: 各ファイルが特定の責務を持つようになり、コードが読みやすくなります。
  • 保守性の向上: DOCTYPE関連のバグ修正や機能追加が必要になった際に、関連コードが1箇所にまとまっているため、変更が容易になります。
  • テストのしやすさ: 特定の機能が独立したファイルに分離されることで、その機能に対する単体テストが書きやすくなります。
  • 依存関係の明確化: parse.go から doctype.go への依存関係が明確になります。

前提知識の解説

HTMLのDOCTYPE(Document Type Declaration)

DOCTYPEは、HTMLドキュメントの冒頭に記述される宣言であり、そのドキュメントがどのHTMLまたはXHTMLのバージョン、あるいはDTD(Document Type Definition)に準拠しているかを示すものです。ブラウザはDOCTYPEを読み取り、それに基づいてドキュメントをどのようにレンダリングするかを決定します。

例:

  • HTML5: <!DOCTYPE html>
  • HTML 4.01 Strict: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

Quirks Mode(互換モード)

Quirks Mode(クォークスモード)は、ウェブブラウザが古い、または標準に準拠していないウェブページをレンダリングする際に使用する互換モードです。多くの古いウェブページは、W3Cなどの標準仕様に厳密に従って記述されていませんでした。ブラウザがこれらのページを標準モードでレンダリングしようとすると、レイアウトが崩れたり、JavaScriptが正しく動作しなかったりする可能性があります。

Quirks Modeでは、ブラウザは過去のブラウザ(特にInternet Explorer 5など)の非標準的な動作を模倣してページをレンダリングします。これにより、古いウェブページが意図した通りに表示されるようになります。

ブラウザがQuirks Modeに入るかどうかは、主にDOCTYPE宣言の有無やその内容によって決まります。

  • DOCTYPEが完全に存在しない場合。
  • DOCTYPEが不正な形式である場合。
  • 特定の古いDOCTYPE宣言が使用されている場合(例: HTML 4.0 Transitionalの特定のバージョンなど)。

Go言語の html パッケージ

Go言語の golang.org/x/net/html パッケージ(以前は src/pkg/html)は、HTML5の仕様に準拠したHTMLパーサーを提供します。このパッケージは、HTMLドキュメントをトークン化し、DOMツリーを構築するための機能を提供します。ウェブスクレイピング、HTMLの変換、HTMLテンプレートエンジンの実装など、様々な用途で利用されます。

このパッケージは、HTMLの構文解析において、ブラウザがどのようにHTMLを解釈するかを模倣するように設計されています。これには、エラー回復メカニズムや、Quirks Modeの判定ロジックも含まれます。

技術的詳細

このコミットでは、DOCTYPEの解析とQuirks Modeの判定ロジックが parse.go から doctype.go へと移動されました。

新しい doctype.go ファイルには、以下の主要な要素が含まれています。

  1. parseDoctype 関数:

    • この関数は、DOCTYPEトークンからドキュメント名、公開識別子(Public Identifier)、システム識別子(System Identifier)を解析します。
    • 戻り値として *Node(HTMLノードを表す構造体)と quirks(ブール値でQuirks Modeに入るべきかを示す)を返します。
    • NodeTypeDoctypeNode となり、Data にはDOCTYPE名(例: "html")が格納されます。
    • 公開識別子とシステム識別子は、NodeAttr フィールドに Attribute{Key: key, Val: id} の形式で追加されます。
    • Quirks Modeの判定ロジックがこの関数内に実装されています。これは、DOCTYPE名が "html" でない場合や、特定の公開識別子やシステム識別子が存在する場合に quirkstrue に設定します。
  2. quirkyIDs 変数:

    • これは、Quirks Modeを引き起こす既知の公開DOCTYPE識別子のリストです。
    • これらの識別子は、歴史的に古いブラウザのQuirks Modeをトリガーするために使用されてきたものです。
    • parseDoctype 関数内で、解析された公開識別子がこのリストに含まれているかどうかがチェックされ、Quirks Modeの判定に利用されます。

parseDoctype 関数は、DOCTYPE文字列を解析し、その構造(名前、PUBLIC/SYSTEMキーワード、引用符で囲まれた識別子)を抽出し、HTML5の仕様で定義されているQuirks Modeのトリガー条件に基づいて quirks フラグを設定します。例えば、特定の古いDTDの公開識別子(quirkyIDs にリストされているもの)や、HTML5のDOCTYPEではない場合などにQuirks Modeが有効になります。

コアとなるコードの変更箇所

このコミットによる主要な変更は以下の3つのファイルにわたります。

  1. src/pkg/html/Makefile:

    • GOFILES 変数に新しく doctype.go が追加されました。これにより、ビルドシステムが新しいファイルを認識し、コンパイル対象に含めるようになります。
    --- a/src/pkg/html/Makefile
    +++ b/src/pkg/html/Makefile
    @@ -8,6 +8,7 @@ TARG=html
     GOFILES=\
      const.go\
      doc.go\
    +\tdoctype.go\
      entity.go\
      escape.go\
      node.go\
    
  2. src/pkg/html/doctype.go:

    • このファイルが新規作成され、parse.go から移動された parseDoctype 関数と quirkyIDs 変数が格納されました。
    • ファイル冒頭にはGoの標準的な著作権表示とパッケージ宣言が含まれています。
    --- /dev/null
    +++ b/src/pkg/html/doctype.go
    @@ -0,0 +1,156 @@
    +// Copyright 2011 The Go Authors. All rights reserved.
    +// Use of this source code is governed by a BSD-style
    +// license that can be found in the LICENSE file.
    +
    +package html
    +
    +import (
    +	"strings"
    +)
    +
    +// parseDoctype parses the data from a DoctypeToken into a name,
    +// public identifier, and system identifier. It returns a Node whose Type
    +// is DoctypeNode, whose Data is the name, and which has attributes
    +// named "system" and "public" for the two identifiers if they were present.
    +// quirks is whether the document should be parsed in "quirks mode".
    +func parseDoctype(s string) (n *Node, quirks bool) {
    +	// ... (function implementation) ...
    +}
    +
    +// quirkyIDs is a list of public doctype identifiers that cause a document
    +// to be interpreted in quirks mode. The identifiers should be in lower case.
    +var quirkyIDs = []string{
    +	// ... (list of IDs) ...
    +}
    
  3. src/pkg/html/parse.go:

    • parseDoctype 関数と quirkyIDs 変数の定義がこのファイルから削除されました。
    • これにより、parse.go はDOCTYPE解析の具体的なロジックから解放され、より高レベルのパース処理に集中できるようになります。
    --- a/src/pkg/html/parse.go
    +++ b/src/pkg/html/parse.go
    @@ -323,153 +323,6 @@ func (p *parser) resetInsertionMode() {
     
     const whitespace = " \t\r\n\f"
     
    -// quirkyIDs is a list of public doctype identifiers that cause a document
    -// to be interpreted in quirks mode. The identifiers should be in lower case.
    -var quirkyIDs = []string{
    -// ... (list of IDs removed) ...
    -}
    -
    -// parseDoctype parses the data from a DoctypeToken into a name,
    -// public identifier, and system identifier. It returns a Node whose Type
    -// is DoctypeNode, whose Data is the name, and which has attributes
    -// named "system" and "public" for the two identifiers if they were present.
    -// quirks is whether the document should be parsed in "quirks mode".
    -func parseDoctype(s string) (n *Node, quirks bool) {
    -// ... (function implementation removed) ...
    -}
    -
     // Section 11.2.5.4.1.
     func initialIM(p *parser) bool {
      switch p.tok.Type {
    

コアとなるコードの解説

src/pkg/html/doctype.go

この新しいファイルは、HTMLパーサーにおけるDOCTYPE処理の心臓部となります。

parseDoctype 関数

func parseDoctype(s string) (n *Node, quirks bool) {
	n = &Node{Type: DoctypeNode}

	// Find the name.
	space := strings.IndexAny(s, whitespace)
	if space == -1 {
		space = len(s)
	}
	n.Data = s[:space] // DOCTYPE名の抽出
	// The comparison to "html" is case-sensitive.
	if n.Data != "html" { // "html" 以外のDOCTYPE名はQuirks Modeのトリガー
		quirks = true
	}
	n.Data = strings.ToLower(n.Data) // DOCTYPE名を小文字に変換
	s = strings.TrimLeft(s[space:], whitespace) // 残りの文字列から空白を除去

	if len(s) < 6 {
		// It can't start with "PUBLIC" or "SYSTEM".
		// Ignore the rest of the string.
		return n, quirks || s != "" // "PUBLIC"や"SYSTEM"がない場合はQuirks Mode
	}

	key := strings.ToLower(s[:6]) // "PUBLIC"または"SYSTEM"の検出
	s = s[6:]
	for key == "public" || key == "system" {
		s = strings.TrimLeft(s, whitespace)
		if s == "" {
			break
		}
		quote := s[0] // 引用符の検出
		if quote != '\'' && quote != '"' {
			break
		}
		s = s[1:]
		q := strings.IndexRune(s, rune(quote)) // 引用符の終わりを検索
		var id string
		if q == -1 {
			id = s
			s = ""
		} else {
			id = s[:q]
			s = s[q+1:]
		}
		n.Attr = append(n.Attr, Attribute{Key: key, Val: id}) // 属性として追加
		if key == "public" {
			key = "system" // PUBLICの次はSYSTEMを期待
		} else {
			key = "" // SYSTEMの次は終了
		}
	}

	if key != "" || s != "" {
		quirks = true // 未解析のキーや残りの文字列があればQuirks Mode
	} else if len(n.Attr) > 0 {
		if n.Attr[0].Key == "public" {
			public := strings.ToLower(n.Attr[0].Val)
			switch public {
			case "-//w3o//dtd w3 html strict 3.0//en//", "-/w3d/dtd html 4.0 transitional/en", "html":
				quirks = true // 特定のPUBLIC IDはQuirks Mode
			default:
				for _, q := range quirkyIDs {
					if strings.HasPrefix(public, q) {
						quirks = true // quirkyIDsに含まれるPUBLIC IDはQuirks Mode
						break
					}
				}
			}
			// The following two public IDs only cause quirks mode if there is no system ID.
			if len(n.Attr) == 1 && (strings.HasPrefix(public, "-//w3c//dtd html 4.01 frameset//") ||
				strings.HasPrefix(public, "-//w3c//dtd html 4.01 transitional//")) {
				quirks = true // 特定のPUBLIC IDはSYSTEM IDがない場合にQuirks Mode
			}
		}
		if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" &&
			strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" {
			quirks = true // 特定のSYSTEM IDはQuirks Mode
		}
	}

	return n, quirks
}

この関数は、HTML5のDOCTYPE解析アルゴリズムに厳密に従っています。主な処理は以下の通りです。

  1. DOCTYPE名の抽出: DOCTYPE宣言の最初の部分(例: <!DOCTYPE htmlhtml)を抽出します。これが n.Data になります。
  2. Quirks Modeの初期判定: 抽出されたDOCTYPE名が "html" でない場合、直ちに quirks = true とします。
  3. PUBLIC/SYSTEM識別子の解析: DOCTYPE名に続く "PUBLIC" や "SYSTEM" キーワード、そしてそれに続く引用符で囲まれた識別子(Public IdentifierやSystem Identifier)を解析し、NodeAttr フィールドに追加します。
  4. 詳細なQuirks Mode判定:
    • 解析が不完全な場合(keys に残りの文字列がある場合)は quirks = true
    • 解析された属性が存在する場合、特に最初の属性が "public" であれば、その値(Public Identifier)をチェックします。
      • 特定の既知のPublic Identifier(例: "-//w3o//dtd w3 html strict 3.0//en//")はQuirks Modeをトリガーします。
      • quirkyIDs リストに含まれるPublic IdentifierもQuirks Modeをトリガーします。
      • 特定のPublic Identifier(例: "-//w3c//dtd html 4.01 frameset//")は、System Identifierが存在しない場合にQuirks Modeをトリガーします。
    • 最後の属性が "system" であり、特定のSystem Identifier(例: "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd")である場合もQuirks Modeをトリガーします。

quirkyIDs 変数

var quirkyIDs = []string{
	"//+silmaril//dtd html pro v0r11 19970101//",
	"//-advasoft ltd//dtd html 3.0 aswedit + extensions//",
	// ... (多数の古いDOCTYPE識別子が続く) ...
	"//-webtechs//dtd mozilla html 2.0//",
	"//-webtechs//dtd mozilla html//",
}

このグローバル変数は、ブラウザがQuirks Modeでレンダリングすべきと判断する、歴史的に使用されてきたPublic Identifierのリストです。これらの識別子は、HTMLの標準化が進む以前に広く使われていたもので、現代のブラウザはこれらを見つけると互換性のためにQuirks Modeに切り替えます。このリストは、HTML5の仕様におけるQuirks Modeのトリガー条件の一部を実装しています。

関連リンク

参考にした情報源リンク