package doc import ( "strings" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" ) var md = goldmark.New(goldmark.WithExtensions(extension.GFM)) // plainText is what a reader sees of a block: link text without the URL, // emphasis without asterisks. Word diffs run on it so a revision reads as // prose, not as Markdown syntax. func plainText(kind, src string) string { switch kind { case KindCode: return codeText(src) case KindBreak: return "" case KindHTML: return collapse(src) case KindTable: return tableText(src) } source := []byte(src) root := md.Parser().Parse(text.NewReader(source)) var b strings.Builder _ = ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { if n.Type() == ast.TypeBlock { b.WriteByte(' ') } return ast.WalkContinue, nil } switch t := n.(type) { case *ast.Text: v := t.Segment.Value(source) if _, inCode := t.Parent().(*ast.CodeSpan); !inCode { v = util.ResolveEntityNames(util.ResolveNumericReferences(util.UnescapePunctuations(v))) } b.Write(v) if t.SoftLineBreak() || t.HardLineBreak() { b.WriteByte(' ') } case *ast.String: b.Write(t.Value) case *ast.AutoLink: b.Write(t.Label(source)) return ast.WalkSkipChildren, nil case *ast.FencedCodeBlock, *ast.CodeBlock, *ast.HTMLBlock: lines := n.Lines() for i := 0; i < lines.Len(); i++ { seg := lines.At(i) b.Write(seg.Value(source)) } return ast.WalkSkipChildren, nil case *ast.RawHTML: return ast.WalkSkipChildren, nil } return ast.WalkContinue, nil }) return collapse(b.String()) } // tableText keeps one line per row and " | " between cells, so a changed // cell is still read in its row. The delimiter row is dropped. func tableText(src string) string { var rows []string for i, line := range strings.Split(src, "\n") { if i == 1 && isDelimiterRow(line) { continue } t := strings.TrimSpace(line) t = strings.TrimSuffix(strings.TrimPrefix(t, "|"), "|") cells := strings.Split(t, "|") for j, c := range cells { cells[j] = plainText(KindParagraph, strings.TrimSpace(c)) } rows = append(rows, strings.Join(cells, " | ")) } return strings.Join(rows, "\n") } // codeText keeps a code block's lines and drops its fences. func codeText(src string) string { lines := strings.Split(src, "\n") if f := fenceMarker(lines[0]); f != "" { lines = lines[1:] if len(lines) > 0 && closesFence(lines[len(lines)-1], f) { lines = lines[:len(lines)-1] } } return strings.Join(lines, "\n") } func collapse(s string) string { return strings.Join(strings.Fields(s), " ") }