| // Copyright 2022 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 comment |
| |
| import ( |
| "bytes" |
| "fmt" |
| "strconv" |
| ) |
| |
| // An htmlPrinter holds the state needed for printing a Doc as HTML. |
| type htmlPrinter struct { |
| *Printer |
| tight bool |
| } |
| |
| // HTML returns an HTML formatting of the Doc. |
| // See the [Printer] documentation for ways to customize the HTML output. |
| func (p *Printer) HTML(d *Doc) []byte { |
| hp := &htmlPrinter{Printer: p} |
| var out bytes.Buffer |
| for _, x := range d.Content { |
| hp.block(&out, x) |
| } |
| return out.Bytes() |
| } |
| |
| // block prints the block x to out. |
| func (p *htmlPrinter) block(out *bytes.Buffer, x Block) { |
| switch x := x.(type) { |
| default: |
| fmt.Fprintf(out, "?%T", x) |
| |
| case *Paragraph: |
| if !p.tight { |
| out.WriteString("<p>") |
| } |
| p.text(out, x.Text) |
| out.WriteString("\n") |
| |
| case *Heading: |
| out.WriteString("<h") |
| h := strconv.Itoa(p.headingLevel()) |
| out.WriteString(h) |
| if id := p.headingID(x); id != "" { |
| out.WriteString(` id="`) |
| p.escape(out, id) |
| out.WriteString(`"`) |
| } |
| out.WriteString(">") |
| p.text(out, x.Text) |
| out.WriteString("</h") |
| out.WriteString(h) |
| out.WriteString(">\n") |
| |
| case *Code: |
| out.WriteString("<pre>") |
| p.escape(out, x.Text) |
| out.WriteString("</pre>\n") |
| |
| case *List: |
| kind := "ol>\n" |
| if x.Items[0].Number == "" { |
| kind = "ul>\n" |
| } |
| out.WriteString("<") |
| out.WriteString(kind) |
| next := "1" |
| for _, item := range x.Items { |
| out.WriteString("<li") |
| if n := item.Number; n != "" { |
| if n != next { |
| out.WriteString(` value="`) |
| out.WriteString(n) |
| out.WriteString(`"`) |
| next = n |
| } |
| next = inc(next) |
| } |
| out.WriteString(">") |
| p.tight = !x.BlankBetween() |
| for _, blk := range item.Content { |
| p.block(out, blk) |
| } |
| p.tight = false |
| } |
| out.WriteString("</") |
| out.WriteString(kind) |
| } |
| } |
| |
| // inc increments the decimal string s. |
| // For example, inc("1199") == "1200". |
| func inc(s string) string { |
| b := []byte(s) |
| for i := len(b) - 1; i >= 0; i-- { |
| if b[i] < '9' { |
| b[i]++ |
| return string(b) |
| } |
| b[i] = '0' |
| } |
| return "1" + string(b) |
| } |
| |
| // text prints the text sequence x to out. |
| func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) { |
| for _, t := range x { |
| switch t := t.(type) { |
| case Plain: |
| p.escape(out, string(t)) |
| case Italic: |
| out.WriteString("<i>") |
| p.escape(out, string(t)) |
| out.WriteString("</i>") |
| case *Link: |
| out.WriteString(`<a href="`) |
| p.escape(out, t.URL) |
| out.WriteString(`">`) |
| p.text(out, t.Text) |
| out.WriteString("</a>") |
| case *DocLink: |
| url := p.docLinkURL(t) |
| if url != "" { |
| out.WriteString(`<a href="`) |
| p.escape(out, url) |
| out.WriteString(`">`) |
| } |
| p.text(out, t.Text) |
| if url != "" { |
| out.WriteString("</a>") |
| } |
| } |
| } |
| } |
| |
| // escape prints s to out as plain text, |
| // escaping < & " ' and > to avoid being misinterpreted |
| // in larger HTML constructs. |
| func (p *htmlPrinter) escape(out *bytes.Buffer, s string) { |
| start := 0 |
| for i := 0; i < len(s); i++ { |
| switch s[i] { |
| case '<': |
| out.WriteString(s[start:i]) |
| out.WriteString("<") |
| start = i + 1 |
| case '&': |
| out.WriteString(s[start:i]) |
| out.WriteString("&") |
| start = i + 1 |
| case '"': |
| out.WriteString(s[start:i]) |
| out.WriteString(""") |
| start = i + 1 |
| case '\'': |
| out.WriteString(s[start:i]) |
| out.WriteString("'") |
| start = i + 1 |
| case '>': |
| out.WriteString(s[start:i]) |
| out.WriteString(">") |
| start = i + 1 |
| } |
| } |
| out.WriteString(s[start:]) |
| } |