setext_headings.go

  1package parser
  2
  3import (
  4	"github.com/yuin/goldmark/ast"
  5	"github.com/yuin/goldmark/text"
  6	"github.com/yuin/goldmark/util"
  7)
  8
  9var temporaryParagraphKey = NewContextKey()
 10
 11type setextHeadingParser struct {
 12	HeadingConfig
 13}
 14
 15func matchesSetextHeadingBar(line []byte) (byte, bool) {
 16	start := 0
 17	end := len(line)
 18	space := util.TrimLeftLength(line, []byte{' '})
 19	if space > 3 {
 20		return 0, false
 21	}
 22	start += space
 23	level1 := util.TrimLeftLength(line[start:end], []byte{'='})
 24	c := byte('=')
 25	var level2 int
 26	if level1 == 0 {
 27		level2 = util.TrimLeftLength(line[start:end], []byte{'-'})
 28		c = '-'
 29	}
 30	if util.IsSpace(line[end-1]) {
 31		end -= util.TrimRightSpaceLength(line[start:end])
 32	}
 33	if !((level1 > 0 && start+level1 == end) || (level2 > 0 && start+level2 == end)) {
 34		return 0, false
 35	}
 36	return c, true
 37}
 38
 39// NewSetextHeadingParser return a new BlockParser that can parse Setext headings.
 40func NewSetextHeadingParser(opts ...HeadingOption) BlockParser {
 41	p := &setextHeadingParser{}
 42	for _, o := range opts {
 43		o.SetHeadingOption(&p.HeadingConfig)
 44	}
 45	return p
 46}
 47
 48func (b *setextHeadingParser) Trigger() []byte {
 49	return []byte{'-', '='}
 50}
 51
 52func (b *setextHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
 53	last := pc.LastOpenedBlock().Node
 54	if last == nil {
 55		return nil, NoChildren
 56	}
 57	paragraph, ok := last.(*ast.Paragraph)
 58	if !ok || paragraph.Parent() != parent {
 59		return nil, NoChildren
 60	}
 61	line, segment := reader.PeekLine()
 62	c, ok := matchesSetextHeadingBar(line)
 63	if !ok {
 64		return nil, NoChildren
 65	}
 66	level := 1
 67	if c == '-' {
 68		level = 2
 69	}
 70	node := ast.NewHeading(level)
 71	node.Lines().Append(segment)
 72	pc.Set(temporaryParagraphKey, last)
 73	return node, NoChildren | RequireParagraph
 74}
 75
 76func (b *setextHeadingParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
 77	return Close
 78}
 79
 80func (b *setextHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) {
 81	heading := node.(*ast.Heading)
 82	segment := node.Lines().At(0)
 83	heading.Lines().Clear()
 84	tmp := pc.Get(temporaryParagraphKey).(*ast.Paragraph)
 85	pc.Set(temporaryParagraphKey, nil)
 86	if tmp.Lines().Len() == 0 {
 87		next := heading.NextSibling()
 88		segment = segment.TrimLeftSpace(reader.Source())
 89		if next == nil || !ast.IsParagraph(next) {
 90			para := ast.NewParagraph()
 91			para.Lines().Append(segment)
 92			heading.Parent().InsertAfter(heading.Parent(), heading, para)
 93		} else {
 94			next.Lines().Unshift(segment)
 95		}
 96		heading.Parent().RemoveChild(heading.Parent(), heading)
 97	} else {
 98		heading.SetLines(tmp.Lines())
 99		heading.SetBlankPreviousLines(tmp.HasBlankPreviousLines())
100		tp := tmp.Parent()
101		if tp != nil {
102			tp.RemoveChild(tp, tmp)
103		}
104	}
105
106	if b.Attribute {
107		parseLastLineAttributes(node, reader, pc)
108	}
109
110	if b.AutoHeadingID {
111		id, ok := node.AttributeString("id")
112		if !ok {
113			generateAutoHeadingID(heading, reader, pc)
114		} else {
115			pc.IDs().Put(id.([]byte))
116		}
117	}
118}
119
120func (b *setextHeadingParser) CanInterruptParagraph() bool {
121	return true
122}
123
124func (b *setextHeadingParser) CanAcceptIndentedLine() bool {
125	return false
126}