diff --git a/src/pkg/html/Makefile b/src/pkg/html/Makefile index 28dc1a3f524..2d664720d3f 100644 --- a/src/pkg/html/Makefile +++ b/src/pkg/html/Makefile @@ -12,6 +12,7 @@ GOFILES=\ escape.go\ node.go\ parse.go\ + render.go\ token.go\ include ../../Make.pkg diff --git a/src/pkg/html/escape.go b/src/pkg/html/escape.go index 0de97c5ac1b..4d0661ff36a 100644 --- a/src/pkg/html/escape.go +++ b/src/pkg/html/escape.go @@ -6,6 +6,7 @@ package html import ( "bytes" + "os" "strings" "utf8" ) @@ -184,10 +185,12 @@ func unescape(b []byte) []byte { const escapedChars = `&'<>"` -func escape(buf *bytes.Buffer, s string) { +func escape(w writer, s string) os.Error { i := strings.IndexAny(s, escapedChars) for i != -1 { - buf.WriteString(s[0:i]) + if _, err := w.WriteString(s[:i]); err != nil { + return err + } var esc string switch s[i] { case '&': @@ -204,10 +207,13 @@ func escape(buf *bytes.Buffer, s string) { panic("unrecognized escape character") } s = s[i+1:] - buf.WriteString(esc) + if _, err := w.WriteString(esc); err != nil { + return err + } i = strings.IndexAny(s, escapedChars) } - buf.WriteString(s) + _, err := w.WriteString(s) + return err } // EscapeString escapes special characters like "<" to become "<". It diff --git a/src/pkg/html/parse_test.go b/src/pkg/html/parse_test.go index 7d918d25086..5a473694b3c 100644 --- a/src/pkg/html/parse_test.go +++ b/src/pkg/html/parse_test.go @@ -134,7 +134,7 @@ func TestParser(t *testing.T) { if err != nil { t.Fatal(err) } - actual, err := dump(doc) + got, err := dump(doc) if err != nil { t.Fatal(err) } @@ -147,9 +147,24 @@ func TestParser(t *testing.T) { if err != nil { t.Fatal(err) } - expected := string(b) - if actual != expected { - t.Errorf("%s test #%d %q, actual vs expected:\n----\n%s----\n%s----", filename, i, text, actual, expected) + if want := string(b); got != want { + t.Errorf("%s test #%d %q, got vs want:\n----\n%s----\n%s----", filename, i, text, got, want) + } + // Check that rendering and re-parsing results in an identical tree. + pr, pw := io.Pipe() + go func() { + pw.CloseWithError(Render(pw, doc)) + }() + doc1, err := Parse(pr) + if err != nil { + t.Fatal(err) + } + got1, err := dump(doc1) + if err != nil { + t.Fatal(err) + } + if got != got1 { + t.Errorf("%s test #%d %q, got vs got1:\n----\n%s----\n%s----", filename, i, text, got, got1) } } } diff --git a/src/pkg/html/render.go b/src/pkg/html/render.go new file mode 100644 index 00000000000..bf7b5995a11 --- /dev/null +++ b/src/pkg/html/render.go @@ -0,0 +1,159 @@ +// Copyright 2011 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. + +package html + +import ( + "bufio" + "fmt" + "io" + "os" +) + +type writer interface { + io.Writer + WriteByte(byte) os.Error + WriteString(string) (int, os.Error) +} + +// Render renders the parse tree n to the given writer. +// +// For 'well-formed' parse trees, calling Parse on the output of Render will +// result in a clone of the original tree. +// +// 'Well-formed' is not formally specified, but calling Parse on arbitrary +// input results in a 'well-formed' parse tree if Parse does not return an +// error. Programmatically constructed trees are typically also 'well-formed', +// but it is possible to construct a tree that, when rendered and re-parsed, +// results in a different tree. A simple example is that a solitary text node +// would become a tree containing , and elements. Another +// example is that the programmatic equivalent of "abc" becomes +// "abc". +// +// Comment nodes are elided from the output, analogous to Parse skipping over +// any input. +func Render(w io.Writer, n *Node) os.Error { + if x, ok := w.(writer); ok { + return render(x, n) + } + buf := bufio.NewWriter(w) + if err := render(buf, n); err != nil { + return err + } + return buf.Flush() +} + +func render(w writer, n *Node) os.Error { + // Render non-element nodes; these are the easy cases. + switch n.Type { + case ErrorNode: + return os.NewError("html: cannot render an ErrorNode node") + case TextNode: + return escape(w, n.Data) + case DocumentNode: + for _, c := range n.Child { + if err := render(w, c); err != nil { + return err + } + } + return nil + case ElementNode: + // No-op. + case CommentNode: + return nil + case DoctypeNode: + if _, err := w.WriteString("') + default: + return os.NewError("html: unknown node type") + } + + // TODO: figure out what to do with