// 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 htmlimport ()type writer interface {io.Writerio.ByteWriter WriteString(string) (int, error)}// Render renders the parse tree n to the given writer.//// Rendering is done on a 'best effort' basis: calling Parse on the output of// Render will always result in something similar to the original tree, but it// is not necessarily an exact clone unless the original tree was 'well-formed'.// 'Well-formed' is not easily specified; the HTML5 specification is// complicated.//// Calling Parse on arbitrary input typically results in a 'well-formed' parse// tree. However, it is possible for Parse to yield a 'badly-formed' parse tree.// For example, in a 'well-formed' parse tree, no <a> element is a child of// another <a> element: parsing "<a><a>" results in two sibling elements.// Similarly, in a 'well-formed' parse tree, no <a> element is a child of a// <table> element: parsing "<p><table><a>" results in a <p> with two sibling// children; the <a> is reparented to the <table>'s parent. However, calling// Parse on "<a><table><a>" does not return an error, but the result has an <a>// element with an <a> child, and is therefore not 'well-formed'.//// Programmatically constructed trees are typically also 'well-formed', but it// is possible to construct a tree that looks innocuous but, when rendered and// re-parsed, results in a different tree. A simple example is that a solitary// text node would become a tree containing <html>, <head> and <body> elements.// Another example is that the programmatic equivalent of "a<head>b</head>c"// becomes "<html><head><head/><body>abc</body></html>".func ( io.Writer, *Node) error {if , := .(writer); {returnrender(, ) } := bufio.NewWriter()if := render(, ); != nil {return }return .Flush()}// plaintextAbort is returned from render1 when a <plaintext> element// has been rendered. No more end tags should be rendered after that.var plaintextAbort = errors.New("html: internal error (plaintext abort)")func render( writer, *Node) error { := render1(, )if == plaintextAbort { = nil }return}func render1( writer, *Node) error {// Render non-element nodes; these are the easy cases.switch .Type {caseErrorNode:returnerrors.New("html: cannot render an ErrorNode node")caseTextNode:returnescape(, .Data)caseDocumentNode:for := .FirstChild; != nil; = .NextSibling {if := (, ); != nil {return } }returnnilcaseElementNode:// No-op.caseCommentNode:if , := .WriteString("<!--"); != nil {return }if := escapeComment(, .Data); != nil {return }if , := .WriteString("-->"); != nil {return }returnnilcaseDoctypeNode:if , := .WriteString("<!DOCTYPE "); != nil {return }if := escape(, .Data); != nil {return }if .Attr != nil {var , stringfor , := range .Attr {switch .Key {case"public": = .Valcase"system": = .Val } }if != "" {if , := .WriteString(" PUBLIC "); != nil {return }if := writeQuoted(, ); != nil {return }if != "" {if := .WriteByte(' '); != nil {return }if := writeQuoted(, ); != nil {return } } } elseif != "" {if , := .WriteString(" SYSTEM "); != nil {return }if := writeQuoted(, ); != nil {return } } }return .WriteByte('>')caseRawNode: , := .WriteString(.Data)returndefault:returnerrors.New("html: unknown node type") }// Render the <xxx> opening tag.if := .WriteByte('<'); != nil {return }if , := .WriteString(.Data); != nil {return }for , := range .Attr {if := .WriteByte(' '); != nil {return }if .Namespace != "" {if , := .WriteString(.Namespace); != nil {return }if := .WriteByte(':'); != nil {return } }if , := .WriteString(.Key); != nil {return }if , := .WriteString(`="`); != nil {return }if := escape(, .Val); != nil {return }if := .WriteByte('"'); != nil {return } }ifvoidElements[.Data] {if .FirstChild != nil {returnfmt.Errorf("html: void element <%s> has child nodes", .Data) } , := .WriteString("/>")return }if := .WriteByte('>'); != nil {return }// Add initial newline where there is danger of a newline beging ignored.if := .FirstChild; != nil && .Type == TextNode && strings.HasPrefix(.Data, "\n") {switch .Data {case"pre", "listing", "textarea":if := .WriteByte('\n'); != nil {return } } }// Render any child nodesifchildTextNodesAreLiteral() {for := .FirstChild; != nil; = .NextSibling {if .Type == TextNode {if , := .WriteString(.Data); != nil {return } } else {if := (, ); != nil {return } } }if .Data == "plaintext" {// Don't render anything else. <plaintext> must be the // last element in the file, with no closing tag.returnplaintextAbort } } else {for := .FirstChild; != nil; = .NextSibling {if := (, ); != nil {return } } }// Render the </xxx> closing tag.if , := .WriteString("</"); != nil {return }if , := .WriteString(.Data); != nil {return }return .WriteByte('>')}func childTextNodesAreLiteral( *Node) bool {// Per WHATWG HTML 13.3, if the parent of the current node is a style, // script, xmp, iframe, noembed, noframes, or plaintext element, and the // current node is a text node, append the value of the node's data // literally. The specification is not explicit about it, but we only // enforce this if we are in the HTML namespace (i.e. when the namespace is // ""). // NOTE: we also always include noscript elements, although the // specification states that they should only be rendered as such if // scripting is enabled for the node (which is not something we track).if .Namespace != "" {returnfalse }switch .Data {case"iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":returntruedefault:returnfalse }}// writeQuoted writes s to w surrounded by quotes. Normally it will use double// quotes, but if s contains a double quote, it will use single quotes.// It is used for writing the identifiers in a doctype declaration.// In valid HTML, they can't contain both types of quotes.func writeQuoted( writer, string) error {varbyte = '"'ifstrings.Contains(, `"`) { = '\'' }if := .WriteByte(); != nil {return }if , := .WriteString(); != nil {return }if := .WriteByte(); != nil {return }returnnil}// Section 12.1.2, "Elements", gives this list of void elements. Void elements// are those that can't have any contents.var voidElements = map[string]bool{"area": true,"base": true,"br": true,"col": true,"embed": true,"hr": true,"img": true,"input": true,"keygen": true, // "keygen" has been removed from the spec, but are kept here for backwards compatibility."link": true,"meta": true,"param": true,"source": true,"track": true,"wbr": true,}
The pages are generated with Goldsv0.6.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds.