summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorLauris BH <lauris@nix.lv>2019-10-31 03:06:25 +0200
committerzeripath <art27@cantab.net>2019-10-31 01:06:25 +0000
commit086a46994a9f59ba06bfacdf2041817ef2f6671c (patch)
tree1a54a4b9f74d2a1c8a3827f24dec2fb77b8a3554 /modules
parent690a8ec502ff2e162b6db9cfe1f0555cf6b37323 (diff)
downloadgitea-086a46994a9f59ba06bfacdf2041817ef2f6671c.tar.gz
gitea-086a46994a9f59ba06bfacdf2041817ef2f6671c.zip
Rewrite markdown rendering to blackfriday v2 and rewrite orgmode rendering to go-org (#8560)
* Rewrite markdown rendering to blackfriday v2.0 * Fix style * Fix go mod with golang 1.13 * Fix blackfriday v2 import * Inital orgmode renderer migration to go-org * Vendor go-org dependency * Ignore errors :/ * Update go-org to latest version * Update test * Fix go-org test * Remove unneeded code * Fix comments * Fix markdown test * Fix blackfriday regression rendering HTML block
Diffstat (limited to 'modules')
-rw-r--r--modules/markup/html_test.go4
-rw-r--r--modules/markup/markdown/markdown.go209
-rw-r--r--modules/markup/markdown/markdown_test.go8
-rw-r--r--modules/markup/mdstripper/mdstripper.go260
-rw-r--r--modules/markup/orgmode/orgmode.go100
-rw-r--r--modules/markup/orgmode/orgmode_test.go10
6 files changed, 256 insertions, 335 deletions
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index 91ef320b40..07747e97e1 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -323,6 +323,6 @@ func TestRender_ShortLinks(t *testing.T) {
`<p><a href="`+notencodedImgurlWiki+`" rel="nofollow"><img src="`+notencodedImgurlWiki+`"/></a></p>`)
test(
"<p><a href=\"https://example.org\">[[foobar]]</a></p>",
- `<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`,
- `<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`)
+ `<p></p><p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p><p></p>`,
+ `<p></p><p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p><p></p>`)
}
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index d9fc768891..ff78d7ea3a 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -7,13 +7,14 @@ package markdown
import (
"bytes"
+ "io"
"strings"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
- "github.com/russross/blackfriday"
+ "github.com/russross/blackfriday/v2"
)
// Renderer is a extended version of underlying render object.
@@ -25,134 +26,138 @@ type Renderer struct {
var byteMailto = []byte("mailto:")
-// Link defines how formal links should be processed to produce corresponding HTML elements.
-func (r *Renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
- // special case: this is not a link, a hash link or a mailto:, so it's a
- // relative URL
- if len(link) > 0 && !markup.IsLink(link) &&
- link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
- lnk := string(link)
- if r.IsWiki {
- lnk = util.URLJoin("wiki", lnk)
- }
- mLink := util.URLJoin(r.URLPrefix, lnk)
- link = []byte(mLink)
- }
-
- if len(content) > 10 && string(content[0:9]) == "<a href=\"" && bytes.Contains(content[9:], []byte("<img")) {
- // Image with link case: markdown `[![]()]()`
- // If the content is an image, then we change the original href around it
- // which points to itself to a new address "link"
- rightQuote := bytes.Index(content[9:], []byte("\""))
- content = bytes.Replace(content, content[9:9+rightQuote], link, 1)
- out.Write(content)
- } else {
- r.Renderer.Link(out, link, title, content)
- }
+var htmlEscaper = [256][]byte{
+ '&': []byte("&amp;"),
+ '<': []byte("&lt;"),
+ '>': []byte("&gt;"),
+ '"': []byte("&quot;"),
}
-// List renders markdown bullet or digit lists to HTML
-func (r *Renderer) List(out *bytes.Buffer, text func() bool, flags int) {
- marker := out.Len()
- if out.Len() > 0 {
- out.WriteByte('\n')
- }
-
- if flags&blackfriday.LIST_TYPE_DEFINITION != 0 {
- out.WriteString("<dl>")
- } else if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
- out.WriteString("<ol class='ui list'>")
- } else {
- out.WriteString("<ul class='ui list'>")
- }
- if !text() {
- out.Truncate(marker)
- return
+func escapeHTML(w io.Writer, s []byte) {
+ var start, end int
+ for end < len(s) {
+ escSeq := htmlEscaper[s[end]]
+ if escSeq != nil {
+ _, _ = w.Write(s[start:end])
+ _, _ = w.Write(escSeq)
+ start = end + 1
+ }
+ end++
}
- if flags&blackfriday.LIST_TYPE_DEFINITION != 0 {
- out.WriteString("</dl>\n")
- } else if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
- out.WriteString("</ol>\n")
- } else {
- out.WriteString("</ul>\n")
+ if start < len(s) && end <= len(s) {
+ _, _ = w.Write(s[start:end])
}
}
-// ListItem defines how list items should be processed to produce corresponding HTML elements.
-func (r *Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
- // Detect procedures to draw checkboxes.
- prefix := ""
- if bytes.HasPrefix(text, []byte("<p>")) {
- prefix = "<p>"
- }
- switch {
- case bytes.HasPrefix(text, []byte(prefix+"[ ] ")):
- text = append([]byte(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></span>`), text[3+len(prefix):]...)
- if prefix != "" {
- text = bytes.Replace(text, []byte(prefix), []byte{}, 1)
+// RenderNode is a default renderer of a single node of a syntax tree. For
+// block nodes it will be called twice: first time with entering=true, second
+// time with entering=false, so that it could know when it's working on an open
+// tag and when on close. It writes the result to w.
+//
+// The return value is a way to tell the calling walker to adjust its walk
+// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
+// can ask the walker to skip a subtree of this node by returning SkipChildren.
+// The typical behavior is to return GoToNext, which asks for the usual
+// traversal to the next node.
+func (r *Renderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
+ switch node.Type {
+ case blackfriday.Image:
+ prefix := r.URLPrefix
+ if r.IsWiki {
+ prefix = util.URLJoin(prefix, "wiki", "raw")
}
- case bytes.HasPrefix(text, []byte(prefix+"[x] ")):
- text = append([]byte(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></span>`), text[3+len(prefix):]...)
- if prefix != "" {
- text = bytes.Replace(text, []byte(prefix), []byte{}, 1)
+ prefix = strings.Replace(prefix, "/src/", "/media/", 1)
+ link := node.LinkData.Destination
+ if len(link) > 0 && !markup.IsLink(link) {
+ lnk := string(link)
+ lnk = util.URLJoin(prefix, lnk)
+ lnk = strings.Replace(lnk, " ", "+", -1)
+ link = []byte(lnk)
+ }
+ node.LinkData.Destination = link
+ // Render link around image only if parent is not link already
+ if node.Parent != nil && node.Parent.Type != blackfriday.Link {
+ if entering {
+ _, _ = w.Write([]byte(`<a href="`))
+ escapeHTML(w, link)
+ _, _ = w.Write([]byte(`">`))
+ return r.Renderer.RenderNode(w, node, entering)
+ }
+ s := r.Renderer.RenderNode(w, node, entering)
+ _, _ = w.Write([]byte(`</a>`))
+ return s
+ }
+ return r.Renderer.RenderNode(w, node, entering)
+ case blackfriday.Link:
+ // special case: this is not a link, a hash link or a mailto:, so it's a
+ // relative URL
+ link := node.LinkData.Destination
+ if len(link) > 0 && !markup.IsLink(link) &&
+ link[0] != '#' && !bytes.HasPrefix(link, byteMailto) &&
+ node.LinkData.Footnote == nil {
+ lnk := string(link)
+ if r.IsWiki {
+ lnk = util.URLJoin("wiki", lnk)
+ }
+ link = []byte(util.URLJoin(r.URLPrefix, lnk))
+ }
+ node.LinkData.Destination = link
+ return r.Renderer.RenderNode(w, node, entering)
+ case blackfriday.Text:
+ isListItem := false
+ for n := node.Parent; n != nil; n = n.Parent {
+ if n.Type == blackfriday.Item {
+ isListItem = true
+ break
+ }
+ }
+ if isListItem {
+ text := node.Literal
+ switch {
+ case bytes.HasPrefix(text, []byte("[ ] ")):
+ _, _ = w.Write([]byte(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></span>`))
+ text = text[3:]
+ case bytes.HasPrefix(text, []byte("[x] ")):
+ _, _ = w.Write([]byte(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></span>`))
+ text = text[3:]
+ }
+ node.Literal = text
}
}
- r.Renderer.ListItem(out, text, flags)
-}
-
-// Image defines how images should be processed to produce corresponding HTML elements.
-func (r *Renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
- prefix := r.URLPrefix
- if r.IsWiki {
- prefix = util.URLJoin(prefix, "wiki", "raw")
- }
- prefix = strings.Replace(prefix, "/src/", "/media/", 1)
- if len(link) > 0 && !markup.IsLink(link) {
- lnk := string(link)
- lnk = util.URLJoin(prefix, lnk)
- lnk = strings.Replace(lnk, " ", "+", -1)
- link = []byte(lnk)
- }
-
- // Put a link around it pointing to itself by default
- out.WriteString(`<a href="`)
- out.Write(link)
- out.WriteString(`">`)
- r.Renderer.Image(out, link, title, alt)
- out.WriteString("</a>")
+ return r.Renderer.RenderNode(w, node, entering)
}
const (
blackfridayExtensions = 0 |
- blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
- blackfriday.EXTENSION_TABLES |
- blackfriday.EXTENSION_FENCED_CODE |
- blackfriday.EXTENSION_STRIKETHROUGH |
- blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK |
- blackfriday.EXTENSION_DEFINITION_LISTS |
- blackfriday.EXTENSION_FOOTNOTES |
- blackfriday.EXTENSION_HEADER_IDS |
- blackfriday.EXTENSION_AUTO_HEADER_IDS
+ blackfriday.NoIntraEmphasis |
+ blackfriday.Tables |
+ blackfriday.FencedCode |
+ blackfriday.Strikethrough |
+ blackfriday.NoEmptyLineBeforeBlock |
+ blackfriday.DefinitionLists |
+ blackfriday.Footnotes |
+ blackfriday.HeadingIDs |
+ blackfriday.AutoHeadingIDs
blackfridayHTMLFlags = 0 |
- blackfriday.HTML_SKIP_STYLE |
- blackfriday.HTML_OMIT_CONTENTS |
- blackfriday.HTML_USE_SMARTYPANTS
+ blackfriday.Smartypants
)
// RenderRaw renders Markdown to HTML without handling special links.
func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte {
renderer := &Renderer{
- Renderer: blackfriday.HtmlRenderer(blackfridayHTMLFlags, "", ""),
+ Renderer: blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
+ Flags: blackfridayHTMLFlags,
+ }),
URLPrefix: urlPrefix,
IsWiki: wikiMarkdown,
}
exts := blackfridayExtensions
if setting.Markdown.EnableHardLineBreak {
- exts |= blackfriday.EXTENSION_HARD_LINE_BREAK
+ exts |= blackfriday.HardLineBreak
}
- body = blackfriday.Markdown(body, renderer, exts)
+ body = blackfriday.Run(body, blackfriday.WithRenderer(renderer), blackfriday.WithExtensions(exts))
return markup.SanitizeBytes(body)
}
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 669b49367e..b29f870ce5 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -166,13 +166,13 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
<h3 id="footnotes">Footnotes</h3>
<p>Here is a simple footnote,<sup id="fnref:1"><a href="#fn:1" rel="nofollow">1</a></sup> and here is a longer one.<sup id="fnref:bignote"><a href="#fn:bignote" rel="nofollow">2</a></sup></p>
+
<div>
<hr/>
<ol>
-<li id="fn:1">This is the first footnote.
-</li>
+<li id="fn:1">This is the first footnote.</li>
<li id="fn:bignote"><p>Here is one with multiple paragraphs and code.</p>
@@ -180,9 +180,9 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
<p><code>{ my code }</code></p>
-<p>Add as many paragraphs as you like.</p>
-</li>
+<p>Add as many paragraphs as you like.</p></li>
</ol>
+
</div>
`,
}
diff --git a/modules/markup/mdstripper/mdstripper.go b/modules/markup/mdstripper/mdstripper.go
index 7a901b17a9..d248944b68 100644
--- a/modules/markup/mdstripper/mdstripper.go
+++ b/modules/markup/mdstripper/mdstripper.go
@@ -6,43 +6,39 @@ package mdstripper
import (
"bytes"
+ "io"
- "github.com/russross/blackfriday"
+ "github.com/russross/blackfriday/v2"
)
// MarkdownStripper extends blackfriday.Renderer
type MarkdownStripper struct {
- blackfriday.Renderer
links []string
coallesce bool
+ empty bool
}
const (
blackfridayExtensions = 0 |
- blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
- blackfriday.EXTENSION_TABLES |
- blackfriday.EXTENSION_FENCED_CODE |
- blackfriday.EXTENSION_STRIKETHROUGH |
- blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK |
- blackfriday.EXTENSION_DEFINITION_LISTS |
- blackfriday.EXTENSION_FOOTNOTES |
- blackfriday.EXTENSION_HEADER_IDS |
- blackfriday.EXTENSION_AUTO_HEADER_IDS |
+ blackfriday.NoIntraEmphasis |
+ blackfriday.Tables |
+ blackfriday.FencedCode |
+ blackfriday.Strikethrough |
+ blackfriday.NoEmptyLineBeforeBlock |
+ blackfriday.DefinitionLists |
+ blackfriday.Footnotes |
+ blackfriday.HeadingIDs |
+ blackfriday.AutoHeadingIDs |
// Not included in modules/markup/markdown/markdown.go;
// required here to process inline links
- blackfriday.EXTENSION_AUTOLINK
+ blackfriday.Autolink
)
-//revive:disable:var-naming Implementing the Rendering interface requires breaking some linting rules
-
// StripMarkdown parses markdown content by removing all markup and code blocks
// in order to extract links and other references
func StripMarkdown(rawBytes []byte) (string, []string) {
- stripper := &MarkdownStripper{
- links: make([]string, 0, 10),
- }
- body := blackfriday.Markdown(rawBytes, stripper, blackfridayExtensions)
- return string(body), stripper.GetLinks()
+ buf, links := StripMarkdownBytes(rawBytes)
+ return string(buf), links
}
// StripMarkdownBytes parses markdown content by removing all markup and code blocks
@@ -50,205 +46,67 @@ func StripMarkdown(rawBytes []byte) (string, []string) {
func StripMarkdownBytes(rawBytes []byte) ([]byte, []string) {
stripper := &MarkdownStripper{
links: make([]string, 0, 10),
+ empty: true,
}
- body := blackfriday.Markdown(rawBytes, stripper, blackfridayExtensions)
- return body, stripper.GetLinks()
-}
-
-// block-level callbacks
-
-// BlockCode dummy function to proceed with rendering
-func (r *MarkdownStripper) BlockCode(out *bytes.Buffer, text []byte, infoString string) {
- // Not rendered
- r.coallesce = false
-}
-
-// BlockQuote dummy function to proceed with rendering
-func (r *MarkdownStripper) BlockQuote(out *bytes.Buffer, text []byte) {
- // FIXME: perhaps it's better to leave out block quote for this?
- r.processString(out, text, false)
-}
-
-// BlockHtml dummy function to proceed with rendering
-func (r *MarkdownStripper) BlockHtml(out *bytes.Buffer, text []byte) { //nolint
- // Not rendered
- r.coallesce = false
-}
-
-// Header dummy function to proceed with rendering
-func (r *MarkdownStripper) Header(out *bytes.Buffer, text func() bool, level int, id string) {
- text()
- r.coallesce = false
-}
-
-// HRule dummy function to proceed with rendering
-func (r *MarkdownStripper) HRule(out *bytes.Buffer) {
- // Not rendered
- r.coallesce = false
-}
-
-// List dummy function to proceed with rendering
-func (r *MarkdownStripper) List(out *bytes.Buffer, text func() bool, flags int) {
- text()
- r.coallesce = false
-}
-
-// ListItem dummy function to proceed with rendering
-func (r *MarkdownStripper) ListItem(out *bytes.Buffer, text []byte, flags int) {
- r.processString(out, text, false)
-}
-
-// Paragraph dummy function to proceed with rendering
-func (r *MarkdownStripper) Paragraph(out *bytes.Buffer, text func() bool) {
- text()
- r.coallesce = false
-}
-
-// Table dummy function to proceed with rendering
-func (r *MarkdownStripper) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
- r.processString(out, header, false)
- r.processString(out, body, false)
-}
-
-// TableRow dummy function to proceed with rendering
-func (r *MarkdownStripper) TableRow(out *bytes.Buffer, text []byte) {
- r.processString(out, text, false)
-}
-
-// TableHeaderCell dummy function to proceed with rendering
-func (r *MarkdownStripper) TableHeaderCell(out *bytes.Buffer, text []byte, flags int) {
- r.processString(out, text, false)
-}
-
-// TableCell dummy function to proceed with rendering
-func (r *MarkdownStripper) TableCell(out *bytes.Buffer, text []byte, flags int) {
- r.processString(out, text, false)
-}
-
-// Footnotes dummy function to proceed with rendering
-func (r *MarkdownStripper) Footnotes(out *bytes.Buffer, text func() bool) {
- text()
-}
-
-// FootnoteItem dummy function to proceed with rendering
-func (r *MarkdownStripper) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
- r.processString(out, text, false)
-}
-
-// TitleBlock dummy function to proceed with rendering
-func (r *MarkdownStripper) TitleBlock(out *bytes.Buffer, text []byte) {
- r.processString(out, text, false)
-}
-
-// Span-level callbacks
-
-// AutoLink dummy function to proceed with rendering
-func (r *MarkdownStripper) AutoLink(out *bytes.Buffer, link []byte, kind int) {
- r.processLink(out, link, []byte{})
-}
-
-// CodeSpan dummy function to proceed with rendering
-func (r *MarkdownStripper) CodeSpan(out *bytes.Buffer, text []byte) {
- // Not rendered
- r.coallesce = false
-}
-
-// DoubleEmphasis dummy function to proceed with rendering
-func (r *MarkdownStripper) DoubleEmphasis(out *bytes.Buffer, text []byte) {
- r.processString(out, text, false)
-}
-
-// Emphasis dummy function to proceed with rendering
-func (r *MarkdownStripper) Emphasis(out *bytes.Buffer, text []byte) {
- r.processString(out, text, false)
-}
-// Image dummy function to proceed with rendering
-func (r *MarkdownStripper) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
- // Not rendered
- r.coallesce = false
-}
-
-// LineBreak dummy function to proceed with rendering
-func (r *MarkdownStripper) LineBreak(out *bytes.Buffer) {
- // Not rendered
- r.coallesce = false
-}
-
-// Link dummy function to proceed with rendering
-func (r *MarkdownStripper) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
- r.processLink(out, link, content)
-}
-
-// RawHtmlTag dummy function to proceed with rendering
-func (r *MarkdownStripper) RawHtmlTag(out *bytes.Buffer, tag []byte) { //nolint
- // Not rendered
- r.coallesce = false
-}
-
-// TripleEmphasis dummy function to proceed with rendering
-func (r *MarkdownStripper) TripleEmphasis(out *bytes.Buffer, text []byte) {
- r.processString(out, text, false)
-}
-
-// StrikeThrough dummy function to proceed with rendering
-func (r *MarkdownStripper) StrikeThrough(out *bytes.Buffer, text []byte) {
- r.processString(out, text, false)
-}
-
-// FootnoteRef dummy function to proceed with rendering
-func (r *MarkdownStripper) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
- // Not rendered
- r.coallesce = false
-}
-
-// Low-level callbacks
-
-// Entity dummy function to proceed with rendering
-func (r *MarkdownStripper) Entity(out *bytes.Buffer, entity []byte) {
- // FIXME: literal entities are not parsed; perhaps they should
- r.coallesce = false
-}
-
-// NormalText dummy function to proceed with rendering
-func (r *MarkdownStripper) NormalText(out *bytes.Buffer, text []byte) {
- r.processString(out, text, true)
-}
-
-// Header and footer
-
-// DocumentHeader dummy function to proceed with rendering
-func (r *MarkdownStripper) DocumentHeader(out *bytes.Buffer) {
+ parser := blackfriday.New(blackfriday.WithRenderer(stripper), blackfriday.WithExtensions(blackfridayExtensions))
+ ast := parser.Parse(rawBytes)
+ var buf bytes.Buffer
+ stripper.RenderHeader(&buf, ast)
+ ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
+ return stripper.RenderNode(&buf, node, entering)
+ })
+ stripper.RenderFooter(&buf, ast)
+ return buf.Bytes(), stripper.GetLinks()
+}
+
+// RenderNode is the main rendering method. It will be called once for
+// every leaf node and twice for every non-leaf node (first with
+// entering=true, then with entering=false). The method should write its
+// rendition of the node to the supplied writer w.
+func (r *MarkdownStripper) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
+ if !entering {
+ return blackfriday.GoToNext
+ }
+ switch node.Type {
+ case blackfriday.Text:
+ r.processString(w, node.Literal, node.Parent == nil)
+ return blackfriday.GoToNext
+ case blackfriday.Link:
+ r.processLink(w, node.LinkData.Destination)
+ r.coallesce = false
+ return blackfriday.SkipChildren
+ }
r.coallesce = false
+ return blackfriday.GoToNext
}
-// DocumentFooter dummy function to proceed with rendering
-func (r *MarkdownStripper) DocumentFooter(out *bytes.Buffer) {
- r.coallesce = false
+// RenderHeader is a method that allows the renderer to produce some
+// content preceding the main body of the output document.
+func (r *MarkdownStripper) RenderHeader(w io.Writer, ast *blackfriday.Node) {
}
-// GetFlags returns rendering flags
-func (r *MarkdownStripper) GetFlags() int {
- return 0
+// RenderFooter is a symmetric counterpart of RenderHeader.
+func (r *MarkdownStripper) RenderFooter(w io.Writer, ast *blackfriday.Node) {
}
-//revive:enable:var-naming
-
-func doubleSpace(out *bytes.Buffer) {
- if out.Len() > 0 {
- out.WriteByte('\n')
+func (r *MarkdownStripper) doubleSpace(w io.Writer) {
+ if !r.empty {
+ _, _ = w.Write([]byte{'\n'})
}
}
-func (r *MarkdownStripper) processString(out *bytes.Buffer, text []byte, coallesce bool) {
+func (r *MarkdownStripper) processString(w io.Writer, text []byte, coallesce bool) {
// Always break-up words
if !coallesce || !r.coallesce {
- doubleSpace(out)
+ r.doubleSpace(w)
}
- out.Write(text)
+ _, _ = w.Write(text)
r.coallesce = coallesce
+ r.empty = false
}
-func (r *MarkdownStripper) processLink(out *bytes.Buffer, link []byte, content []byte) {
+
+func (r *MarkdownStripper) processLink(w io.Writer, link []byte) {
// Links are processed out of band
r.links = append(r.links, string(link))
r.coallesce = false
diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go
index f63155201e..54188d2734 100644
--- a/modules/markup/orgmode/orgmode.go
+++ b/modules/markup/orgmode/orgmode.go
@@ -5,12 +5,16 @@
package markup
import (
+ "bytes"
+ "fmt"
+ "html"
+ "strings"
+
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
- "code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/util"
- "github.com/chaseadamsio/goorgeous"
- "github.com/russross/blackfriday"
+ "github.com/niklasfasching/go-org/org"
)
func init() {
@@ -32,23 +36,23 @@ func (Parser) Extensions() []string {
}
// Render renders orgmode rawbytes to HTML
-func Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) (result []byte) {
- defer func() {
- if err := recover(); err != nil {
- log.Error("Panic in orgmode.Render: %v Just returning the rawBytes", err)
- result = rawBytes
- }
- }()
- htmlFlags := blackfriday.HTML_USE_XHTML
- htmlFlags |= blackfriday.HTML_SKIP_STYLE
- htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
- renderer := &markdown.Renderer{
- Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
- URLPrefix: urlPrefix,
- IsWiki: isWiki,
+func Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
+ htmlWriter := org.NewHTMLWriter()
+
+ renderer := &Renderer{
+ HTMLWriter: htmlWriter,
+ URLPrefix: urlPrefix,
+ IsWiki: isWiki,
+ }
+
+ htmlWriter.ExtendingWriter = renderer
+
+ res, err := org.New().Silent().Parse(bytes.NewReader(rawBytes), "").Write(renderer)
+ if err != nil {
+ log.Error("Panic in orgmode.Render: %v Just returning the rawBytes", err)
+ return rawBytes
}
- result = goorgeous.Org(rawBytes, renderer)
- return
+ return []byte(res)
}
// RenderString reners orgmode string to HTML string
@@ -56,7 +60,63 @@ func RenderString(rawContent string, urlPrefix string, metas map[string]string,
return string(Render([]byte(rawContent), urlPrefix, metas, isWiki))
}
-// Render implements markup.Parser
+// Render reners orgmode string to HTML string
func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
return Render(rawBytes, urlPrefix, metas, isWiki)
}
+
+// Renderer implements org.Writer
+type Renderer struct {
+ *org.HTMLWriter
+ URLPrefix string
+ IsWiki bool
+}
+
+var byteMailto = []byte("mailto:")
+
+// WriteRegularLink renders images, links or videos
+func (r *Renderer) WriteRegularLink(l org.RegularLink) {
+ link := []byte(html.EscapeString(l.URL))
+ if l.Protocol == "file" {
+ link = link[len("file:"):]
+ }
+ if len(link) > 0 && !markup.IsLink(link) &&
+ link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
+ lnk := string(link)
+ if r.IsWiki {
+ lnk = util.URLJoin("wiki", lnk)
+ }
+ link = []byte(util.URLJoin(r.URLPrefix, lnk))
+ }
+
+ description := string(link)
+ if l.Description != nil {
+ description = r.nodesAsString(l.Description...)
+ }
+ switch l.Kind() {
+ case "image":
+ r.WriteString(fmt.Sprintf(`<img src="%s" alt="%s" title="%s" />`, link, description, description))
+ case "video":
+ r.WriteString(fmt.Sprintf(`<video src="%s" title="%s">%s</video>`, link, description, description))
+ default:
+ r.WriteString(fmt.Sprintf(`<a href="%s" title="%s">%s</a>`, link, description, description))
+ }
+}
+
+func (r *Renderer) emptyClone() *Renderer {
+ wcopy := *(r.HTMLWriter)
+ wcopy.Builder = strings.Builder{}
+
+ rcopy := *r
+ rcopy.HTMLWriter = &wcopy
+
+ wcopy.ExtendingWriter = &rcopy
+
+ return &rcopy
+}
+
+func (r *Renderer) nodesAsString(nodes ...org.Node) string {
+ tmp := r.emptyClone()
+ org.WriteNodes(tmp, nodes...)
+ return tmp.String()
+}
diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go
index 3846922c25..40323912b4 100644
--- a/modules/markup/orgmode/orgmode_test.go
+++ b/modules/markup/orgmode/orgmode_test.go
@@ -27,12 +27,12 @@ func TestRender_StandardLinks(t *testing.T) {
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
}
- googleRendered := `<p><a href="https://google.com/" title="https://google.com/">https://google.com/</a></p>`
+ googleRendered := "<p>\n<a href=\"https://google.com/\" title=\"https://google.com/\">https://google.com/</a>\n</p>"
test("[[https://google.com/]]", googleRendered)
lnk := util.URLJoin(AppSubURL, "WikiPage")
test("[[WikiPage][WikiPage]]",
- `<p><a href="`+lnk+`" title="WikiPage">WikiPage</a></p>`)
+ "<p>\n<a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a>\n</p>")
}
func TestRender_Images(t *testing.T) {
@@ -45,10 +45,8 @@ func TestRender_Images(t *testing.T) {
}
url := "../../.images/src/02/train.jpg"
- title := "Train"
result := util.URLJoin(AppSubURL, url)
- test(
- "[[file:"+url+"]["+title+"]]",
- `<p><a href="`+result+`"><img src="`+result+`" alt="`+title+`" title="`+title+`" /></a></p>`)
+ test("[[file:"+url+"]]",
+ "<p>\n<img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" />\n</p>")
}