[インデックス 1689] ファイルの概要
このコミットは、Go言語の標準ライブラリにXMLパーサーを導入するための初期提案と設計を記述したものです。具体的には、src/lib/xml.go
という新しいファイルが追加され、XMLパーシングの概念、Goのインターフェースを活用したパーサーの設計思想、およびXMLの基本的な仕様に関する詳細なノートが含まれています。この時点ではパーサーの実装自体は含まれておらず、あくまで設計案とドキュメントとしての役割を担っています。
コミット
commit 9e3e61627de958d4f3252cb60e2e5bff3861d7b4
Author: Russ Cox <rsc@golang.org>
Date: Mon Feb 16 20:14:21 2009 -0800
proposed XML parser design.
inspired by expat's callback interface,
but a bit simpler thanks to go interfaces.
also serves as reference notes about XML.
the parser itself is unimplemented.
not in Makefiles, though it does build.
R=r
DELTA=425 (425 added, 0 deleted, 0 changed)
OCL=25077
CL=25080
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9e3e61627de958d4f3252cb60e2e5bff3861d7b4
元コミット内容
proposed XML parser design.
inspired by expat's callback interface,
but a bit simpler thanks to go interfaces.
also serves as reference notes about XML.
the parser itself is unimplemented.
not in Makefiles, though it does build.
変更の背景
このコミットは、Go言語がまだ初期段階にあった2009年2月に行われました。当時のGo言語には、標準で利用できる堅牢なXMLパーサーが存在していなかったと考えられます。XMLは、設定ファイル、データ交換フォーマット、Webサービス(SOAPなど)など、様々な場面で広く利用されており、Go言語が実用的なプログラミング言語として普及するためには、XMLを効率的に処理できる機能が不可欠でした。
このコミットは、Go言語の設計思想(シンプルさ、並行処理の容易さなど)に合致するXMLパーサーの設計を模索する初期段階の成果物です。特に、C言語で書かれた高速なXMLパーサーライブラリであるExpatのコールバックインターフェースから着想を得つつ、Go言語のインターフェースの特性を活かしてよりシンプルでGoらしいAPIを提供しようとする意図が見て取れます。
また、このコミットが単なるコードの追加ではなく、「reference notes about XML」としても機能すると明記されていることから、Go言語コミュニティ内でXMLの基本的な概念やGoでのXML処理のベストプラクティスを共有するための基盤を築く目的もあったと推測されます。
前提知識の解説
このコミットの理解には、以下の前提知識が役立ちます。
1. XML (Extensible Markup Language)
XMLは、情報を構造化するためのマークアップ言語です。HTMLと似ていますが、XMLはデータの意味を記述することに重点を置いており、ユーザーが独自のタグを定義できる点が特徴です。
- 要素 (Elements):
<tag>...</tag>
のように、開始タグと終了タグで囲まれた構造。属性を持つこともあります。 - 属性 (Attributes): 開始タグ内に
name="value"
の形式で記述され、要素に追加情報を提供します。 - テキスト (Text): 要素の開始タグと終了タグの間に含まれるデータ。
- XML宣言 (XML Declaration):
<?xml version="1.0" encoding="UTF-8"?>
のように、XML文書の冒頭に記述され、XMLのバージョンやエンコーディングを指定します。 - DOCTYPE宣言 (Document Type Declaration):
<!DOCTYPE ...>
の形式で、XML文書が特定のDTD(Document Type Definition)またはスキーマに準拠していることを宣言します。 - 処理命令 (Processing Instructions - PI):
<?target instruction?>
の形式で、アプリケーションに対する命令を記述します。XML宣言も一種のPIです。 - コメント (Comments):
<!-- comment text -->
の形式で、XML文書内にコメントを記述します。 - CDATAセクション:
<![CDATA[...]]>
の形式で、内部のテキストがマークアップとして解釈されないようにします。これにより、<
や&
といった特殊文字をエスケープせずに記述できます。 - 名前空間 (Namespaces): XML要素や属性の名前の衝突を避けるために使用されます。
xmlns:prefix="URI"
の形式で宣言され、prefix:tag
のように使用されます。
2. XMLパーサーの種類
XMLパーサーは、XML文書を読み込み、その構造をアプリケーションが利用できる形式に変換するソフトウェアです。主なパーサーの種類には以下があります。
- DOM (Document Object Model) パーサー: XML文書全体をメモリ上にツリー構造として構築します。文書全体にアクセスできるため柔軟ですが、大きな文書ではメモリ消費が大きくなる可能性があります。
- SAX (Simple API for XML) パーサー: イベント駆動型パーサーとも呼ばれ、XML文書を順次読み込み、特定のイベント(要素の開始、終了、テキストデータの検出など)が発生したときにコールバック関数を呼び出します。メモリ効率が良いですが、文書の構造を自分で追跡する必要があります。
- StAX (Streaming API for XML) パーサー: プルパーサーとも呼ばれ、アプリケーションがXMLイベントを要求するまで待機します。SAXとDOMの中間的な特性を持ち、SAXよりも柔軟でDOMよりもメモリ効率が良いとされます。
このコミットで提案されている設計は、Expatに触発されていることから、SAXのようなイベント駆動型のアプローチを採用していることが示唆されます。
3. Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。型がインターフェースのすべてのメソッドを実装していれば、そのインターフェースを満たしているとみなされます(暗黙的な実装)。これにより、柔軟な設計とポリモーフィズムが実現されます。このコミットでは、XMLパーサーが検出したイベントを通知するための「Builder」インターフェースが定義されており、Goのインターフェースの強力な機能が活用されています。
4. Expat XML Parser
Expatは、C言語で書かれたオープンソースのXML 1.0パーサーライブラリです。SAXのようなイベント駆動型のアプローチを採用しており、高速で軽量であることが特徴です。Expatは、XML文書を解析する際に、要素の開始、終了、文字データなどのイベントが発生すると、登録されたコールバック関数を呼び出します。このコミットの設計は、このExpatのコールバックモデルから大きな影響を受けていることが明記されています。
技術的詳細
このコミットで提案されているXMLパーサーの設計は、Go言語のインターフェースを最大限に活用したイベント駆動型のアプローチに基づいています。
1. Builder
インターフェース
パーサーの核となるのは Builder
インターフェースです。このインターフェースは、XML文書の解析中に発生する様々なイベントに対応するメソッドを定義しています。パーサーは、これらのメソッドを呼び出すことで、解析結果をクライアント(Builder
インターフェースを実装するオブジェクト)に通知します。
StartElement(name Name, attr []Attr) *os.Error
: 要素の開始タグが検出されたときに呼び出されます。要素名と属性のリストが渡されます。xmlns
やxmlns:foo
のような名前空間宣言属性は内部で処理され、このメソッドには渡されません。EndElement(name Name) *os.Error
: 要素の終了タグが検出されたときに呼び出されます。要素名が渡されます。Text(text []byte) *os.Error
: 要素内の文字データ(テキストノード)が検出されたときに呼び出されます。テキストデータはバイトスライスとして渡されます。XMLの仕様に従い、\r
や\r\n
は\n
に変換され、エスケープされた文字(<
,&
など)は元の文字にアンエスケープされて渡されます。Comment(text []byte) *os.Error
: XMLコメント(<!-- ... -->
)が検出されたときに呼び出されます。コメントの内容がバイトスライスとして渡されます。ProcInst(target string, text []byte) *os.Error
: 処理命令(<?target text?>
)が検出されたときに呼び出されます。ターゲット名と命令のテキストが渡されます。XML宣言(<?xml ...?>
)はパーサー内部で処理され、このメソッドには渡されません。
これらのメソッドがエラーを返した場合、パーシング処理は停止します。
2. Name
および Attr
構造体
XMLの名前空間を適切に扱うために、Name
構造体が導入されています。
type Name struct { ns, name string; }
: XML要素名や属性名を表現します。ns
フィールドは名前空間のURI(Uniform Resource Identifier)を表し、name
フィールドはローカル名を表します。名前空間が指定されていない場合はns
は空文字列になります。これにより、prefix:localname
のような形式ではなく、名前空間とローカル名を分離して扱うことで、XMLの名前空間の複雑さをクライアントから隠蔽しようとしています。type Attr struct { name Name; value string; }
: XML属性を表現します。name
フィールドはName
構造体であり、属性名とその名前空間を保持します。value
フィールドは属性の値を文字列として保持します。
3. BaseBuilder
構造体
BaseBuilder
は、Builder
インターフェースのすべてのメソッドを空の操作(no-op)として実装するヘルパー構造体です。これにより、カスタムの Builder
を実装する際に、関心のないイベントのメソッドを個別に実装する必要がなく、BaseBuilder
を埋め込むことで、必要なメソッドのみをオーバーライドすればよくなります。これはGo言語における「埋め込み」の典型的なユースケースの一つです。
4. Parse
関数 (未実装)
func Parse(r io.Read, b Builder) *os.Error
: この関数は、XMLパーサーのエントリポイントとなることを意図していますが、このコミットの時点では return os.NewError("unimplemented");
となっており、実際のパーシングロジックは実装されていません。これは、このコミットが設計提案とドキュメントに重点を置いていることを明確に示しています。
5. チャネルベースのインターフェース (ChanBuilder
)
コールバックベースの Builder
インターフェースに加えて、Goのチャネルを活用した別のパーシングインターフェースも提案されています。
type Token struct { ... }
: XMLパーシング中に発生するイベントを表す構造体です。イベントの種類(Kind
)、要素名(Name
)、属性(Attr
)、処理命令のターゲット(Target
)、テキストデータ(Text
)、およびエラー情報(Err
)を保持します。type ChanBuilder chan Token
:Token
型のチャネルをBuilder
インターフェースとしてラップしています。ChanBuilder
の各Builder
メソッドは、対応するToken
をチャネルに送信します。func ParseToChan(r io.Read, c chan Token)
: この関数は、Parse
関数を呼び出し、その結果をTokenEnd
型のToken
としてチャネルに送信します。このアプローチの利点として、チャネルを読み取るプロセスを再帰関数として記述できる点が挙げられています。しかし、パーサーが早期にエラーをシグナルして停止させることができないという欠点も指摘されています。
このチャネルベースの設計は、Go言語の並行処理プリミティブであるチャネルをXMLパーシングに適用しようとする初期の試みであり、Goの設計思想を反映しています。
6. XML仕様に関するノート
src/lib/xml.go
ファイルの後半には、XMLの仕様に関する詳細な「scribbled notes」が含まれています。これには、XML宣言、DOCTYPE宣言、処理命令、コメント、タグの構造、テキストのエスケープ、CDATAセクション、そして特に複雑なXML名前空間の扱いに関する詳細な説明が含まれています。また、XML名(要素名、属性名)に使用できるUnicode文字の範囲も詳細にリストアップされています。これらのノートは、パーサーの実装者がXMLの複雑な仕様を正確に理解し、適切に処理するためのガイドラインとして機能することを意図しています。
コアとなるコードの変更箇所
このコミットでは、src/lib/xml.go
という新しいファイルが追加されています。既存のファイルへの変更や削除はありません。
--- /dev/null
+++ b/src/lib/xml.go
@@ -0,0 +1,426 @@
+// Copyright 2009 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.
+
+// NOTE(rsc): Actually, this package is just a description
+// of an implementation that hasn't been written yet.
+
+// This package implements an XML parser but relies on
+// clients to implement the parsing actions.
+
+// An XML document is a single XML element.
+// ... (XMLの基本的な概念に関する詳細な説明) ...
+
+// This parser reads an XML document and calls methods on a
+// Builder interface object in response to the text.
+// ... (Builderインターフェースの呼び出し例) ...
+
+// There are, of course, a few more details, but the story so far
+// should be enough for the majority of uses. The details are:
+// ... (XMLのより詳細な仕様に関する説明: XML宣言、DOCTYPE、PI、コメント、エスケープ、CDATA、名前空間) ...
+
+// References:
+// Annotated XML spec: http://www.xml.com/axml/testaxml.htm
+// XML name spaces: http://www.w3.org/TR/REC-xml-names/
+
+package xml
+
+import (
+ "io";
+ "os";
+)
+
+// XML name, annotated with name space URL
+type Name struct {
+ ns, name string;
+}
+
+// XML attribute (name=value).
+type Attr struct {
+ name Name;
+ value string;
+}
+
+// XML Builder - methods client provides to Parser.
+// Parser calls methods on builder as it reads and parses XML.
+// If a builder method returns an error, the parse stops.
+type Builder interface {
+ // Called when an element starts.
+ // ...
+ StartElement(name Name, attr []Attr) *os.Error;
+
+ // Called when an element ends.
+ // ...
+ EndElement(name Name) *os.Error;
+
+ // Called for non-empty character data string inside element.
+ // ...
+ Text(text []byte) *os.Error;
+
+ // Called when a comment is found in the XML.
+ // ...
+ Comment(text []byte) *os.Error;
+
+ // Called for a processing instruction
+ // ...
+ ProcInst(target string, text []byte) *os.Error;
+}
+
+// Default builder. Implements no-op Builder methods.
+// ...
+type BaseBuilder struct {
+}
+
+func (b *BaseBuilder) StartElement(name Name, attr []Attr) *os.Error {
+ return nil;
+}
+
+func (b *BaseBuilder) EndElement(name Name) *os.Error {
+ return nil;
+}
+
+func (b *BaseBuilder) Text(text []byte) *os.Error {
+ return nil;
+}
+
+func (b *BaseBuilder) Comment(text []byte) *os.Error {
+ return nil;
+}
+
+func (b *BaseBuilder) ProcInst(target string, text []byte) *os.Error {
+ return nil;
+}
+
+// XML Parser. Calls Builder methods as it parses.
+func Parse(r io.Read, b Builder) *os.Error {
+ return os.NewError("unimplemented");
+}
+
+// Channel interface to XML parser: create a new channel,
+// go ParseTokens(r, c), and then read from the channel
+// until TokenEnd. This variant has the benefit that
+// the process reading the channel can be a recursive
+// function instead of a set of callbacks, but it has the
+// drawback that the channel interface cannot signal an
+// error to cause the parser to stop early.
+
+// An XML parsing token.
+const (
+ TokenStartElement = 1 + iota;
+ TokenEndElement;
+ TokenText;
+ TokenComment;
+ TokenProcInst;
+ TokenEnd;
+)
+
+type Token struct {
+ Kind int; // TokenStartElement, TokenEndElement, etc.
+ Name Name; // name (TokenStartElement, TokenEndElement)
+ Attr []Attr; // attributes (TokenStartElement)
+ Target string; // target (TokenProcessingInstruction)
+ Text []byte; // text (TokenCharData, TokenComment, etc.)
+ Err *os.Error; // error (TokenEnd)
+}
+
+type ChanBuilder chan Token;
+
+func (c ChanBuilder) StartElement(name Name, attr []Attr) *os.Error {
+ var t Token;
+ t.Kind = TokenStartElement;
+ t.Name = name;
+ t.Attr = attr;
+ c <- t;
+ return nil;
+}
+
+func (c ChanBuilder) EndElement(name Name) *os.Error {
+ var t Token;
+ t.Kind = TokenEndElement;
+ t.Name = name;
+ c <- t;
+ return nil;
+}
+
+func (c ChanBuilder) Text(text []byte) *os.Error {
+ var t Token;
+ t.Kind = TokenText;
+ t.Text = text;
+ c <- t;
+ return nil;
+}
+
+func (c ChanBuilder) Comment(text []byte) *os.Error {
+ var t Token;
+ t.Kind = TokenComment;
+ t.Text = text;
+ c <- t;
+ return nil;
+}
+
+func (c ChanBuilder) ProcInst(target string, text []byte) *os.Error {
+ var t Token;
+ t.Kind = TokenProcInst;
+ t.Target = target;
+ t.Text = text;
+ c <- t;
+ return nil;
+}
+
+func ParseToChan(r io.Read, c chan Token) {
+ var t Token;
+ t.Kind = TokenEnd;
+ t.Err = Parse(r, ChanBuilder(c));
+ c <- t;
+}
+
+
+// scribbled notes based on XML spec.
+
+// document is
+// xml decl?
+// doctype decl?
+// element
+// ... (XMLの構文規則と文字セットに関する詳細なノート) ...
コアとなるコードの解説
追加された src/lib/xml.go
ファイルは、Go言語におけるXMLパーサーの設計概念を提示しています。
-
パッケージ宣言とインポート:
package xml
: このファイルがxml
パッケージの一部であることを示します。import ("io"; "os";)
: 入出力操作のためのio
パッケージと、エラー処理のためのos
パッケージをインポートしています。
-
データ構造の定義:
Name
構造体: XML要素名や属性名を表現するために使用されます。ns
(namespace URI) とname
(local name) の2つの文字列フィールドを持ちます。これにより、XMLの名前空間を透過的に扱うことを目指しています。Attr
構造体: XML属性を表現します。name
フィールドはName
型であり、属性名とその名前空間をカプセル化します。value
フィールドは属性の値を文字列として保持します。
-
Builder
インターフェース:- XMLパーサーが解析中に検出したイベントをクライアントに通知するためのコールバックインターフェースです。
StartElement
,EndElement
,Text
,Comment
,ProcInst
の各メソッドが定義されており、それぞれ要素の開始、要素の終了、テキストデータ、コメント、処理命令の検出に対応します。- これらのメソッドが
*os.Error
を返すことで、パーシング処理を途中で停止させるメカニズムを提供します。
-
BaseBuilder
構造体:Builder
インターフェースのすべてのメソッドを空実装(no-op)として提供するヘルパー構造体です。- カスタムの
Builder
を実装する際に、このBaseBuilder
を埋め込むことで、関心のないイベントのメソッドを実装する手間を省くことができます。
-
Parse
関数:func Parse(r io.Read, b Builder) *os.Error
: XMLパーシングを開始するための主要な関数として定義されています。io.Read
インターフェースを実装するリーダーからXMLデータを読み込み、Builder
インターフェースを実装するオブジェクトにイベントを通知することを想定しています。- しかし、このコミットの時点では、この関数は
os.NewError("unimplemented")
を返すだけで、実際のパーシングロジックは含まれていません。これは、このファイルが設計提案とドキュメントとしての役割を果たすことを強調しています。
-
チャネルベースのパーシングインターフェース:
- Go言語のチャネルを活用した代替のパーシングメカニズムも提案されています。
Token
構造体: XMLパーシング中に発生する様々なイベント(要素の開始、終了、テキストなど)を表現するための汎用的な構造体です。イベントの種類を示すKind
フィールドと、イベントに応じたデータ(Name
,Attr
,Target
,Text
,Err
)を持ちます。ChanBuilder
型:Token
型のチャネルをBuilder
インターフェースとしてラップしています。ChanBuilder
の各Builder
メソッドは、対応するToken
をチャネルに送信します。ParseToChan
関数:Parse
関数を内部で呼び出し、パーシング結果をチャネルを通じてToken
としてストリームします。このアプローチは、コールバックではなくチャネルを介してイベントを消費したい場合に有用です。
-
XML仕様に関する詳細なノート:
- ファイルの後半には、XMLの文書構造、タグの形式、属性、テキストのエスケープルール、CDATAセクション、そして特に複雑なXML名前空間の解決メカニズムに関する詳細な説明がコメント形式で記述されています。
- XML名に使用できるUnicode文字の範囲も、詳細なコードポイントリストとして提供されています。
- これらのノートは、将来のパーサー実装者がXMLの複雑な仕様を正確に理解し、堅牢なパーサーを構築するための重要なリファレンスとして機能します。
全体として、このコミットはGo言語のXMLパーサーの基礎となる設計思想とAPIの骨格を提示しており、Goのインターフェースとチャネルの特性を活かした、シンプルかつGoらしいXML処理の方向性を示唆しています。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語のインターフェースに関するドキュメント: https://go.dev/tour/methods/9
- Go言語のチャネルに関するドキュメント: https://go.dev/tour/concurrency/2
参考にした情報源リンク
- Annotated XML spec: http://www.xml.com/axml/testaxml.htm (コミット内のコメントで参照されているリンク)
- XML name spaces: http://www.w3.org/TR/REC-xml-names/ (コミット内のコメントで参照されているリンク)
- Expat XML Parser: https://libexpat.github.io/ (コミットメッセージで言及されているXMLパーサーライブラリ)
- XML (Extensible Markup Language) - Wikipedia: https://ja.wikipedia.org/wiki/Extensible_Markup_Language
- SAX (Simple API for XML) - Wikipedia: https://ja.wikipedia.org/wiki/SAX
- DOM (Document Object Model) - Wikipedia: https://ja.wikipedia.org/wiki/Document_Object_Model
- StAX (Streaming API for XML) - Wikipedia: https://ja.wikipedia.org/wiki/StAX
- Go言語の歴史 (Go言語が誕生した時期の背景理解のため): https://go.dev/doc/history