Implemented links
This commit is contained in:
parent
93d5949eff
commit
862f9837e4
@ -4,6 +4,7 @@ Markdown renderer.
|
||||
|
||||
## Features
|
||||
|
||||
- Links
|
||||
- Headers
|
||||
- Paragraphs
|
||||
|
||||
@ -17,7 +18,6 @@ go get git.akyoto.dev/go/markdown
|
||||
|
||||
```go
|
||||
html := markdown.Render("# Header")
|
||||
fmt.Println(html)
|
||||
```
|
||||
|
||||
## Tests
|
||||
@ -26,14 +26,16 @@ fmt.Println(html)
|
||||
PASS: TestEmpty
|
||||
PASS: TestParagraphs
|
||||
PASS: TestHeader
|
||||
PASS: TestLink
|
||||
PASS: TestCombined
|
||||
PASS: TestSecurity
|
||||
coverage: 100.0% of statements
|
||||
```
|
||||
|
||||
## 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
|
||||
|
71
Render.go
71
Render.go
@ -25,7 +25,7 @@ func Render(markdown string) string {
|
||||
}
|
||||
|
||||
out.WriteString("<p>")
|
||||
out.WriteString(html.EscapeString(paragraph.String()))
|
||||
writeText(&out, paragraph.String())
|
||||
out.WriteString("</p>")
|
||||
paragraph.Reset()
|
||||
}
|
||||
@ -52,7 +52,7 @@ func Render(markdown string) string {
|
||||
|
||||
if space > 0 && space <= 6 {
|
||||
out.WriteString(headerStart[space-1])
|
||||
out.WriteString(html.EscapeString(line[space+1:]))
|
||||
writeText(&out, line[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(""), "")
|
||||
}
|
||||
|
||||
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\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>")
|
||||
}
|
||||
|
||||
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) {
|
||||
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 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>")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user