Allow other inline block inside
This commit is contained in:
parent
1b1cb0929b
commit
9dc65b1c1e
@ -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
|
||||
|
@ -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
151
parser.go
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user