github.com/vugu/vugu@v0.3.6-0.20240430171613-3f6f402e014b/internal/htmlx/render.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package htmlx
     6  
     7  import (
     8  	"bufio"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"strings"
    13  )
    14  
    15  type writer interface {
    16  	io.Writer
    17  	io.ByteWriter
    18  	WriteString(string) (int, error)
    19  }
    20  
    21  // Render renders the parse tree n to the given writer.
    22  //
    23  // Rendering is done on a 'best effort' basis: calling Parse on the output of
    24  // Render will always result in something similar to the original tree, but it
    25  // is not necessarily an exact clone unless the original tree was 'well-formed'.
    26  // 'Well-formed' is not easily specified; the HTML5 specification is
    27  // complicated.
    28  //
    29  // Calling Parse on arbitrary input typically results in a 'well-formed' parse
    30  // tree. However, it is possible for Parse to yield a 'badly-formed' parse tree.
    31  // For example, in a 'well-formed' parse tree, no <a> element is a child of
    32  // another <a> element: parsing "<a><a>" results in two sibling elements.
    33  // Similarly, in a 'well-formed' parse tree, no <a> element is a child of a
    34  // <table> element: parsing "<p><table><a>" results in a <p> with two sibling
    35  // children; the <a> is reparented to the <table>'s parent. However, calling
    36  // Parse on "<a><table><a>" does not return an error, but the result has an <a>
    37  // element with an <a> child, and is therefore not 'well-formed'.
    38  //
    39  // Programmatically constructed trees are typically also 'well-formed', but it
    40  // is possible to construct a tree that looks innocuous but, when rendered and
    41  // re-parsed, results in a different tree. A simple example is that a solitary
    42  // text node would become a tree containing <html>, <head> and <body> elements.
    43  // Another example is that the programmatic equivalent of "a<head>b</head>c"
    44  // becomes "<html><head><head/><body>abc</body></html>".
    45  func Render(w io.Writer, n *Node) error {
    46  	if x, ok := w.(writer); ok {
    47  		return render(x, n)
    48  	}
    49  	buf := bufio.NewWriter(w)
    50  	if err := render(buf, n); err != nil {
    51  		return err
    52  	}
    53  	return buf.Flush()
    54  }
    55  
    56  // plaintextAbort is returned from render1 when a <plaintext> element
    57  // has been rendered. No more end tags should be rendered after that.
    58  var plaintextAbort = errors.New("html: internal error (plaintext abort)")
    59  
    60  func render(w writer, n *Node) error {
    61  	err := render1(w, n)
    62  	if err == plaintextAbort {
    63  		err = nil
    64  	}
    65  	return err
    66  }
    67  
    68  func render1(w writer, n *Node) error {
    69  	// Render non-element nodes; these are the easy cases.
    70  	switch n.Type {
    71  	case ErrorNode:
    72  		return errors.New("html: cannot render an ErrorNode node")
    73  	case TextNode:
    74  		return escape(w, n.Data)
    75  	case DocumentNode:
    76  		for c := n.FirstChild; c != nil; c = c.NextSibling {
    77  			if err := render1(w, c); err != nil {
    78  				return err
    79  			}
    80  		}
    81  		return nil
    82  	case ElementNode:
    83  		// No-op.
    84  	case CommentNode:
    85  		if _, err := w.WriteString("<!--"); err != nil {
    86  			return err
    87  		}
    88  		if _, err := w.WriteString(n.Data); err != nil {
    89  			return err
    90  		}
    91  		if _, err := w.WriteString("-->"); err != nil {
    92  			return err
    93  		}
    94  		return nil
    95  	case DoctypeNode:
    96  		if _, err := w.WriteString("<!DOCTYPE "); err != nil {
    97  			return err
    98  		}
    99  		if _, err := w.WriteString(n.Data); err != nil {
   100  			return err
   101  		}
   102  		if n.Attr != nil {
   103  			var p, s string
   104  			for _, a := range n.Attr {
   105  				switch a.Key {
   106  				case "public":
   107  					p = a.Val
   108  				case "system":
   109  					s = a.Val
   110  				}
   111  			}
   112  			if p != "" {
   113  				if _, err := w.WriteString(" PUBLIC "); err != nil {
   114  					return err
   115  				}
   116  				if err := writeQuoted(w, p); err != nil {
   117  					return err
   118  				}
   119  				if s != "" {
   120  					if err := w.WriteByte(' '); err != nil {
   121  						return err
   122  					}
   123  					if err := writeQuoted(w, s); err != nil {
   124  						return err
   125  					}
   126  				}
   127  			} else if s != "" {
   128  				if _, err := w.WriteString(" SYSTEM "); err != nil {
   129  					return err
   130  				}
   131  				if err := writeQuoted(w, s); err != nil {
   132  					return err
   133  				}
   134  			}
   135  		}
   136  		return w.WriteByte('>')
   137  	default:
   138  		return errors.New("html: unknown node type")
   139  	}
   140  
   141  	// Render the <xxx> opening tag.
   142  	if err := w.WriteByte('<'); err != nil {
   143  		return err
   144  	}
   145  	if _, err := w.WriteString(n.Data); err != nil {
   146  		return err
   147  	}
   148  	for _, a := range n.Attr {
   149  		if err := w.WriteByte(' '); err != nil {
   150  			return err
   151  		}
   152  		if a.Namespace != "" {
   153  			if _, err := w.WriteString(a.Namespace); err != nil {
   154  				return err
   155  			}
   156  			if err := w.WriteByte(':'); err != nil {
   157  				return err
   158  			}
   159  		}
   160  		if _, err := w.WriteString(a.Key); err != nil {
   161  			return err
   162  		}
   163  		if _, err := w.WriteString(`="`); err != nil {
   164  			return err
   165  		}
   166  		if err := escape(w, a.Val); err != nil {
   167  			return err
   168  		}
   169  		if err := w.WriteByte('"'); err != nil {
   170  			return err
   171  		}
   172  	}
   173  	if voidElements[n.Data] {
   174  		if n.FirstChild != nil {
   175  			return fmt.Errorf("html: void element <%s> has child nodes", n.Data)
   176  		}
   177  		_, err := w.WriteString("/>")
   178  		return err
   179  	}
   180  	if err := w.WriteByte('>'); err != nil {
   181  		return err
   182  	}
   183  
   184  	// Add initial newline where there is danger of a newline beging ignored.
   185  	if c := n.FirstChild; c != nil && c.Type == TextNode && strings.HasPrefix(c.Data, "\n") {
   186  		switch n.Data {
   187  		case "pre", "listing", "textarea":
   188  			if err := w.WriteByte('\n'); err != nil {
   189  				return err
   190  			}
   191  		}
   192  	}
   193  
   194  	// Render any child nodes.
   195  	switch n.Data {
   196  	case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
   197  		for c := n.FirstChild; c != nil; c = c.NextSibling {
   198  			if c.Type == TextNode {
   199  				if _, err := w.WriteString(c.Data); err != nil {
   200  					return err
   201  				}
   202  			} else {
   203  				if err := render1(w, c); err != nil {
   204  					return err
   205  				}
   206  			}
   207  		}
   208  		if n.Data == "plaintext" {
   209  			// Don't render anything else. <plaintext> must be the
   210  			// last element in the file, with no closing tag.
   211  			return plaintextAbort
   212  		}
   213  	default:
   214  		for c := n.FirstChild; c != nil; c = c.NextSibling {
   215  			if err := render1(w, c); err != nil {
   216  				return err
   217  			}
   218  		}
   219  	}
   220  
   221  	// Render the </xxx> closing tag.
   222  	if _, err := w.WriteString("</"); err != nil {
   223  		return err
   224  	}
   225  	if _, err := w.WriteString(n.Data); err != nil {
   226  		return err
   227  	}
   228  	return w.WriteByte('>')
   229  }
   230  
   231  // writeQuoted writes s to w surrounded by quotes. Normally it will use double
   232  // quotes, but if s contains a double quote, it will use single quotes.
   233  // It is used for writing the identifiers in a doctype declaration.
   234  // In valid HTML, they can't contain both types of quotes.
   235  func writeQuoted(w writer, s string) error {
   236  	var q byte = '"'
   237  	if strings.Contains(s, `"`) {
   238  		q = '\''
   239  	}
   240  	if err := w.WriteByte(q); err != nil {
   241  		return err
   242  	}
   243  	if _, err := w.WriteString(s); err != nil {
   244  		return err
   245  	}
   246  	if err := w.WriteByte(q); err != nil {
   247  		return err
   248  	}
   249  	return nil
   250  }
   251  
   252  // Section 12.1.2, "Elements", gives this list of void elements. Void elements
   253  // are those that can't have any contents.
   254  var voidElements = map[string]bool{
   255  	"area":    true,
   256  	"base":    true,
   257  	"br":      true,
   258  	"col":     true,
   259  	"command": true,
   260  	"embed":   true,
   261  	"hr":      true,
   262  	"img":     true,
   263  	"input":   true,
   264  	"keygen":  true,
   265  	"link":    true,
   266  	"meta":    true,
   267  	"param":   true,
   268  	"source":  true,
   269  	"track":   true,
   270  	"wbr":     true,
   271  }