123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- package render
- import (
- "golang.org/x/net/html"
- "fmt"
- "mimicry/style"
- "errors"
- "strings"
- "regexp"
- "golang.org/x/net/html/atom"
- )
- func renderHTML(markup string) (string, error) {
-
- markup = strings.ReplaceAll(markup, "\u001b", "␛")
- nodes, err := html.ParseFragment(strings.NewReader(markup), &html.Node{
- Type: html.ElementNode,
- Data: "body",
- DataAtom: atom.Body,
- })
- if err != nil {
- return "", err
- }
- serialized, err := SerializeList(nodes)
- if err != nil {
- return "", err
- }
-
- manyNewlines := regexp.MustCompile(`\n{2,}`)
- serialized = manyNewlines.ReplaceAllString(serialized, "\n\n")
- serialized = strings.Trim(serialized, "\n")
- return serialized, nil
- }
- func renderNode(node *html.Node, preserveWhitespace bool) (string, error) {
- if node.Type == html.TextNode {
- if !preserveWhitespace {
- whitespace := regexp.MustCompile(`[\t ]+`)
- newline := regexp.MustCompile(`[\n\t ]*\n[n\t ]*`)
- processed := newline.ReplaceAllString(node.Data, "\n")
- processed = whitespace.ReplaceAllString(processed, " ")
- return processed, nil
- }
- return node.Data, nil
- }
- if node.Type != html.ElementNode {
- return "", nil
- }
-
-
- content := serializeChildren(node, preserveWhitespace)
- switch node.Data {
- case "a":
- return style.Linkify(content), nil
- case "s", "del":
- return style.Strikethrough(content), nil
- case "code":
- return style.Code(content), nil
- case "i", "em":
- return style.Italic(content), nil
- case "b", "strong":
- return style.Bold(content), nil
- case "u":
- return style.Underline(content), nil
- case "mark":
- return style.Highlight(content), nil
- case "span", "li":
- return content, nil
- case "br":
- return "\n", nil
- case "p", "div":
- return block(content), nil
- case "pre":
- return block(style.CodeBlock(content)), nil
- case "blockquote":
-
- return block(blockquote(content)), nil
- case "ul":
- return block(bulletedList(node, preserveWhitespace)), nil
-
-
- }
- return "", errors.New("Encountered unrecognized element " + node.Data)
- }
- func serializeChildren(node *html.Node, preserveWhitespace bool) (string) {
- output := ""
- for current := node.FirstChild; current != nil; current = current.NextSibling {
- result, _ := renderNode(current, preserveWhitespace)
-
-
-
- output += result
- }
- return output
- }
- func SerializeList(nodes []*html.Node) (string, error) {
- output := ""
- for _, current := range nodes {
- result, err := renderNode(current, false)
- if err != nil {
- return "", err
- }
- output += result
- }
- return output, nil
- }
- func block(text string) string {
- return fmt.Sprintf("\n\n%s\n\n", text)
- }
- func blockquote(text string) string {
- withBar := fmt.Sprintf("▌%s", strings.ReplaceAll(text, "\n", "\n▌"))
- withColor := style.Color(withBar)
- return withColor
- }
- func bulletedList(node *html.Node, preserveWhitespace bool) string {
- output := ""
- for current := node.FirstChild; current != nil; current = current.NextSibling {
- if current.Type != html.ElementNode {
- continue
- }
- if current.Data != "li" {
- continue
- }
- result, _ := renderNode(current, preserveWhitespace)
- output += fmt.Sprintf("• %s", strings.ReplaceAll(result, "\n", "\n "))
- }
- return output
- }
|