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

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

このコミットは、Go言語の標準ライブラリである encoding/xml パッケージにおいて、XMLのDOCTYPE宣言内に含まれるコメント(<!-- ... -->)を正しくパースできるようにする修正です。これにより、パーサーがDOCTYPE宣言内のコメントをスキップし、その後のXML構造を正確に解析できるようになります。

コミット

commit a11b748fa2e4d3244b0a3f28d4ce1647f2ef9997
Author: Shawn Smith <shawn.p.smith@gmail.com>
Date:   Fri Aug 31 18:09:31 2012 -0400

    encoding/xml: parse comments in DOCTYPE
    
    R=rsc, n13m3y3r
    CC=golang-dev
    https://golang.org/cl/6330061
---
 src/pkg/encoding/xml/xml.go      | 31 ++++++++++++++++++++++++++++++-\n src/pkg/encoding/xml/xml_test.go | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+), 1 deletion(-)

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

https://github.com/golang/go/commit/a11b748fa2e4d3244b0a3f28d4ce1647f2ef9997

元コミット内容

このコミットの目的は、encoding/xml パッケージがXMLのDOCTYPE宣言(Document Type Declaration)内でコメントを適切に解析できるようにすることです。具体的には、RawToken() メソッドがDOCTYPE宣言の内部で <!-- で始まるコメントブロックを検出した場合に、そのコメントの内容をスキップし、コメントの終了を示す --> までを正しく読み飛ばすように変更されています。これにより、コメントがパーサーの処理を妨げることがなくなり、DOCTYPE宣言の残りの部分が正しく解釈されるようになります。

変更の背景

XMLパーサーは、XMLドキュメントの構造を正確に理解するために、すべての構文要素を適切に処理する必要があります。DOCTYPE宣言はXMLドキュメントのDTD(Document Type Definition)を指定する重要な部分であり、その内部にはエンティティ宣言やコメントなど、様々なマークアップが含まれることがあります。

このコミット以前の encoding/xml パッケージでは、DOCTYPE宣言内にコメントが含まれている場合、パーサーがそのコメントを正しく認識せず、構文エラーとして扱ってしまう可能性がありました。これは、XML仕様に準拠したドキュメントを処理する上で問題となります。例えば、DTDの定義を説明するためのコメントがDOCTYPE宣言内に記述されている場合、そのドキュメントをGoのXMLパーサーで処理できないという問題が発生します。

この変更は、このようなXMLドキュメントの互換性の問題を解決し、encoding/xml パッケージがより広範なXMLドキュメントを堅牢に処理できるようにするために行われました。

前提知識の解説

XML (Extensible Markup Language)

XMLは、情報を構造化するためのマークアップ言語です。HTMLと同様にタグを使用しますが、XMLのタグはユーザーが自由に定義できる点が異なります。XMLはデータの記述と転送に広く用いられ、異なるシステム間でのデータ交換の標準となっています。

DOCTYPE宣言 (Document Type Declaration)

DOCTYPE宣言は、XMLドキュメントの冒頭に記述され、そのドキュメントがどのDTD(Document Type Definition)に準拠しているかを宣言します。DTDは、XMLドキュメントの構造(どの要素がどこに現れるか、どのような属性を持つかなど)を定義するスキーマです。

例:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

または、内部DTDサブセットを含む場合:

