mirror of
https://github.com/cwinfo/matterbridge.git
synced 2024-11-22 23:00:28 +00:00
04567c765e
This uses our own gomatrix lib with the SendHTML function which adds HTML to formatted_body in matrix. golang-commonmark is used to convert markdown into valid HTML.
334 lines
6.3 KiB
Go
334 lines
6.3 KiB
Go
// 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("</blockquote>")
|
|
|
|
case *BlockquoteOpen:
|
|
r.w.WriteString("<blockquote>")
|
|
|
|
case *BulletListClose:
|
|
r.w.WriteString("</ul>")
|
|
|
|
case *BulletListOpen:
|
|
r.w.WriteString("<ul>")
|
|
|
|
case *CodeBlock:
|
|
r.w.WriteString("<pre><code>")
|
|
html.WriteEscapedString(r.w, tok.Content)
|
|
r.w.WriteString("</code></pre>")
|
|
|
|
case *CodeInline:
|
|
r.w.WriteString("<code>")
|
|
html.WriteEscapedString(r.w, tok.Content)
|
|
r.w.WriteString("</code>")
|
|
|
|
case *EmphasisClose:
|
|
r.w.WriteString("</em>")
|
|
|
|
case *EmphasisOpen:
|
|
r.w.WriteString("<em>")
|
|
|
|
case *Fence:
|
|
r.w.WriteString("<pre><code")
|
|
if tok.Params != "" {
|
|
langName := strings.SplitN(unescapeAll(tok.Params), " ", 2)[0]
|
|
langName = rNotSpace.FindString(langName)
|
|
if langName != "" {
|
|
r.w.WriteString(` class="`)
|
|
r.w.WriteString(options.LangPrefix)
|
|
html.WriteEscapedString(r.w, langName)
|
|
r.w.WriteByte('"')
|
|
}
|
|
}
|
|
r.w.WriteByte('>')
|
|
html.WriteEscapedString(r.w, tok.Content)
|
|
r.w.WriteString("</code></pre>")
|
|
|
|
case *Hardbreak:
|
|
if options.XHTML {
|
|
r.w.WriteString("<br />\n")
|
|
} else {
|
|
r.w.WriteString("<br>\n")
|
|
}
|
|
|
|
case *HeadingClose:
|
|
r.w.WriteString("</h")
|
|
r.w.WriteByte("0123456789"[tok.HLevel])
|
|
r.w.WriteString(">")
|
|
|
|
case *HeadingOpen:
|
|
r.w.WriteString("<h")
|
|
r.w.WriteByte("0123456789"[tok.HLevel])
|
|
r.w.WriteByte('>')
|
|
|
|
case *Hr:
|
|
if options.XHTML {
|
|
r.w.WriteString("<hr />")
|
|
} else {
|
|
r.w.WriteString("<hr>")
|
|
}
|
|
|
|
case *HTMLBlock:
|
|
r.w.WriteString(tok.Content)
|
|
return // no newline
|
|
|
|
case *HTMLInline:
|
|
r.w.WriteString(tok.Content)
|
|
|
|
case *Image:
|
|
r.w.WriteString(`<img src="`)
|
|
html.WriteEscapedString(r.w, tok.Src)
|
|
r.w.WriteString(`" alt="`)
|
|
r.renderInlineAsText(tok.Tokens)
|
|
r.w.WriteByte('"')
|
|
|
|
if tok.Title != "" {
|
|
r.w.WriteString(` title="`)
|
|
html.WriteEscapedString(r.w, tok.Title)
|
|
r.w.WriteByte('"')
|
|
}
|
|
if options.XHTML {
|
|
r.w.WriteString(" />")
|
|
} else {
|
|
r.w.WriteByte('>')
|
|
}
|
|
|
|
case *LinkClose:
|
|
r.w.WriteString("</a>")
|
|
|
|
case *LinkOpen:
|
|
r.w.WriteString(`<a href="`)
|
|
html.WriteEscapedString(r.w, tok.Href)
|
|
r.w.WriteByte('"')
|
|
if tok.Title != "" {
|
|
r.w.WriteString(` title="`)
|
|
html.WriteEscapedString(r.w, (tok.Title))
|
|
r.w.WriteByte('"')
|
|
}
|
|
if tok.Target != "" {
|
|
r.w.WriteString(` target="`)
|
|
html.WriteEscapedString(r.w, tok.Target)
|
|
r.w.WriteByte('"')
|
|
}
|
|
if options.Nofollow {
|
|
r.w.WriteString(` rel="nofollow"`)
|
|
}
|
|
r.w.WriteByte('>')
|
|
|
|
case *ListItemClose:
|
|
r.w.WriteString("</li>")
|
|
|
|
case *ListItemOpen:
|
|
r.w.WriteString("<li>")
|
|
|
|
case *OrderedListClose:
|
|
r.w.WriteString("</ol>")
|
|
|
|
case *OrderedListOpen:
|
|
if tok.Order != 1 {
|
|
r.w.WriteString(`<ol start="`)
|
|
r.w.WriteString(strconv.Itoa(tok.Order))
|
|
r.w.WriteString(`">`)
|
|
} else {
|
|
r.w.WriteString("<ol>")
|
|
}
|
|
|
|
case *ParagraphClose:
|
|
if tok.Hidden {
|
|
return
|
|
}
|
|
if !tok.Tight {
|
|
r.w.WriteString("</p>")
|
|
} else if tokens[idx+1].Closing() {
|
|
return // no newline
|
|
}
|
|
|
|
case *ParagraphOpen:
|
|
if tok.Hidden {
|
|
return
|
|
}
|
|
if !tok.Tight {
|
|
r.w.WriteString("<p>")
|
|
}
|
|
|
|
case *Softbreak:
|
|
if options.Breaks {
|
|
if options.XHTML {
|
|
r.w.WriteString("<br />\n")
|
|
} else {
|
|
r.w.WriteString("<br>\n")
|
|
}
|
|
} else {
|
|
r.w.WriteByte('\n')
|
|
}
|
|
return
|
|
|
|
case *StrongClose:
|
|
r.w.WriteString("</strong>")
|
|
|
|
case *StrongOpen:
|
|
r.w.WriteString("<strong>")
|
|
|
|
case *StrikethroughClose:
|
|
r.w.WriteString("</s>")
|
|
|
|
case *StrikethroughOpen:
|
|
r.w.WriteString("<s>")
|
|
|
|
case *TableClose:
|
|
r.w.WriteString("</table>")
|
|
|
|
case *TableOpen:
|
|
r.w.WriteString("<table>")
|
|
|
|
case *TbodyClose:
|
|
r.w.WriteString("</tbody>")
|
|
|
|
case *TbodyOpen:
|
|
r.w.WriteString("<tbody>")
|
|
|
|
case *TdClose:
|
|
r.w.WriteString("</td>")
|
|
|
|
case *TdOpen:
|
|
if tok.Align != AlignNone {
|
|
r.w.WriteString(`<td style="text-align:`)
|
|
r.w.WriteString(tok.Align.String())
|
|
r.w.WriteString(`">`)
|
|
} else {
|
|
r.w.WriteString("<td>")
|
|
}
|
|
|
|
case *Text:
|
|
html.WriteEscapedString(r.w, tok.Content)
|
|
|
|
case *TheadClose:
|
|
r.w.WriteString("</thead>")
|
|
|
|
case *TheadOpen:
|
|
r.w.WriteString("<thead>")
|
|
|
|
case *ThClose:
|
|
r.w.WriteString("</th>")
|
|
|
|
case *ThOpen:
|
|
if align := tok.Align; align != AlignNone {
|
|
r.w.WriteString(`<th style="text-align:`)
|
|
r.w.WriteString(align.String())
|
|
r.w.WriteString(`">`)
|
|
} else {
|
|
r.w.WriteString("<th>")
|
|
}
|
|
|
|
case *TrClose:
|
|
r.w.WriteString("</tr>")
|
|
|
|
case *TrOpen:
|
|
r.w.WriteString("<tr>")
|
|
|
|
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')
|
|
}
|
|
}
|