// Copyright 2015 The 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 markdown import ( "io" "regexp" "strconv" "strings" "gitlab.com/golang-commonmark/html" ) type Renderer struct { w *monadicWriter } func NewRenderer(w io.Writer) *Renderer { return &Renderer{newMonadicWriter(w)} } func (r *Renderer) Render(tokens []Token, options RenderOptions) error { for i, tok := range tokens { if tok, ok := tok.(*Inline); ok { r.renderInline(tok.Children, options) } else { r.renderToken(tokens, i, options) } } r.w.Flush() return r.w.err } func (r *Renderer) renderInline(tokens []Token, o RenderOptions) { for i := range tokens { r.renderToken(tokens, i, o) } } func (r *Renderer) renderInlineAsText(tokens []Token) { for _, tok := range tokens { if text, ok := tok.(*Text); ok { html.WriteEscapedString(r.w, text.Content) } else if img, ok := tok.(*Image); ok { r.renderInlineAsText(img.Tokens) } } } var rNotSpace = regexp.MustCompile(`^\S+`) func (r *Renderer) renderToken(tokens []Token, idx int, options RenderOptions) { tok := tokens[idx] if idx > 0 && tok.Block() && !tok.Closing() { switch t := tokens[idx-1].(type) { case *ParagraphOpen: if t.Hidden { r.w.WriteByte('\n') } case *ParagraphClose: if t.Hidden { r.w.WriteByte('\n') } } } switch tok := tok.(type) { case *BlockquoteClose: r.w.WriteString("") case *BlockquoteOpen: r.w.WriteString("
") case *BulletListClose: r.w.WriteString("") case *BulletListOpen: r.w.WriteString("") case *CodeBlock: r.w.WriteString("
") case *CodeInline: r.w.WriteString("") html.WriteEscapedString(r.w, tok.Content) r.w.WriteString("
") html.WriteEscapedString(r.w, tok.Content) r.w.WriteString("
") case *EmphasisClose: r.w.WriteString("") case *EmphasisOpen: r.w.WriteString("") case *Fence: r.w.WriteString("") case *Hardbreak: if options.XHTML { r.w.WriteString("') html.WriteEscapedString(r.w, tok.Content) r.w.WriteString("
\n") } else { r.w.WriteString("
\n") } case *HeadingClose: r.w.WriteString("") case *HeadingOpen: r.w.WriteString("') case *Hr: if options.XHTML { r.w.WriteString("
") } else { r.w.WriteString("
") } case *HTMLBlock: r.w.WriteString(tok.Content) return // no newline case *HTMLInline: r.w.WriteString(tok.Content) case *Image: r.w.WriteString(`") } else { r.w.WriteByte('>') } case *LinkClose: r.w.WriteString("") case *LinkOpen: r.w.WriteString(`') case *ListItemClose: r.w.WriteString("") case *ListItemOpen: r.w.WriteString("") case *OrderedListClose: r.w.WriteString("") case *OrderedListOpen: if tok.Order != 1 { r.w.WriteString(` `) } else { r.w.WriteString("
") } case *ParagraphClose: if tok.Hidden { return } if !tok.Tight { r.w.WriteString("") } else if tokens[idx+1].Closing() { return // no newline } case *ParagraphOpen: if tok.Hidden { return } if !tok.Tight { r.w.WriteString("
") } case *Softbreak: if options.Breaks { if options.XHTML { r.w.WriteString("
\n") } else { r.w.WriteString("
\n") } } else { r.w.WriteByte('\n') } return case *StrongClose: r.w.WriteString("") case *StrongOpen: r.w.WriteString("") case *StrikethroughClose: r.w.WriteString("") case *StrikethroughOpen: r.w.WriteString("") case *TableClose: r.w.WriteString("") case *TableOpen: r.w.WriteString("") case *TbodyClose: r.w.WriteString("") case *TbodyOpen: r.w.WriteString("") case *TdClose: r.w.WriteString("") case *TdOpen: if tok.Align != AlignNone { r.w.WriteString(`
`) } else { r.w.WriteString(" ") } case *Text: html.WriteEscapedString(r.w, tok.Content) case *TheadClose: r.w.WriteString("") case *TheadOpen: r.w.WriteString("") case *ThClose: r.w.WriteString("") case *ThOpen: if align := tok.Align; align != AlignNone { r.w.WriteString(` `) } else { r.w.WriteString(" ") } case *TrClose: r.w.WriteString("") case *TrOpen: r.w.WriteString(" ") default: panic("unknown token type") } needLf := false if tok.Block() { needLf = true if tok.Opening() { nextTok := tokens[idx+1] blockquote := false switch nextTok := nextTok.(type) { case *Inline: needLf = false case *ParagraphOpen: if nextTok.Tight || nextTok.Hidden { needLf = false } case *ParagraphClose: if nextTok.Tight || nextTok.Hidden { needLf = false } case *BlockquoteClose: blockquote = true } if !blockquote && needLf && nextTok.Closing() && nextTok.Tag() == tok.Tag() { needLf = false } } } if needLf { r.w.WriteByte('\n') } }