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

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

このコミットは、Go言語の初期段階におけるprettyパッケージ、特にhtmlwriterprinterコンポーネントの改善に焦点を当てています。主な目的は、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つの主要な変更を目的としています。

  1. 現在の状態のスナップショット(コードベースの特定時点の保存)。
  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エンティティ(例: &lt;, &gt;, &amp;, &quot;, &#39;)に変換する処理です。これにより、ブラウザがそれらをHTMLの構文として解釈するのを防ぎます。
  • tabwriterパッケージ: Go言語の標準ライブラリの一部で、テキストをタブ区切りで整形し、列を揃えて出力するためのパッケージです。通常、コマンドラインツールなどで整形された表形式の出力を生成する際に使用されます。このコミットでは、tabwriter.New関数のシグネチャが変更され、HTMLモードを考慮した引数が追加されています。
  • fmt.Fprintf: fmtパッケージの関数で、指定されたio.Writerにフォーマットされた文字列を書き込みます。

技術的詳細

このコミットの技術的な核心は、htmlwriterパッケージのWriteメソッドの挙動変更と、printerパッケージにおける出力ストリームの分離です。

  1. htmlwriter.goの変更:

    • HTMLエスケープの実装: 以前のWriter.Writeメソッドは、単に基になるio.Writerにバイト列をそのまま渡していました。この変更により、Writeメソッドは入力されたバイト列を走査し、<&lt;に、&&amp;に変換して出力するようになりました。これにより、htmlwriterを介して書き込まれるテキストコンテンツは自動的にHTMLエスケープされるようになります。
    • Tagメソッドの追加: HTMLエスケープされたテキストとは別に、HTMLタグそのもの(例: <html>, <head>)を直接出力するためのTag(s string)メソッドが追加されました。このメソッドは、入力文字列をエスケープせずに基になるio.Writerに直接書き込みます。これにより、HTML構造を構築するためのタグと、その中に含まれるエスケープが必要なテキストコンテンツの出力パスが明確に分離されます。
    • インポートの変更: arrayutf8パッケージのインポートが削除され、fmtパッケージが追加されています。これは、Writeメソッドの実装変更に伴うものです。
  2. printer.goの変更:

    • 出力ストリームの分離: Printer構造体内の出力先が、以前のwriter *htmlwriter.Writerから、text io.Writetags *htmlwriter.Writerの2つに分割されました。
      • text io.Write: 主に整形されたテキストコンテンツ(コードなど)を出力するためのストリームです。これはtabwriterのインスタンスになることが想定されます。
      • tags *htmlwriter.Writer: HTMLタグやHTMLエスケープが必要な特定のHTML要素を出力するためのストリームです。これはhtmlwriterのインスタンスになることが想定されます。
    • Initメソッドの変更: Printer.Initメソッドのシグネチャが変更され、text io.Writetags *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のインスタンスが作成されず、リソースが節約されます。
  3. 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 = "&lt;";
+		case '&': s = "&amp;";
+		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エスケープ処理を行うようになった点です。
    • 入力bufi0からiまで走査し、<または&が見つかった場合、その手前までのバイト列をまず基になるp.writerに書き込みます。
    • 次に、見つかった特殊文字に対応するHTMLエンティティ(&lt;または&amp;)をio.WriteStringを使って書き込みます。
    • このプロセスをbufの最後まで繰り返し、最終的に残りのバイト列を書き込みます。
    • これにより、htmlwriter.Writerを介して出力されるすべてのテキストコンテンツは、自動的にHTMLエスケープされることが保証されます。
  • Writer.Tagメソッド: この新しいメソッドは、HTMLエスケープを必要としない生のHTMLタグ文字列を直接書き込むために導入されました。io.WriteStringを直接使用することで、エスケープ処理をスキップし、HTML構造を正確に構築できます。

printer.go

  • Printer構造体の変更: writer *htmlwriter.Writertext io.Writetags *htmlwriter.Writerに分割されたことで、プリンタはテキストコンテンツとHTMLタグを異なる方法で処理できるようになりました。
    • textは、主にコードの整形されたテキスト部分を担当し、tabwriterによってタブ整形されます。
    • tagsは、HTMLのプロローグ、エピローグ、特定のHTML要素など、HTMLタグそのものを出力する役割を担います。
  • Printer.Initメソッド: このメソッドは、新しいtexttagsの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タグを直接出力するようになりました。
    • HtmlIdentifierif false && P.tags != nilという条件は、一時的にHTML識別子のリンク生成を無効にしていることを示唆しています。これは開発中のデバッグや機能の一時停止のためによく見られるパターンです。
  • Print関数: この関数は、pretty printerのエントリポイントです。
    • tabwriter.Newの呼び出しにhtml.BVal()が追加されました。これは、tabwriterがHTMLモードを認識し、それに応じて内部的な挙動(例: タブの扱い方)を調整する可能性があることを示唆しています。
    • htmlwriter.Newの呼び出しは、html.BVal()が真の場合にのみ行われ、その結果がtags変数に代入されます。これにより、HTML出力が不要な場合にはhtmlwriterのインスタンスが作成されず、リソースが節約されます。
    • P.Initには、texttabwriterインスタンス)と、HTMLモードの場合はtagshtmlwriterインスタンス)、そうでない場合はnilが渡されます。
    • hwriter.Flush()の呼び出しが削除され、代わりにtext.Flush()が呼び出され、エラーハンドリングが追加されました。これは、tabwriterが最終的なフラッシュを担当し、そのエラーを適切に処理することを示しています。

untab.go

  • tabwriter.Newの呼び出しにfalseという追加の引数が渡されるようになりました。これは、untabツールがHTML出力を生成しないため、tabwriterを非HTMLモードで初期化することを示しています。

これらの変更は、Go言語の初期段階におけるコード整形とHTML出力の品質向上に向けた重要なステップであり、出力の正確性と柔軟性を高めることを目的としています。

関連リンク

参考にした情報源リンク