mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-10-30 19:56:16 +00:00
109 lines
2.5 KiB
Go
109 lines
2.5 KiB
Go
package markup
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html"
|
|
"strings"
|
|
)
|
|
|
|
type spanTokenType int
|
|
|
|
const (
|
|
spanTextNode = iota
|
|
spanItalic
|
|
spanBold
|
|
spanMono
|
|
spanSuper
|
|
spanSub
|
|
spanMark
|
|
)
|
|
|
|
func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, originalForm string) string {
|
|
if tagState[spanMono] && (stt != spanMono) {
|
|
return originalForm
|
|
}
|
|
if tagState[stt] {
|
|
tagState[stt] = false
|
|
return fmt.Sprintf("</%s>", tagName)
|
|
} else {
|
|
tagState[stt] = true
|
|
return fmt.Sprintf("<%s>", tagName)
|
|
}
|
|
}
|
|
|
|
// getTextNode splits the `p` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = p`, `rest = ""`. It handles escaping with backslash.
|
|
func getTextNode(input *bytes.Buffer) string {
|
|
var (
|
|
textNodeBuffer = bytes.Buffer{}
|
|
escaping = false
|
|
)
|
|
// Always read the first byte in advance to avoid endless loops that kill computers (sad experience)
|
|
if input.Len() != 0 {
|
|
b, _ := input.ReadByte()
|
|
textNodeBuffer.WriteByte(b)
|
|
}
|
|
for input.Len() != 0 {
|
|
// Assume no error is possible because we check for length
|
|
b, _ := input.ReadByte()
|
|
if escaping {
|
|
textNodeBuffer.WriteByte(b)
|
|
escaping = false
|
|
} else if b == '\\' {
|
|
escaping = true
|
|
} else if strings.IndexByte("/*`^,!", b) >= 0 {
|
|
input.UnreadByte()
|
|
break
|
|
} else {
|
|
textNodeBuffer.WriteByte(b)
|
|
}
|
|
}
|
|
return textNodeBuffer.String()
|
|
}
|
|
|
|
func ParagraphToHtml(input string) string {
|
|
var (
|
|
p = bytes.NewBufferString(input)
|
|
ret strings.Builder
|
|
// true = tag is opened, false = tag is not opened
|
|
tagState = map[spanTokenType]bool{
|
|
spanItalic: false,
|
|
spanBold: false,
|
|
spanMono: false,
|
|
spanSuper: false,
|
|
spanSub: false,
|
|
spanMark: false,
|
|
}
|
|
startsWith = func(t string) bool {
|
|
return bytes.HasPrefix(p.Bytes(), []byte(t))
|
|
}
|
|
)
|
|
|
|
for p.Len() != 0 {
|
|
switch {
|
|
case startsWith("//"):
|
|
ret.WriteString(tagFromState(spanItalic, tagState, "em", "//"))
|
|
p.Next(2)
|
|
case startsWith("**"):
|
|
ret.WriteString(tagFromState(spanBold, tagState, "strong", "**"))
|
|
p.Next(2)
|
|
case startsWith("`"):
|
|
ret.WriteString(tagFromState(spanMono, tagState, "code", "`"))
|
|
p.Next(1)
|
|
case startsWith("^"):
|
|
ret.WriteString(tagFromState(spanSuper, tagState, "sup", "^"))
|
|
p.Next(1)
|
|
case startsWith(",,"):
|
|
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
|
|
p.Next(2)
|
|
case startsWith("!!"):
|
|
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
|
|
p.Next(2)
|
|
default:
|
|
ret.WriteString(html.EscapeString(getTextNode(p)))
|
|
}
|
|
}
|
|
|
|
return ret.String()
|
|
}
|