2020-10-30 13:25:48 +00:00
package markup
import (
"bytes"
"fmt"
"html"
"strings"
)
type spanTokenType int
const (
spanTextNode = iota
spanItalic
spanBold
spanMono
spanSuper
spanSub
spanMark
2020-11-04 17:42:02 +00:00
spanLink
2020-10-30 13:25:48 +00:00
)
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 )
}
}
2020-11-04 17:42:02 +00:00
func getLinkNode ( input * bytes . Buffer , hyphaName string ) string {
input . Next ( 2 )
var (
escaping = false
addrBuf = bytes . Buffer { }
displayBuf = bytes . Buffer { }
currBuf = & addrBuf
)
for input . Len ( ) != 0 {
b , _ := input . ReadByte ( )
if escaping {
currBuf . WriteByte ( b )
escaping = false
} else if b == '|' && currBuf == & addrBuf {
currBuf = & displayBuf
} else if b == ']' && bytes . HasPrefix ( input . Bytes ( ) , [ ] byte { ']' } ) {
input . Next ( 1 )
break
} else {
currBuf . WriteByte ( b )
}
}
href , text , class := LinkParts ( addrBuf . String ( ) , displayBuf . String ( ) , hyphaName )
return fmt . Sprintf ( ` <a href="%s" class="%s">%s</a> ` , href , class , html . EscapeString ( text ) )
}
// getTextNode splits the `input` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = input`, `rest = ""`. It handles escaping with backslash.
2020-10-30 13:25:48 +00:00
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
2020-11-04 17:42:02 +00:00
} else if strings . IndexByte ( "/*`^,![" , b ) >= 0 {
2020-10-30 13:25:48 +00:00
input . UnreadByte ( )
break
} else {
textNodeBuffer . WriteByte ( b )
}
}
return textNodeBuffer . String ( )
}
2020-11-04 17:42:02 +00:00
func ParagraphToHtml ( hyphaName , input string ) string {
2020-10-30 13:25:48 +00:00
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 ,
2020-11-04 17:42:02 +00:00
spanLink : false ,
2020-10-30 13:25:48 +00:00
}
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 )
2020-11-04 17:42:02 +00:00
case startsWith ( "[[" ) :
ret . WriteString ( getLinkNode ( p , hyphaName ) )
2020-10-30 13:25:48 +00:00
default :
ret . WriteString ( html . EscapeString ( getTextNode ( p ) ) )
}
}
2020-10-30 13:34:10 +00:00
for stt , open := range tagState {
if open {
switch stt {
case spanItalic :
ret . WriteString ( tagFromState ( spanItalic , tagState , "em" , "//" ) )
case spanBold :
ret . WriteString ( tagFromState ( spanBold , tagState , "strong" , "**" ) )
case spanMono :
ret . WriteString ( tagFromState ( spanMono , tagState , "code" , "`" ) )
case spanSuper :
ret . WriteString ( tagFromState ( spanSuper , tagState , "sup" , "^" ) )
case spanSub :
ret . WriteString ( tagFromState ( spanSub , tagState , "sub" , ",," ) )
case spanMark :
ret . WriteString ( tagFromState ( spanMark , tagState , "mark" , "!!" ) )
2020-11-04 17:42:02 +00:00
case spanLink :
ret . WriteString ( tagFromState ( spanLink , tagState , "a" , "[[" ) )
2020-10-30 13:34:10 +00:00
}
}
}
2020-10-30 13:25:48 +00:00
return ret . String ( )
}