[インデックス 1305] ファイルの概要
このコミットは、Go言語の初期段階におけるpretty
パッケージ、特にhtmlwriter
とprinter
コンポーネントの改善に焦点を当てています。主な目的は、HTML出力の正確性を向上させるために、htmlwriter
が適切にHTMLエスケープを実行するように修正すること、および新しいtabwriter
インターフェースに合わせてプリンタのロジックを調整することです。
コミット
commit 4ad804679de4bc07d45e5d0bbfcc304bc1f45f12
Author: Robert Griesemer <gri@golang.org>
Date: Tue Dec 9 15:29:15 2008 -0800
- snapshot if current state
- fix pretty printer to work with new tabwriter interface
R=r
OCL=20854
CL=20854
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4ad804679de4bc07d45e5d0bbfcc304bc1f45f12
元コミット内容
このコミットは、以下の2つの主要な変更を目的としています。
- 現在の状態のスナップショット(コードベースの特定時点の保存)。
pretty printer
が新しいtabwriter
インターフェースと連携するように修正。
特に2番目の項目は、HTML出力におけるエスケープ処理の改善と、出力ストリームの分離を伴います。
変更の背景
Go言語の初期開発段階において、コードの整形(pretty printing)機能は重要なコンポーネントでした。この機能は、ソースコードを読みやすく表示するだけでなく、HTML形式で出力する能力も持っていました。しかし、HTML出力において、<
や&
といった特殊文字が適切にエスケープされずにそのまま出力されると、ブラウザがそれらをHTMLタグやエンティティとして解釈してしまい、意図しない表示崩れやセキュリティ上の問題(例: クロスサイトスクリプティング)を引き起こす可能性がありました。
また、tabwriter
パッケージは、テキストを整形してタブ区切りで出力するためのユーティリティですが、そのインターフェースが変更されたため、pretty printer
がこれに対応する必要がありました。この変更は、テキスト出力とHTMLタグ出力の役割を明確に分離し、より堅牢なコードベースを構築するための初期ステップと考えられます。
前提知識の解説
- Go言語の初期開発: このコミットは2008年に行われており、Go言語が一般に公開される前の非常に初期の段階です。当時のGo言語のAPIやパッケージ構造は現在とは大きく異なる場合があります。例えば、エラーハンドリングには
os.Error
が使われており、これは現在のerror
インターフェースとは異なります。 io.Writer
インターフェース: Go言語における基本的なI/Oインターフェースの一つで、Write([]byte) (n int, err error)
メソッドを持つ型が実装します。これにより、様々な出力先(ファイル、ネットワーク、標準出力など)に対して統一的な方法でバイト列を書き込むことができます。- HTMLエスケープ: HTMLドキュメント内で特殊な意味を持つ文字(例:
<
,>
,&
,"
,'
)を、その文字自体として表示するために、対応するHTMLエンティティ(例:<
,>
,&
,"
,'
)に変換する処理です。これにより、ブラウザがそれらをHTMLの構文として解釈するのを防ぎます。 tabwriter
パッケージ: Go言語の標準ライブラリの一部で、テキストをタブ区切りで整形し、列を揃えて出力するためのパッケージです。通常、コマンドラインツールなどで整形された表形式の出力を生成する際に使用されます。このコミットでは、tabwriter.New
関数のシグネチャが変更され、HTMLモードを考慮した引数が追加されています。fmt.Fprintf
:fmt
パッケージの関数で、指定されたio.Writer
にフォーマットされた文字列を書き込みます。
技術的詳細
このコミットの技術的な核心は、htmlwriter
パッケージのWrite
メソッドの挙動変更と、printer
パッケージにおける出力ストリームの分離です。
-
htmlwriter.go
の変更:- HTMLエスケープの実装: 以前の
Writer.Write
メソッドは、単に基になるio.Writer
にバイト列をそのまま渡していました。この変更により、Write
メソッドは入力されたバイト列を走査し、<
を<
に、&
を&
に変換して出力するようになりました。これにより、htmlwriter
を介して書き込まれるテキストコンテンツは自動的にHTMLエスケープされるようになります。 Tag
メソッドの追加: HTMLエスケープされたテキストとは別に、HTMLタグそのもの(例:<html>
,<head>
)を直接出力するためのTag(s string)
メソッドが追加されました。このメソッドは、入力文字列をエスケープせずに基になるio.Writer
に直接書き込みます。これにより、HTML構造を構築するためのタグと、その中に含まれるエスケープが必要なテキストコンテンツの出力パスが明確に分離されます。- インポートの変更:
array
とutf8
パッケージのインポートが削除され、fmt
パッケージが追加されています。これは、Write
メソッドの実装変更に伴うものです。
- HTMLエスケープの実装: 以前の
-
printer.go
の変更:- 出力ストリームの分離:
Printer
構造体内の出力先が、以前のwriter *htmlwriter.Writer
から、text io.Write
とtags *htmlwriter.Writer
の2つに分割されました。text io.Write
: 主に整形されたテキストコンテンツ(コードなど)を出力するためのストリームです。これはtabwriter
のインスタンスになることが想定されます。tags *htmlwriter.Writer
: HTMLタグやHTMLエスケープが必要な特定のHTML要素を出力するためのストリームです。これはhtmlwriter
のインスタンスになることが想定されます。
Init
メソッドの変更:Printer.Init
メソッドのシグネチャが変更され、text io.Write
とtags *htmlwriter.Writer
を引数として受け取るようになりました。これにより、プリンタの初期化時にテキスト出力とHTMLタグ出力のそれぞれのWriterを設定できるようになります。Printf
の出力先変更:Printf
メソッドは、P.writer
ではなくP.text
に出力するようになりました。これは、コードのテキスト部分がHTMLエスケープされることなく、tabwriter
によって整形されることを意図しています。- HTML関連メソッドの条件変更:
HtmlPrologue
,HtmlEpilogue
,HtmlIdentifier
などのHTML関連メソッドは、以前はhtml.BVal()
(おそらくHTMLモードを示すグローバルなブール値)をチェックしていましたが、この変更によりP.tags != nil
をチェックするようになりました。これは、tags
ストリームが設定されている場合にのみHTML出力を行うという、より明示的でオブジェクト指向的なアプローチです。 Print
関数のロジック変更:Print
関数は、tabwriter.New
の呼び出しにhtml.BVal()
を渡すようになりました。これは、tabwriter
自体がHTMLモードを認識し、それに応じて内部的な挙動を調整する可能性があることを示唆しています。また、htmlwriter
のインスタンスは、html.BVal()
が真の場合にのみ作成され、P.Init
に渡されます。これにより、HTML出力が不要な場合にはhtmlwriter
のインスタンスが作成されず、リソースが節約されます。
- 出力ストリームの分離:
-
untab.go
の変更:tabwriter.New
の呼び出しに、false
という追加の引数が渡されるようになりました。これは、untab
ツールがHTML出力を生成しないため、tabwriter
を非HTMLモードで初期化することを示しています。
これらの変更により、pretty printer
は、テキストコンテンツとHTMLタグをより適切に区別し、それぞれに適切なエスケープ処理を適用できるようになりました。
コアとなるコードの変更箇所
usr/gri/pretty/htmlwriter.go
--- a/usr/gri/pretty/htmlwriter.go
+++ b/usr/gri/pretty/htmlwriter.go
@@ -7,13 +7,13 @@ package htmlwriter
import (
"os";
"io";
- "array";
- "utf8";
+ "fmt";
)
// Writer is a filter implementing the io.Write interface.
// It provides facilities to generate HTML tags and does
-// proper HTML-escaping for text written through it.
+// HTML-escaping for text written through Write. Incoming
+// text is assumed to be UTF-8 encoded.
export type Writer struct {
// TODO should not export any of the fields
@@ -27,17 +27,43 @@ func (b *Writer) Init(writer io.Write) *Writer {
}
-/* export */ func (b *Writer) Flush() *os.Error {
- return nil;
+/* export */ func (p *Writer) Write(buf *[]byte) (written int, err *os.Error) {
+ i0 := 0;
+ for i := i0; i < len(buf); i++ {
+ var s string;
+ switch buf[i] {
+ case '<': s = "<";
+ case '&': s = "&";
+ default: continue;
+ }
+ // write HTML escape instead of buf[i]
+ w1, e1 := p.writer.Write(buf[i0 : i]);
+ if e1 != nil {
+ return i0 + w1, e1;
+ }
+ w2, e2 := io.WriteString(p.writer, s);
+ if e2 != nil {
+ return i0 + w1 /* not w2! */, e2;
+ }
+ i0 = i + 1;
+ }
+ written, err = p.writer.Write(buf[i0 : len(buf)]);
+ return len(buf), err;
}
-/* export */ func (b *Writer) Write(buf *[]byte) (written int, err *os.Error) {
- written, err = b.writer.Write(buf); // BUG 6g - should just have return
- return written, err;
+// ----------------------------------------------------------------------------
+// HTML-specific interface
+
+/* export */ func (p *Writer) Tag(s string) {
+ // TODO proper error handling
+ io.WriteString(p.writer, s);
}
+// ----------------------------------------------------------------------------
+//
+
export func New(writer io.Write) *Writer {
return new(Writer).Init(writer);
}
usr/gri/pretty/printer.go
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -6,6 +6,7 @@ package Printer
import (
"os";
+ "io";
"array";
"tabwriter";
"flag";
@@ -55,7 +56,8 @@ const (
type Printer struct {
// output
- writer *htmlwriter.Writer;
+ text io.Write;
+ tags *htmlwriter.Writer;
// comments
comments *array.Array; // the list of all comments
@@ -92,9 +94,10 @@ func (P *Printer) NextComment() {
}
-func (P *Printer) Init(writer *htmlwriter.Writer, comments *array.Array) {
- // writer
- P.writer = writer;
+func (P *Printer) Init(text io.Write, tags *htmlwriter.Writer, comments *array.Array) {
+ // writers
+ P.text = text;
+ P.tags = tags;
// comments
P.comments = comments;
@@ -109,7 +112,7 @@ func (P *Printer) Init(writer *htmlwriter.Writer, comments *array.Array) {
// Printing support
func (P *Printer) Printf(format string, s ...) {
- n, err := fmt.fprintf(P.writer, format, s);
+ n, err := fmt.fprintf(P.text, format, s);
if err != nil {
panic("print error - exiting");
}
@@ -311,11 +314,10 @@ func (P *Printer) Error(pos int, tok int, msg string) {
// ----------------------------------------------------------------------------
// HTML support
-// TODO Move this to html writer
func (P *Printer) HtmlPrologue(title string) {
- if html.BVal() {
- P.String(0,
+ if P.tags != nil {
+ P.tags.Tag(
"<html>\n"
"<head>\n"
"\t<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\n"
@@ -331,8 +333,8 @@ func (P *Printer) HtmlPrologue(title string) {
func (P *Printer) HtmlEpilogue() {
- if html.BVal() {
- P.String(0,
+ if P.tags != nil {
+ P.tags.Tag(
"</pre>\n"
"</body>\n"
"<html>\n"
@@ -342,8 +344,8 @@ func (P *Printer) HtmlEpilogue() {
func (P *Printer) HtmlIdentifier(pos int, ident string) {
- if html.BVal() {
- P.String(pos, `<a href="#` + ident + `">` + ident + `</a>`);
+ if false && P.tags != nil {
+ P.tags.Tag(`<a href="#` + ident + `">` + ident + `</a>`);
} else {
P.String(pos, ident);
}
@@ -831,20 +833,27 @@ func (P *Printer) Program(p *AST.Program) {
export func Print(prog *AST.Program) {
// setup
+ var P Printer;
padchar := byte(' ');
if usetabs.BVal() {
padchar = '\t';
}
- twriter := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true);
- hwriter := htmlwriter.New(twriter);
- var P Printer;
- P.Init(hwriter, prog.comments);
+ var (
+ text = tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true, html.BVal());
+ tags *htmlwriter.Writer;
+ )
+ if html.BVal() {
+ tags = htmlwriter.New(text);
+ }
+ P.Init(text, tags, prog.comments);
P.HtmlPrologue("<the source>");
P.Program(prog);
P.HtmlEpilogue();
P.String(0, ""); // flush pending separator/newlines
- hwriter.Flush(); // ignore errors
- twriter.Flush(); // ignore errors
+ err := text.Flush();
+ if err != nil {
+ panic("print error - exiting");
+ }
}
usr/gri/pretty/untab.go
--- a/usr/gri/pretty/untab.go
+++ b/usr/gri/pretty/untab.go
@@ -40,7 +40,7 @@ func main() {
if usetabs.BVal() {
padchar = '\t';
}
- dst := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true);
+ dst := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true, false);
if flag.NArg() > 0 {
for i := 0; i < flag.NArg(); i++ {
name := flag.Arg(i);
コアとなるコードの解説
htmlwriter.go
Writer.Write
メソッド: このメソッドは、io.Writer
インターフェースを実装しており、バイトスライスを受け取って書き込みます。変更の核心は、このメソッドがHTMLエスケープ処理を行うようになった点です。- 入力
buf
をi0
からi
まで走査し、<
または&
が見つかった場合、その手前までのバイト列をまず基になるp.writer
に書き込みます。 - 次に、見つかった特殊文字に対応するHTMLエンティティ(
<
または&
)をio.WriteString
を使って書き込みます。 - このプロセスを
buf
の最後まで繰り返し、最終的に残りのバイト列を書き込みます。 - これにより、
htmlwriter.Writer
を介して出力されるすべてのテキストコンテンツは、自動的にHTMLエスケープされることが保証されます。
- 入力
Writer.Tag
メソッド: この新しいメソッドは、HTMLエスケープを必要としない生のHTMLタグ文字列を直接書き込むために導入されました。io.WriteString
を直接使用することで、エスケープ処理をスキップし、HTML構造を正確に構築できます。
printer.go
Printer
構造体の変更:writer *htmlwriter.Writer
がtext io.Write
とtags *htmlwriter.Writer
に分割されたことで、プリンタはテキストコンテンツとHTMLタグを異なる方法で処理できるようになりました。text
は、主にコードの整形されたテキスト部分を担当し、tabwriter
によってタブ整形されます。tags
は、HTMLのプロローグ、エピローグ、特定のHTML要素など、HTMLタグそのものを出力する役割を担います。
Printer.Init
メソッド: このメソッドは、新しいtext
とtags
のWriterを受け取るように変更されました。これにより、Printer
のインスタンスが、HTML出力の有無や、どのようなio.Writer
を使用するかを柔軟に設定できるようになります。Printer.Printf
メソッド:fmt.fprintf
の出力先がP.writer
からP.text
に変更されました。これは、コードのテキスト部分がHTMLエスケープされることなく、tabwriter
によって整形されることを意図しています。- HTML関連メソッド (
HtmlPrologue
,HtmlEpilogue
,HtmlIdentifier
): これらのメソッドは、HTML出力を行うかどうかの判断基準をhtml.BVal()
からP.tags != nil
に変更しました。これは、tags
ストリームが設定されている場合にのみHTML関連の処理を行うという、より堅牢な設計です。また、これらのメソッドはP.tags.Tag()
を呼び出すことで、生のHTMLタグを直接出力するようになりました。HtmlIdentifier
のif false && P.tags != nil
という条件は、一時的にHTML識別子のリンク生成を無効にしていることを示唆しています。これは開発中のデバッグや機能の一時停止のためによく見られるパターンです。
Print
関数: この関数は、pretty printer
のエントリポイントです。tabwriter.New
の呼び出しにhtml.BVal()
が追加されました。これは、tabwriter
がHTMLモードを認識し、それに応じて内部的な挙動(例: タブの扱い方)を調整する可能性があることを示唆しています。htmlwriter.New
の呼び出しは、html.BVal()
が真の場合にのみ行われ、その結果がtags
変数に代入されます。これにより、HTML出力が不要な場合にはhtmlwriter
のインスタンスが作成されず、リソースが節約されます。P.Init
には、text
(tabwriter
インスタンス)と、HTMLモードの場合はtags
(htmlwriter
インスタンス)、そうでない場合はnil
が渡されます。hwriter.Flush()
の呼び出しが削除され、代わりにtext.Flush()
が呼び出され、エラーハンドリングが追加されました。これは、tabwriter
が最終的なフラッシュを担当し、そのエラーを適切に処理することを示しています。
untab.go
tabwriter.New
の呼び出しにfalse
という追加の引数が渡されるようになりました。これは、untab
ツールがHTML出力を生成しないため、tabwriter
を非HTMLモードで初期化することを示しています。
これらの変更は、Go言語の初期段階におけるコード整形とHTML出力の品質向上に向けた重要なステップであり、出力の正確性と柔軟性を高めることを目的としています。
関連リンク
- Go言語の公式ドキュメント (現在のバージョン): https://go.dev/doc/
io
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/iotext/tabwriter
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/text/tabwriterfmt
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/fmt
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- HTMLエスケープに関する一般的な情報: https://developer.mozilla.org/ja/docs/Glossary/HTML_escape_characters
- Go言語の歴史に関する情報 (非公式): https://go.dev/blog/go-at-google (Go言語の誕生に関する公式ブログ記事)
- Go言語の初期のコミット履歴を辿るためのGitHubインターフェース