Path: blob/main/vendor/golang.org/x/net/html/render.go
2880 views
// Copyright 2011 The Go Authors. All rights reserved.1// Use of this source code is governed by a BSD-style2// license that can be found in the LICENSE file.34package html56import (7"bufio"8"errors"9"fmt"10"io"11"strings"12)1314type writer interface {15io.Writer16io.ByteWriter17WriteString(string) (int, error)18}1920// Render renders the parse tree n to the given writer.21//22// Rendering is done on a 'best effort' basis: calling Parse on the output of23// Render will always result in something similar to the original tree, but it24// is not necessarily an exact clone unless the original tree was 'well-formed'.25// 'Well-formed' is not easily specified; the HTML5 specification is26// complicated.27//28// Calling Parse on arbitrary input typically results in a 'well-formed' parse29// tree. However, it is possible for Parse to yield a 'badly-formed' parse tree.30// For example, in a 'well-formed' parse tree, no <a> element is a child of31// another <a> element: parsing "<a><a>" results in two sibling elements.32// Similarly, in a 'well-formed' parse tree, no <a> element is a child of a33// <table> element: parsing "<p><table><a>" results in a <p> with two sibling34// children; the <a> is reparented to the <table>'s parent. However, calling35// Parse on "<a><table><a>" does not return an error, but the result has an <a>36// element with an <a> child, and is therefore not 'well-formed'.37//38// Programmatically constructed trees are typically also 'well-formed', but it39// is possible to construct a tree that looks innocuous but, when rendered and40// re-parsed, results in a different tree. A simple example is that a solitary41// text node would become a tree containing <html>, <head> and <body> elements.42// Another example is that the programmatic equivalent of "a<head>b</head>c"43// becomes "<html><head><head/><body>abc</body></html>".44func Render(w io.Writer, n *Node) error {45if x, ok := w.(writer); ok {46return render(x, n)47}48buf := bufio.NewWriter(w)49if err := render(buf, n); err != nil {50return err51}52return buf.Flush()53}5455// plaintextAbort is returned from render1 when a <plaintext> element56// has been rendered. No more end tags should be rendered after that.57var plaintextAbort = errors.New("html: internal error (plaintext abort)")5859func render(w writer, n *Node) error {60err := render1(w, n)61if err == plaintextAbort {62err = nil63}64return err65}6667func render1(w writer, n *Node) error {68// Render non-element nodes; these are the easy cases.69switch n.Type {70case ErrorNode:71return errors.New("html: cannot render an ErrorNode node")72case TextNode:73return escape(w, n.Data)74case DocumentNode:75for c := n.FirstChild; c != nil; c = c.NextSibling {76if err := render1(w, c); err != nil {77return err78}79}80return nil81case ElementNode:82// No-op.83case CommentNode:84if _, err := w.WriteString("<!--"); err != nil {85return err86}87if err := escapeComment(w, n.Data); err != nil {88return err89}90if _, err := w.WriteString("-->"); err != nil {91return err92}93return nil94case DoctypeNode:95if _, err := w.WriteString("<!DOCTYPE "); err != nil {96return err97}98if err := escape(w, n.Data); err != nil {99return err100}101if n.Attr != nil {102var p, s string103for _, a := range n.Attr {104switch a.Key {105case "public":106p = a.Val107case "system":108s = a.Val109}110}111if p != "" {112if _, err := w.WriteString(" PUBLIC "); err != nil {113return err114}115if err := writeQuoted(w, p); err != nil {116return err117}118if s != "" {119if err := w.WriteByte(' '); err != nil {120return err121}122if err := writeQuoted(w, s); err != nil {123return err124}125}126} else if s != "" {127if _, err := w.WriteString(" SYSTEM "); err != nil {128return err129}130if err := writeQuoted(w, s); err != nil {131return err132}133}134}135return w.WriteByte('>')136case RawNode:137_, err := w.WriteString(n.Data)138return err139default:140return errors.New("html: unknown node type")141}142143// Render the <xxx> opening tag.144if err := w.WriteByte('<'); err != nil {145return err146}147if _, err := w.WriteString(n.Data); err != nil {148return err149}150for _, a := range n.Attr {151if err := w.WriteByte(' '); err != nil {152return err153}154if a.Namespace != "" {155if _, err := w.WriteString(a.Namespace); err != nil {156return err157}158if err := w.WriteByte(':'); err != nil {159return err160}161}162if _, err := w.WriteString(a.Key); err != nil {163return err164}165if _, err := w.WriteString(`="`); err != nil {166return err167}168if err := escape(w, a.Val); err != nil {169return err170}171if err := w.WriteByte('"'); err != nil {172return err173}174}175if voidElements[n.Data] {176if n.FirstChild != nil {177return fmt.Errorf("html: void element <%s> has child nodes", n.Data)178}179_, err := w.WriteString("/>")180return err181}182if err := w.WriteByte('>'); err != nil {183return err184}185186// Add initial newline where there is danger of a newline being ignored.187if c := n.FirstChild; c != nil && c.Type == TextNode && strings.HasPrefix(c.Data, "\n") {188switch n.Data {189case "pre", "listing", "textarea":190if err := w.WriteByte('\n'); err != nil {191return err192}193}194}195196// Render any child nodes197if childTextNodesAreLiteral(n) {198for c := n.FirstChild; c != nil; c = c.NextSibling {199if c.Type == TextNode {200if _, err := w.WriteString(c.Data); err != nil {201return err202}203} else {204if err := render1(w, c); err != nil {205return err206}207}208}209if n.Data == "plaintext" {210// Don't render anything else. <plaintext> must be the211// last element in the file, with no closing tag.212return plaintextAbort213}214} else {215for c := n.FirstChild; c != nil; c = c.NextSibling {216if err := render1(w, c); err != nil {217return err218}219}220}221222// Render the </xxx> closing tag.223if _, err := w.WriteString("</"); err != nil {224return err225}226if _, err := w.WriteString(n.Data); err != nil {227return err228}229return w.WriteByte('>')230}231232func childTextNodesAreLiteral(n *Node) bool {233// Per WHATWG HTML 13.3, if the parent of the current node is a style,234// script, xmp, iframe, noembed, noframes, or plaintext element, and the235// current node is a text node, append the value of the node's data236// literally. The specification is not explicit about it, but we only237// enforce this if we are in the HTML namespace (i.e. when the namespace is238// "").239// NOTE: we also always include noscript elements, although the240// specification states that they should only be rendered as such if241// scripting is enabled for the node (which is not something we track).242if n.Namespace != "" {243return false244}245switch n.Data {246case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":247return true248default:249return false250}251}252253// writeQuoted writes s to w surrounded by quotes. Normally it will use double254// quotes, but if s contains a double quote, it will use single quotes.255// It is used for writing the identifiers in a doctype declaration.256// In valid HTML, they can't contain both types of quotes.257func writeQuoted(w writer, s string) error {258var q byte = '"'259if strings.Contains(s, `"`) {260q = '\''261}262if err := w.WriteByte(q); err != nil {263return err264}265if _, err := w.WriteString(s); err != nil {266return err267}268if err := w.WriteByte(q); err != nil {269return err270}271return nil272}273274// Section 12.1.2, "Elements", gives this list of void elements. Void elements275// are those that can't have any contents.276var voidElements = map[string]bool{277"area": true,278"base": true,279"br": true,280"col": true,281"embed": true,282"hr": true,283"img": true,284"input": true,285"keygen": true, // "keygen" has been removed from the spec, but are kept here for backwards compatibility.286"link": true,287"meta": true,288"param": true,289"source": true,290"track": true,291"wbr": true,292}293294295