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.
|
[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
|
```markdown
|
||||||
[Attention]{.underline} some text
|
[This is *some text*]{.class key="val"} outside text
|
||||||
```
|
```
|
||||||
|
|
||||||
```html
|
```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
|
```go
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func TestAttributes(t *testing.T) {
|
func TestAttributes(t *testing.T) {
|
||||||
source := []byte(`
|
source := []byte(`
|
||||||
[Text underlined]{.underline}
|
[This is *some text*]{.class key="val"} outside text
|
||||||
`)
|
`)
|
||||||
|
|
||||||
var md = goldmark.New(Enable)
|
var md = goldmark.New(Enable)
|
||||||
|
151
parser.go
151
parser.go
@ -1,13 +1,16 @@
|
|||||||
package attributes
|
package attributes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var spanContentStateKey = parser.NewContextKey()
|
||||||
|
|
||||||
type attributesParser struct {
|
type attributesParser struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,35 +23,155 @@ func NewAttributesParser() parser.InlineParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *attributesParser) Trigger() []byte {
|
func (s *attributesParser) Trigger() []byte {
|
||||||
return []byte{'['}
|
return []byte{'[', ']'}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var attributeBottom = parser.NewContextKey()
|
||||||
|
|
||||||
func (s *attributesParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
func (s *attributesParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||||
savedLine, savedPosition := block.Position()
|
|
||||||
|
|
||||||
line, seg := block.PeekLine()
|
line, seg := block.PeekLine()
|
||||||
|
|
||||||
endText := bytes.Index(line, []byte{']'})
|
if line[0] == '[' {
|
||||||
if endText < 0 {
|
pc.Set(attributeBottom, pc.LastDelimiter())
|
||||||
return nil // must close on the same line
|
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
|
return nil
|
||||||
}
|
}
|
||||||
block.Advance(endText + 1)
|
|
||||||
|
|
||||||
attrs, ok := parser.ParseAttributes(block)
|
attrs, ok := parser.ParseAttributes(block)
|
||||||
if !ok {
|
if !ok {
|
||||||
block.SetPosition(savedLine, savedPosition)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &Node{}
|
span := &Node{}
|
||||||
for _, attr := range attrs {
|
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)))
|
var bottom ast.Node
|
||||||
return n
|
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