func NewATXHeadingParser (opts ...HeadingOption ) BlockParser {
p := &atxHeadingParser {}
for _ , o := range opts {
o .SetHeadingOption (&p .HeadingConfig )
}
return p
}
func (b *atxHeadingParser ) Trigger () []byte {
return []byte {'#' }
}
func (b *atxHeadingParser ) Open (parent ast .Node , reader text .Reader , pc Context ) (ast .Node , State ) {
line , segment := reader .PeekLine ()
pos := pc .BlockOffset ()
if pos < 0 {
return nil , NoChildren
}
i := pos
for ; i < len (line ) && line [i ] == '#' ; i ++ {
}
level := i - pos
if i == pos || level > 6 {
return nil , NoChildren
}
l := util .TrimLeftSpaceLength (line [i :])
if l == 0 {
return nil , NoChildren
}
start := i + l
if start >= len (line ) {
start = len (line ) - 1
}
origstart := start
stop := len (line ) - util .TrimRightSpaceLength (line )
node := ast .NewHeading (level )
parsed := false
if b .Attribute {
start --
closureClose := -1
closureOpen := -1
for j := start ; j < stop ; {
c := line [j ]
if util .IsEscapedPunctuation (line , j ) {
j += 2
} else if util .IsSpace (c ) && j < stop -1 && line [j +1 ] == '#' {
closureOpen = j + 1
k := j + 1
for ; k < stop && line [k ] == '#' ; k ++ {
}
closureClose = k
break
} else {
j ++
}
}
if closureClose > 0 {
reader .Advance (closureClose )
attrs , ok := ParseAttributes (reader )
rest , _ := reader .PeekLine ()
parsed = ok && util .IsBlank (rest )
if parsed {
for _ , attr := range attrs {
node .SetAttribute (attr .Name , attr .Value )
}
node .Lines ().Append (text .NewSegment (segment .Start +start +1 -segment .Padding , segment .Start +closureOpen -segment .Padding ))
}
}
}
if !parsed {
start = origstart
stop := len (line ) - util .TrimRightSpaceLength (line )
if stop <= start {
stop = start
} else {
i = stop - 1
for ; line [i ] == '#' && i >= start ; i -- {
}
if i != stop -1 && !util .IsSpace (line [i ]) {
i = stop - 1
}
i ++
stop = i
}
if len (util .TrimRight (line [start :stop ], []byte {'#' })) != 0 {
node .Lines ().Append (text .NewSegment (segment .Start +start -segment .Padding , segment .Start +stop -segment .Padding ))
}
}
return node , NoChildren
}
func (b *atxHeadingParser ) Continue (node ast .Node , reader text .Reader , pc Context ) State {
return Close
}
func (b *atxHeadingParser ) Close (node ast .Node , reader text .Reader , pc Context ) {
if b .Attribute {
_ , ok := node .AttributeString ("id" )
if !ok {
parseLastLineAttributes (node , reader , pc )
}
}
if b .AutoHeadingID {
id , ok := node .AttributeString ("id" )
if !ok {
generateAutoHeadingID (node .(*ast .Heading ), reader , pc )
} else {
pc .IDs ().Put (id .([]byte ))
}
}
}
func (b *atxHeadingParser ) CanInterruptParagraph () bool {
return true
}
func (b *atxHeadingParser ) CanAcceptIndentedLine () bool {
return false
}
func generateAutoHeadingID (node *ast .Heading , reader text .Reader , pc Context ) {
var line []byte
lastIndex := node .Lines ().Len () - 1
if lastIndex > -1 {
lastLine := node .Lines ().At (lastIndex )
line = lastLine .Value (reader .Source ())
}
headingID := pc .IDs ().Generate (line , ast .KindHeading )
node .SetAttribute (attrNameID , headingID )
}
func parseLastLineAttributes (node ast .Node , reader text .Reader , pc Context ) {
lastIndex := node .Lines ().Len () - 1
if lastIndex < 0 {
return
}
lastLine := node .Lines ().At (lastIndex )
line := lastLine .Value (reader .Source ())
lr := text .NewReader (line )
var attrs Attributes
var ok bool
var start text .Segment
var sl int
var end text .Segment
for {
c := lr .Peek ()
if c == text .EOF {
break
}
if c == '\\' {
lr .Advance (1 )
if lr .Peek () == '{' {
lr .Advance (1 )
}
continue
}
if c == '{' {
sl , start = lr .Position ()
attrs , ok = ParseAttributes (lr )
_, end = lr .Position ()
lr .SetPosition (sl , start )
}
lr .Advance (1 )
}
if ok && util .IsBlank (line [end .Start :]) {
for _ , attr := range attrs {
node .SetAttribute (attr .Name , attr .Value )
}
lastLine .Stop = lastLine .Start + start .Start
node .Lines ().Set (lastIndex , lastLine )
}