goldmark-inline-attributes/parser.go

183 lines
3.6 KiB
Go

package attributes
import (
"bytes"
"fmt"
"strings"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
)
var spanContentStateKey = parser.NewContextKey()
type attributesParser struct {
}
var defaultAttributesParser = &attributesParser{}
// NewAttributesParser return a new InlineParser that parses inline attributes
// like '[txt]{.underline}' .
func NewAttributesParser() parser.InlineParser {
return defaultAttributesParser
}
func (s *attributesParser) Trigger() []byte {
return []byte{'[', ']'}
}
var attributeBottom = parser.NewContextKey()
func (s *attributesParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
sl, ss := block.Position()
line, seg := block.PeekLine()
if line[0] == '[' && bytes.Contains(line, []byte("]{")) {
pc.Set(attributeBottom, pc.LastDelimiter())
return processSpanContentOpen(block, seg.Start, pc)
}
// line[0] == ']'
tlist := pc.Get(spanContentStateKey)
if tlist == nil {
block.SetPosition(sl, ss)
return nil
}
last := tlist.(*spanContentState).Last
if last == nil {
return nil
}
block.Advance(1)
removeSpanContentState(pc, last)
c := block.Peek()
if c != '{' {
block.SetPosition(sl, ss)
return nil
}
attrs, ok := parser.ParseAttributes(block)
if !ok {
block.SetPosition(sl, ss)
return nil
}
span := &Node{}
for _, attr := range attrs {
span.SetAttribute(attr.Name, attr.Value)
}
var bottom ast.Node
if v := pc.Get(attributeBottom); v != nil {
bottom = v.(ast.Node)
}
pc.Set(attributeBottom, nil)
parser.ProcessDelimiters(bottom, pc)
for c := last.NextSibling(); c != nil; {
next := c.NextSibling()
parent.RemoveChild(parent, c)
span.AppendChild(span, c)
c = next
}
last.Parent().RemoveChild(last.Parent(), last)
return span
}
type spanContentState struct {
ast.BaseInline
Segment text.Segment
Prev *spanContentState
Next *spanContentState
First *spanContentState
Last *spanContentState
}
func (s *spanContentState) Text(source []byte) []byte {
return s.Segment.Value(source)
}
func (s *spanContentState) Dump(source []byte, level int) {
fmt.Printf("%sspanContentState: \"%s\"\n", strings.Repeat(" ", level), s.Text(source))
}
var kindSpanContentState = ast.NewNodeKind("SpanContentState")
func (s *spanContentState) Kind() ast.NodeKind {
return kindSpanContentState
}
func spanContentStateLength(v *spanContentState) int {
if v == nil || v.Last == nil || v.First == nil {
return 0
}
return v.Last.Segment.Stop - v.First.Segment.Start
}
func pushSpanContentState(pc parser.Context, v *spanContentState) {
tlist := pc.Get(spanContentStateKey)
var list *spanContentState
if tlist == nil {
list = v
v.First = v
v.Last = v
pc.Set(spanContentStateKey, list)
} else {
list = tlist.(*spanContentState)
l := list.Last
list.Last = v
l.Next = v
v.Prev = l
}
}
func removeSpanContentState(pc parser.Context, d *spanContentState) {
tlist := pc.Get(spanContentStateKey)
var list *spanContentState
if tlist == nil {
return
}
list = tlist.(*spanContentState)
if d.Prev == nil {
list = d.Next
if list != nil {
list.First = d
list.Last = d.Last
list.Prev = nil
pc.Set(spanContentStateKey, list)
} else {
pc.Set(spanContentStateKey, nil)
}
} else {
d.Prev.Next = d.Next
if d.Next != nil {
d.Next.Prev = d.Prev
}
}
if list != nil && d.Next == nil {
list.Last = d.Prev
}
d.Next = nil
d.Prev = nil
d.First = nil
d.Last = nil
}
func processSpanContentOpen(block text.Reader, pos int, pc parser.Context) *spanContentState {
state := &spanContentState{
Segment: text.NewSegment(pos, pos+1),
}
pushSpanContentState(pc, state)
block.Advance(1)
return state
}