Implemented links
This commit is contained in:
@ -4,6 +4,7 @@ Markdown renderer.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- Links
|
||||||
- Headers
|
- Headers
|
||||||
- Paragraphs
|
- Paragraphs
|
||||||
|
|
||||||
@ -17,7 +18,6 @@ go get git.akyoto.dev/go/markdown
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
html := markdown.Render("# Header")
|
html := markdown.Render("# Header")
|
||||||
fmt.Println(html)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
@ -26,14 +26,16 @@ fmt.Println(html)
|
|||||||
PASS: TestEmpty
|
PASS: TestEmpty
|
||||||
PASS: TestParagraphs
|
PASS: TestParagraphs
|
||||||
PASS: TestHeader
|
PASS: TestHeader
|
||||||
|
PASS: TestLink
|
||||||
PASS: TestCombined
|
PASS: TestCombined
|
||||||
|
PASS: TestSecurity
|
||||||
coverage: 100.0% of statements
|
coverage: 100.0% of statements
|
||||||
```
|
```
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
```
|
```
|
||||||
BenchmarkSmall-12 2187232 544.9 ns/op 296 B/op 9 allocs/op
|
BenchmarkSmall-12 2019103 591.4 ns/op 296 B/op 9 allocs/op
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
71
Render.go
71
Render.go
@ -25,7 +25,7 @@ func Render(markdown string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out.WriteString("<p>")
|
out.WriteString("<p>")
|
||||||
out.WriteString(html.EscapeString(paragraph.String()))
|
writeText(&out, paragraph.String())
|
||||||
out.WriteString("</p>")
|
out.WriteString("</p>")
|
||||||
paragraph.Reset()
|
paragraph.Reset()
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ func Render(markdown string) string {
|
|||||||
|
|
||||||
if space > 0 && space <= 6 {
|
if space > 0 && space <= 6 {
|
||||||
out.WriteString(headerStart[space-1])
|
out.WriteString(headerStart[space-1])
|
||||||
out.WriteString(html.EscapeString(line[space+1:]))
|
writeText(&out, line[space+1:])
|
||||||
out.WriteString(headerEnd[space-1])
|
out.WriteString(headerEnd[space-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,3 +69,70 @@ func Render(markdown string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeText(out *strings.Builder, text string) {
|
||||||
|
var (
|
||||||
|
i = 0
|
||||||
|
tokenStart = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
textStart = -1
|
||||||
|
textEnd = -1
|
||||||
|
urlStart = -1
|
||||||
|
parentheses = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if i == len(text) {
|
||||||
|
out.WriteString(html.EscapeString(text[tokenStart:]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := text[i]
|
||||||
|
|
||||||
|
switch c {
|
||||||
|
case '[':
|
||||||
|
out.WriteString(html.EscapeString(text[tokenStart:i]))
|
||||||
|
tokenStart = i
|
||||||
|
textStart = i
|
||||||
|
case ']':
|
||||||
|
textEnd = i
|
||||||
|
case '(':
|
||||||
|
if parentheses == 0 {
|
||||||
|
urlStart = i
|
||||||
|
}
|
||||||
|
|
||||||
|
parentheses++
|
||||||
|
case ')':
|
||||||
|
parentheses--
|
||||||
|
|
||||||
|
if parentheses == 0 && textStart >= 0 && textEnd >= 0 && urlStart >= 0 {
|
||||||
|
linkText := text[textStart+1 : textEnd]
|
||||||
|
linkURL := text[urlStart+1 : i]
|
||||||
|
|
||||||
|
out.WriteString("<a href=\"")
|
||||||
|
out.WriteString(formatURL(linkURL))
|
||||||
|
out.WriteString("\">")
|
||||||
|
out.WriteString(html.EscapeString(linkText))
|
||||||
|
out.WriteString("</a>")
|
||||||
|
|
||||||
|
textStart = -1
|
||||||
|
textEnd = -1
|
||||||
|
urlStart = -1
|
||||||
|
|
||||||
|
tokenStart = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatURL(linkURL string) string {
|
||||||
|
if strings.HasPrefix(strings.ToLower(linkURL), "javascript:") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return html.EscapeString(linkURL)
|
||||||
|
}
|
||||||
|
@ -11,7 +11,7 @@ func TestEmpty(t *testing.T) {
|
|||||||
assert.Equal(t, markdown.Render(""), "")
|
assert.Equal(t, markdown.Render(""), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParagraphs(t *testing.T) {
|
func TestParagraph(t *testing.T) {
|
||||||
assert.Equal(t, markdown.Render("Text"), "<p>Text</p>")
|
assert.Equal(t, markdown.Render("Text"), "<p>Text</p>")
|
||||||
assert.Equal(t, markdown.Render("Text\n"), "<p>Text</p>")
|
assert.Equal(t, markdown.Render("Text\n"), "<p>Text</p>")
|
||||||
assert.Equal(t, markdown.Render("Text\n\n"), "<p>Text</p>")
|
assert.Equal(t, markdown.Render("Text\n\n"), "<p>Text</p>")
|
||||||
@ -28,7 +28,24 @@ func TestHeader(t *testing.T) {
|
|||||||
assert.Equal(t, markdown.Render("###### Header"), "<h6>Header</h6>")
|
assert.Equal(t, markdown.Render("###### Header"), "<h6>Header</h6>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLink(t *testing.T) {
|
||||||
|
assert.Equal(t, markdown.Render("[text](https://example.com/)"), "<p><a href=\"https://example.com/\">text</a></p>")
|
||||||
|
assert.Equal(t, markdown.Render("[text](https://example.com/"), "<p>[text](https://example.com/</p>")
|
||||||
|
assert.Equal(t, markdown.Render("[text]https://example.com/)"), "<p>[text]https://example.com/)</p>")
|
||||||
|
assert.Equal(t, markdown.Render("[text(https://example.com/)"), "<p>[text(https://example.com/)</p>")
|
||||||
|
assert.Equal(t, markdown.Render("text](https://example.com/)"), "<p>text](https://example.com/)</p>")
|
||||||
|
assert.Equal(t, markdown.Render("Prefix [text](https://example.com/) suffix."), "<p>Prefix <a href=\"https://example.com/\">text</a> suffix.</p>")
|
||||||
|
}
|
||||||
|
|
||||||
func TestCombined(t *testing.T) {
|
func TestCombined(t *testing.T) {
|
||||||
assert.Equal(t, markdown.Render("# Header\nLine 1.\nLine 2.\nLine 3."), "<h1>Header</h1><p>Line 1. Line 2. Line 3.</p>")
|
assert.Equal(t, markdown.Render("# Header\nLine 1.\nLine 2.\nLine 3."), "<h1>Header</h1><p>Line 1. Line 2. Line 3.</p>")
|
||||||
assert.Equal(t, markdown.Render("# Header 1\nLine 1.\n# Header 2\nLine 2."), "<h1>Header 1</h1><p>Line 1.</p><h1>Header 2</h1><p>Line 2.</p>")
|
assert.Equal(t, markdown.Render("# Header 1\nLine 1.\n# Header 2\nLine 2."), "<h1>Header 1</h1><p>Line 1.</p><h1>Header 2</h1><p>Line 2.</p>")
|
||||||
|
assert.Equal(t, markdown.Render("# [Header Link](https://example.com/)"), "<h1><a href=\"https://example.com/\">Header Link</a></h1>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecurity(t *testing.T) {
|
||||||
|
assert.Equal(t, markdown.Render("[text](javascript:alert(\"xss\"))"), "<p><a href=\"\">text</a></p>")
|
||||||
|
assert.Equal(t, markdown.Render("[text](javAscRipt:alert(\"xss\"))"), "<p><a href=\"\">text</a></p>")
|
||||||
|
assert.Equal(t, markdown.Render("[text](\"><div>html</div>)"), "<p><a href=\""><div>html</div>\">text</a></p>")
|
||||||
|
assert.Equal(t, markdown.Render("[<div>html</div>]()"), "<p><a href=\"\"><div>html</div></a></p>")
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user