<!DOCTYPE root [
  <!ELEMENT root (child)>
  <!ELEMENT child (#PCDATA)>
  <!-- これはDOCTYPE宣言内のコメントです -->
]>

XMLコメント

XMLコメントは <!-- で始まり --> で終わります。コメントの内容はXMLパーサーによって無視され、ドキュメントの構造や内容には影響を与えません。コメントは、人間がドキュメントを理解しやすくするために使用されます。

例:

<!-- これはXMLドキュメント内のコメントです -->
<element>
  <!-- この要素に関する情報 -->
  <subelement/>
</element>

Go言語の encoding/xml パッケージ

encoding/xml は、Go言語の標準ライブラリで、XMLドキュメントのエンコード(Goのデータ構造からXMLへ)とデコード(XMLからGoのデータ構造へ)を提供します。このパッケージは、XMLパーサーとXMLエンコーダーの機能を提供し、XMLデータの読み書きを容易にします。

  • xml.Decoder: XMLストリームを読み込み、Goのデータ構造にデコードするための型です。
  • xml.Token(): Decoder のメソッドで、XMLストリームから次のトークン(要素の開始、終了、文字データ、コメント、処理命令など)を読み取ります。
  • xml.RawToken(): Decoder の内部メソッドで、より低レベルなXMLトークン(特にディレクティブなど)を生のバイト列として読み取ります。このコミットで修正されたのはこのメソッドです。
  • xml.Directive: XMLのディレクティブ(DOCTYPE宣言など)を表すトークン型です。

技術的詳細

このコミットの技術的な核心は、encoding/xml パッケージの Decoder 型が持つ RawToken() メソッドの変更にあります。RawToken() メソッドは、XMLドキュメントから生のトークンを読み取る際に、特にDOCTYPE宣言のようなディレクティブの内部構造を解析する役割を担っています。

変更前は、RawToken() メソッドが < 文字を検出すると、単純に depth カウンターをインクリメントしていました。この depth カウンターは、ネストされたタグやディレクティブの深さを追跡するために使用されます。しかし、DOCTYPE宣言の内部で <!-- というコメントの開始シーケンスが出現した場合、パーサーはこれを通常のタグの開始と誤解し、depth をインクリメントしてしまい、コメントの終了シーケンス --> を正しく認識できずに解析エラーを引き起こす可能性がありました。

今回の修正では、RawToken() メソッドが < 文字を検出した際に、その直後に !-- が続くかどうかをチェックするロジックが追加されました。

  1. コメント開始シーケンスの検出: < を検出した後、パーサーは次の3バイトが !-- であるかを順に確認します。
  2. コメントでない場合のフォールバック: もし !-- のいずれかの文字が一致しなかった場合、それはコメントではなく通常のタグまたはディレクティブの開始であると判断されます。この場合、既に読み込んだ s (ここでは !-- の各文字) をバッファに書き戻し、depth をインクリメントして、通常の処理フロー (goto HandleB) に戻ります。これにより、既存のパーシングロジックが維持されます。
  3. コメントである場合の処理: <!-- シーケンスが完全に一致した場合、パーサーはバッファから既に書き込まれた < を削除します(d.buf.Truncate(d.buf.Len() - 1))。これは、コメント自体はディレクティブの「内容」ではないため、ディレクティブの生のバイト列には含めないという設計に基づいています。
  4. コメント終了シーケンスの探索: その後、パーサーはループに入り、コメントの終了シーケンス --> を探し続けます。これは、b0, b1, b という3つのバイト変数を使って、現在のバイトと直前の2つのバイトを比較することで実現されます。b0 == '-' && b1 == '-' && b == '>' が真になった時点でコメントの終了と判断し、ループを抜けます。この間、コメントの内容はバッファに書き込まれず、実質的にスキップされます。

この変更により、RawToken() はDOCTYPE宣言内のコメントを正しく識別し、その内容を無視して、DOCTYPE宣言の残りの部分の解析を続行できるようになりました。これにより、encoding/xml パッケージはXML仕様に準拠したより複雑なDOCTYPE宣言を持つドキュメントも問題なく処理できるようになります。

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

src/pkg/encoding/xml/xml.goRawToken() メソッド内の以下の部分が変更されました。

--- a/src/pkg/encoding/xml/xml.go
+++ b/src/pkg/encoding/xml/xml.go
@@ -599,7 +600,35 @@ func (d *Decoder) RawToken() (Token, error) {
 			case b == '<' && inquote == 0:
-				depth++
+				// Look for <!-- to begin comment.
+				s := "!--"
+				for i := 0; i < len(s); i++ {
+					if b, ok = d.mustgetc(); !ok {
+						return nil, d.err
+					}
+					if b != s[i] {
+						for j := 0; j < i; j++ {
+							d.buf.WriteByte(s[j])
+						}
+						depth++
+						goto HandleB
+					}
+				}
+
+				// Remove < that was written above.
+				d.buf.Truncate(d.buf.Len() - 1)
+
+				// Look for terminator.
+				var b0, b1 byte
+				for {
+					if b, ok = d.mustgetc(); !ok {
+						return nil, d.err
+					}
+					if b0 == '-' && b1 == '-' && b == '>' {
+						break
+					}
+					b0, b1 = b1, b
+				}
 			}
 		}
 		return Directive(d.buf.Bytes()), nil

また、src/pkg/encoding/xml/xml_test.go には、この変更を検証するための新しいテストケース TestDirectivesWithComments が追加されています。

コアとなるコードの解説

変更されたコードブロックは、RawToken() メソッドが < 文字を検出した際に実行されます。これは、新しいXMLマークアップの開始を示唆するものです。

  1. s := "!--": コメントの開始シーケンス !-- を文字列として定義します。
  2. for i := 0; i < len(s); i++ { ... }: このループは、!-- の各文字を順に読み込み、現在の入力ストリームの文字と比較します。
    • if b, ok = d.mustgetc(); !ok { return nil, d.err }: ストリームから次のバイトを読み込みます。エラーが発生した場合は、即座にエラーを返します。
    • if b != s[i] { ... }: 読み込んだバイトが s[i]!-- の期待される文字)と一致しない場合、それはコメントの開始ではないと判断されます。
      • for j := 0; j < i; j++ { d.buf.WriteByte(s[j]) }: ここまでに一致した !-- の部分(もしあれば)をバッファに書き戻します。これは、パーサーがコメントと誤解して読み進めてしまった文字を元に戻すためです。
      • depth++: 通常のタグまたはディレクティブの開始として depth をインクリメントします。
      • goto HandleB: HandleB ラベルにジャンプし、通常のトークン処理を続行します。
  3. d.buf.Truncate(d.buf.Len() - 1): <!-- が完全に一致した場合、バッファから直前に書き込まれた < を削除します。これは、コメントの内容はディレクティブの生のバイト列には含めないためです。
  4. var b0, b1 byte: コメントの終了シーケンス --> を検出するためのヘルパー変数 b0b1 を宣言します。これらは、直前の2つのバイトを保持します。
  5. for { ... }: この無限ループは、コメントの終了シーケンス --> が見つかるまで続きます。
    • if b, ok = d.mustgetc(); !ok { return nil, d.err }: ストリームから次のバイトを読み込みます。
    • if b0 == '-' && b1 == '-' && b == '>' { break }: b0-b1-、そして現在のバイト b> である場合、--> シーケンスが検出されたことを意味します。ループを抜けます。
    • b0, b1 = b1, b: 現在のバイト bb1 に、b1b0 にシフトし、次のバイトの読み込みに備えます。

このロジックにより、encoding/xml パーサーはDOCTYPE宣言内のコメントを正確にスキップし、XMLドキュメントの堅牢な解析を保証します。

関連リンク

参考にした情報源リンク