Allow other inline block inside

This commit is contained in:
nemunaire 2023-08-17 14:58:53 +02:00
parent 1b1cb0929b
commit 9dc65b1c1e
3 changed files with 142 additions and 17 deletions

View File

@ -2,12 +2,14 @@
[GoldMark](https://github.com/yuin/goldmark/) inline attributes extension.
This implements the [`bracketed_spans`](https://pandoc.org/MANUAL.html#extension-bracketed_spans) of pandoc.
```markdown
[Attention]{.underline} some text
[This is *some text*]{.class key="val"} outside text
```
```html
<p><span class="underline">Attention</span> some text</p>
<p><span class="class" key="val">This is <em>some text</em></span> outside text</p>
```
```go

View File

@ -10,7 +10,7 @@ import (
func TestAttributes(t *testing.T) {
source := []byte(`
[Text underlined]{.underline}
[This is *some text*]{.class key="val"} outside text
`)
var md = goldmark.New(Enable)

151
parser.go
View File

@ -1,13 +1,16 @@
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 {
}
@ -20,35 +23,155 @@ func NewAttributesParser() parser.InlineParser {
}
func (s *attributesParser) Trigger() []byte {
return []byte{'['}
return []byte{'[', ']'}
}
func (s *attributesParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
savedLine, savedPosition := block.Position()
var attributeBottom = parser.NewContextKey()
func (s *attributesParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, seg := block.PeekLine()
endText := bytes.Index(line, []byte{']'})
if endText < 0 {
return nil // must close on the same line
if line[0] == '[' {
pc.Set(attributeBottom, pc.LastDelimiter())
return processSpanContentOpen(block, seg.Start, pc)
}
if len(line) <= endText || line[endText+1] != '{' {
// line[0] == ']'
tlist := pc.Get(spanContentStateKey)
if tlist == nil {
return nil
}
last := tlist.(*spanContentState).Last
if last == nil {
return nil
}
block.Advance(1)
removeSpanContentState(pc, last)
c := block.Peek()
if c != '{' {
return nil
}
block.Advance(endText + 1)
attrs, ok := parser.ParseAttributes(block)
if !ok {
block.SetPosition(savedLine, savedPosition)
return nil
}
n := &Node{}
span := &Node{}
for _, attr := range attrs {
n.SetAttribute(attr.Name, attr.Value)
span.SetAttribute(attr.Name, attr.Value)
}
n.AppendChild(n, ast.NewTextSegment(text.NewSegment(seg.Start+1, seg.Start+endText)))
return n
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
}