Implemented links

This commit is contained in:
Eduard Urbach 2024-04-01 11:54:14 +02:00
parent 93d5949eff
commit 862f9837e4
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
3 changed files with 91 additions and 5 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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=\"&#34;&gt;&lt;div&gt;html&lt;/div&gt;\">text</a></p>")
assert.Equal(t, markdown.Render("[<div>html</div>]()"), "<p><a href=\"\">&lt;div&gt;html&lt;/div&gt;</a></p>")
}