[インデックス 10394] ファイルの概要
コミット
コミットハッシュ: 3bd5082f579d3a45cfa3969d799bef2539c988f0
作成者: Andrew Balholm andybalholm@gmail.com
作成日: 2011年11月15日(火)11:39:18 +1100
コミットメッセージ: html: parse and render <plaintext> elements
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3bd5082f579d3a45cfa3969d799bef2539c988f0
元コミット内容
このコミットは、GoのHTMLパッケージに<plaintext>要素のパースとレンダリング機能を追加したものです。コミットメッセージには、このコミットがtests2.dat
のテスト10をパスするようになったことと、テスト25まで通過することが記載されています。
具体的には、以下のテストケースが通過するようになりました:
\<table\>\<plaintext\>\<td\>
のパースが正しく行われ、DOM構造が適切に構築される\<!doctypehtml\>\<p\>\<dd\>
のような複合テストケースも通過
変更の背景
このコミットは2011年のGoの初期開発時期に作成されたものです。当時GoのHTMLパッケージはHTML5仕様に対応した完全なHTMLパーサーとして開発されており、Web標準に準拠したHTMLの解析とレンダリングを目標としていました。
<plaintext>要素は、HTML仕様の中でも特殊な扱いを受ける要素の一つで、HTML5では廃止予定(obsolete)とされていますが、既存のWebコンテンツとの互換性を保つために実装する必要がありました。
このコミットの目的は以下の通りです:
- HTML5仕様に準拠したHTMLパーサーとしての完全性を高める
- 既存のWebコンテンツとの互換性を確保する
- 公式のHTML5テストスイートに対応する
前提知識の解説
<plaintext>要素の特殊性
<plaintext>要素は、HTML仕様の中でも極めて特殊な動作をする要素です:
-
終了タグなし: <plaintext>要素は終了タグを持ちません。開始タグの後は全てプレーンテキストとして扱われます。
-
パーサーの停止: <plaintext>要素に遭遇すると、HTMLパーサーは後続の内容を全てテキストとして扱い、HTMLタグとして解析しません。
-
廃止予定: HTML5では<plaintext>要素は廃止予定(obsolete)とされており、代わりに<pre>要素や<code>要素の使用が推奨されています。
-
歴史的経緯: <plaintext>要素は初期のHTMLで、文書の残りの部分を生テキストとして表示するために使用されていました。
HTMLパーサーの動作原理
HTMLパーサーは以下の段階で動作します:
- トークナイザー(Tokenizer): HTML文字列を意味のあるトークンに分割
- パーサー(Parser): トークンからDOMツリーを構築
- レンダラー(Renderer): DOMツリーをHTML文字列に変換
<plaintext>要素は、これらの各段階で特殊な処理が必要となります。
Go言語のHTMLパッケージ
2011年当時のGoのHTMLパッケージは、以下の特徴を持っていました:
- HTML5仕様に準拠した実装
- トークナイザーとパーサーの分離設計
- 低レベルAPIと高レベルAPIの両方を提供
- 厳密な仕様準拠を目指した実装
技術的詳細
実装された機能
このコミットでは、以下の4つのファイルが変更されました:
- parse.go: <plaintext>要素のパース処理を追加
- parse_test.go: テストケースの更新とブラックリストの追加
- render.go: <plaintext>要素のレンダリング処理を追加
- token.go: トークナイザーに<plaintext>要素の特殊処理を追加
パーサーの状態管理
HTMLパーサーは状態機械として実装されており、<plaintext>要素に遭遇すると特殊な状態に遷移します:
- 通常状態: HTMLタグを解析してDOMツリーを構築
- PLAINTEXT状態: 後続の全ての内容をテキストとして扱う
レンダリングの特殊処理
<plaintext>要素のレンダリングでは、以下の特殊処理が実装されています:
- 内容の生出力: 子要素の内容を生のテキストとして出力
- 終了タグの省略: <plaintext>要素の終了タグは出力しない
- レンダリング停止: <plaintext>要素の後は何も出力しない
コアとなるコードの変更箇所
1. parse.go:655-660行目
case "plaintext":
p.popUntil(buttonScopeStopTags, "p")
p.addElement(p.tok.Data, p.tok.Attr)
この変更により、<plaintext>要素に遭遇した際の処理が追加されました。
2. render.go:73-84行目
// plaintextAbort is returned from render1 when a <plaintext> element
// has been rendered. No more end tags should be rendered after that.
var plaintextAbort = errors.New("html: internal error (plaintext abort)")
func render(w writer, n *Node) error {
err := render1(w, n)
if err == plaintextAbort {
err = nil
}
return err
}
レンダリング停止のための特殊エラーハンドリングが追加されました。
3. token.go:161-170行目
if z.rawTag == "plaintext" {
// Read everything up to EOF.
for z.err == nil {
z.readByte()
}
z.textIsRaw = true
} else {
z.readRawOrRCDATA()
}
トークナイザーに<plaintext>要素の特殊処理が追加されました。
コアとなるコードの解説
パーサーの実装
parse.go
の変更では、inBodyIM
関数内で<plaintext>要素の処理が追加されています:
case "plaintext":
p.popUntil(buttonScopeStopTags, "p")
p.addElement(p.tok.Data, p.tok.Attr)
この処理では:
popUntil
でボタンスコープまでの<p>要素をポップaddElement
で<plaintext>要素をDOMに追加
これにより、<plaintext>要素が適切にDOMツリーに組み込まれます。
レンダラーの実装
render.go
の変更では、<plaintext>要素専用の停止機構が実装されています:
if n.Data == "plaintext" {
// Don't render anything else. <plaintext> must be the
// last element in the file, with no closing tag.
return plaintextAbort
}
この実装により、<plaintext>要素の後は何もレンダリングされなくなります。
トークナイザーの実装
token.go
の変更では、<plaintext>要素の特殊なトークン化処理が実装されています:
if z.rawTag == "plaintext" {
// Read everything up to EOF.
for z.err == nil {
z.readByte()
}
z.textIsRaw = true
}
この処理により、<plaintext>要素以降の全ての内容がテキストとして扱われます。
テストケースの更新
parse_test.go
では、テストケースの範囲が拡張され、<plaintext>要素のテストが追加されています:
{"tests2.dat", 26}, // 10から26に変更
また、レンダリングのブラックリストに<plaintext>要素のテストケースが追加されています:
// A <plaintext> element is reparented, putting it before a table.
// A <plaintext> element can't have anything after it in HTML.
`<table><plaintext><td>`: true,