Source file src/go/doc/comment/html.go

     1  // Copyright 2022 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 comment
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"strconv"
    11  )
    12  
    13  // An htmlPrinter holds the state needed for printing a [Doc] as HTML.
    14  type htmlPrinter struct {
    15  	*Printer
    16  	tight bool
    17  }
    18  
    19  // HTML returns an HTML formatting of the [Doc].
    20  // See the [Printer] documentation for ways to customize the HTML output.
    21  func (p *Printer) HTML(d *Doc) []byte {
    22  	hp := &htmlPrinter{Printer: p}
    23  	var out bytes.Buffer
    24  	for _, x := range d.Content {
    25  		hp.block(&out, x)
    26  	}
    27  	return out.Bytes()
    28  }
    29  
    30  // block prints the block x to out.
    31  func (p *htmlPrinter) block(out *bytes.Buffer, x Block) {
    32  	switch x := x.(type) {
    33  	default:
    34  		fmt.Fprintf(out, "?%T", x)
    35  
    36  	case *Paragraph:
    37  		if !p.tight {
    38  			out.WriteString("<p>")
    39  		}
    40  		p.text(out, x.Text)
    41  		out.WriteString("\n")
    42  
    43  	case *Heading:
    44  		out.WriteString("<h")
    45  		h := strconv.Itoa(p.headingLevel())
    46  		out.WriteString(h)
    47  		if id := p.headingID(x); id != "" {
    48  			out.WriteString(` id="`)
    49  			p.escape(out, id)
    50  			out.WriteString(`"`)
    51  		}
    52  		out.WriteString(">")
    53  		p.text(out, x.Text)
    54  		out.WriteString("</h")
    55  		out.WriteString(h)
    56  		out.WriteString(">\n")
    57  
    58  	case *Code:
    59  		out.WriteString("<pre>")
    60  		p.escape(out, x.Text)
    61  		out.WriteString("</pre>\n")
    62  
    63  	case *List:
    64  		kind := "ol>\n"
    65  		if x.Items[0].Number == "" {
    66  			kind = "ul>\n"
    67  		}
    68  		out.WriteString("<")
    69  		out.WriteString(kind)
    70  		next := "1"
    71  		for _, item := range x.Items {
    72  			out.WriteString("<li")
    73  			if n := item.Number; n != "" {
    74  				if n != next {
    75  					out.WriteString(` value="`)
    76  					out.WriteString(n)
    77  					out.WriteString(`"`)
    78  					next = n
    79  				}
    80  				next = inc(next)
    81  			}
    82  			out.WriteString(">")
    83  			p.tight = !x.BlankBetween()
    84  			for _, blk := range item.Content {
    85  				p.block(out, blk)
    86  			}
    87  			p.tight = false
    88  		}
    89  		out.WriteString("</")
    90  		out.WriteString(kind)
    91  	}
    92  }
    93  
    94  // inc increments the decimal string s.
    95  // For example, inc("1199") == "1200".
    96  func inc(s string) string {
    97  	b := []byte(s)
    98  	for i := len(b) - 1; i >= 0; i-- {
    99  		if b[i] < '9' {
   100  			b[i]++
   101  			return string(b)
   102  		}
   103  		b[i] = '0'
   104  	}
   105  	return "1" + string(b)
   106  }
   107  
   108  // text prints the text sequence x to out.
   109  func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) {
   110  	for _, t := range x {
   111  		switch t := t.(type) {
   112  		case Plain:
   113  			p.escape(out, string(t))
   114  		case Italic:
   115  			out.WriteString("<i>")
   116  			p.escape(out, string(t))
   117  			out.WriteString("</i>")
   118  		case *Link:
   119  			out.WriteString(`<a href="`)
   120  			p.escape(out, t.URL)
   121  			out.WriteString(`">`)
   122  			p.text(out, t.Text)
   123  			out.WriteString("</a>")
   124  		case *DocLink:
   125  			url := p.docLinkURL(t)
   126  			if url != "" {
   127  				out.WriteString(`<a href="`)
   128  				p.escape(out, url)
   129  				out.WriteString(`">`)
   130  			}
   131  			p.text(out, t.Text)
   132  			if url != "" {
   133  				out.WriteString("</a>")
   134  			}
   135  		}
   136  	}
   137  }
   138  
   139  // escape prints s to out as plain text,
   140  // escaping < & " ' and > to avoid being misinterpreted
   141  // in larger HTML constructs.
   142  func (p *htmlPrinter) escape(out *bytes.Buffer, s string) {
   143  	start := 0
   144  	for i := 0; i < len(s); i++ {
   145  		switch s[i] {
   146  		case '<':
   147  			out.WriteString(s[start:i])
   148  			out.WriteString("&lt;")
   149  			start = i + 1
   150  		case '&':
   151  			out.WriteString(s[start:i])
   152  			out.WriteString("&amp;")
   153  			start = i + 1
   154  		case '"':
   155  			out.WriteString(s[start:i])
   156  			out.WriteString("&quot;")
   157  			start = i + 1
   158  		case '\'':
   159  			out.WriteString(s[start:i])
   160  			out.WriteString("&apos;")
   161  			start = i + 1
   162  		case '>':
   163  			out.WriteString(s[start:i])
   164  			out.WriteString("&gt;")
   165  			start = i + 1
   166  		}
   167  	}
   168  	out.WriteString(s[start:])
   169  }
   170  

View as plain text