Skip to content

Commit

Permalink
✨ 支持 kramdown 行级属性列表 #89
Browse files Browse the repository at this point in the history
  • Loading branch information
88250 committed Sep 12, 2020
1 parent 51b905e commit e7fe5f0
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 21 deletions.
13 changes: 7 additions & 6 deletions ast/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ type Node struct {

// 节点基础结构

ID string `json:",omitempty"` // 节点的唯一标识
URL string `json:"-"` // 地址部分
Path string `json:"-"` // 地址路径部分
Memo string `json:",omitempty"` // 节点备注
Bookmark string `json:",omitempty"` // 节点书签
Aliases []string `json:",omitempty"` // 节点别名
ID string `json:",omitempty"` // 节点的唯一标识
URL string `json:"-"` // 地址部分
Path string `json:"-"` // 地址路径部分
Bookmark string `json:",omitempty"` // 节点书签

Type NodeType // 节点类型
Parent *Node `json:"-"` // 父节点
Expand Down Expand Up @@ -107,6 +105,9 @@ type Node struct {
// HTML 实体

HtmlEntityTokens []byte `json:",omitempty"` // 原始输入的实体 tokens,&

// Kramdown 行级属性列表
KramdownIAL [][]string
}

// ListData 用于记录列表或列表项节点的附加信息。
Expand Down
58 changes: 53 additions & 5 deletions parse/inline_attribute_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,64 @@

package parse

import "github.com/88250/lute/ast"
import (
"bytes"
"github.com/88250/lute/ast"
"github.com/88250/lute/util"
)

func (context *Context) parseKramdownIAL(block *ast.Node) {
if !context.Option.KramdownIAL {
var openCurlyBraceColon = util.StrToBytes("{:")
var emptyIAL = util.StrToBytes("{:}")

func (t *Tree) parseKramdownIALs() {
if !t.Context.Option.KramdownIAL {
return
}

t.parseKramdownIAL0(t.Root)
}

func (t *Tree) parseKramdownIAL0(node *ast.Node) {
if ast.NodeText == node.Type {
if curlyBracesStart := bytes.Index(node.Tokens, []byte("{:")); 0 <= curlyBracesStart {
content := node.Tokens[curlyBracesStart+2:]
curlyBracesEnd := bytes.Index(content, closeCurlyBrace)
if 3 > curlyBracesEnd {
goto Continue
}

content = content[:len(content)-1]
for {
valid, remains, attr, name, val := t.parseTagAttr(content)
if !valid {
break
}

switch block.Type {
case ast.NodeParagraph:
content = remains
if 1 > len(attr) {
break
}

node.Parent.KramdownIAL = append(node.Parent.KramdownIAL, []string{util.BytesToStr(name), util.BytesToStr(val)})
node.Tokens = bytes.Replace(node.Tokens, attr, nil, 1)
}

if bytes.Equal(emptyIAL, node.Tokens) {
if nil != node.Previous && ast.NodeSoftBreak == node.Previous.Type {
node.Previous.Unlink()
}
parent := node.Parent
node.Unlink()
if nil != parent && nil == parent.FirstChild { // 如果父节点已经没有子节点,说明这个父节点应该指向它的前一个兄弟节点
parent.Previous.KramdownIAL = parent.KramdownIAL
parent.Unlink()
}
}
}
}

Continue: // 遍历处理子节点
for child := node.FirstChild; nil != child; child = child.Next {
t.parseKramdownIAL0(child)
}
}
8 changes: 6 additions & 2 deletions parse/inline_html.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (t *Tree) parseInlineHTML(ctx *InlineContext) (ret *ast.Node) {
tags = append(tags, tagName...)
tokens = remains
for {
valid, remains, attr := t.parseTagAttr(tokens)
valid, remains, attr, _, _ := t.parseTagAttr(tokens)
if !valid {
ctx.pos++
return
Expand Down Expand Up @@ -278,7 +278,7 @@ func (t *Tree) parseHTMLComment(tokens []byte) (valid bool, remains, comment []b
return
}

func (t *Tree) parseTagAttr(tokens []byte) (valid bool, remains, attr []byte) {
func (t *Tree) parseTagAttr(tokens []byte) (valid bool, remains, attr, name, val []byte) {
valid = true
remains = tokens
var whitespaces []byte
Expand Down Expand Up @@ -311,6 +311,10 @@ func (t *Tree) parseTagAttr(tokens []byte) (valid bool, remains, attr []byte) {
attr = append(attr, whitespaces...)
attr = append(attr, attrName...)
attr = append(attr, valSpec...)
if nil != valSpec {
name = attrName
val = valSpec[2 : len(valSpec)-1]
}
return
}

Expand Down
2 changes: 0 additions & 2 deletions parse/paragraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,9 @@ func paragraphFinalize(p *ast.Node, context *Context) (insertTable bool) {
if nil != paragraph {
p.Tokens = paragraph.Tokens
p.InsertAfter(table)
context.parseKramdownIAL(p)
// 设置末梢及其状态
table.Close = true
context.Tip = table
context.parseKramdownIAL(table)
return true
} else {
// 将该段落节点转成表节点
Expand Down
4 changes: 3 additions & 1 deletion parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func Parse(name string, markdown []byte, options *Options) (tree *Tree) {
tree.parseBlocks()
tree.parseInlines()
tree.lexer = nil
tree.parseKramdownIALs()
return
}

Expand All @@ -37,6 +38,8 @@ func Inline(name string, markdown []byte, options *Options) (tree *Tree) {
tree.Root = &ast.Node{Type: ast.NodeDocument}
tree.Root.AppendChild(&ast.Node{Type: ast.NodeParagraph, Tokens: markdown})
tree.parseInlines()
tree.lexer = nil
tree.parseKramdownIALs()
return
}

Expand Down Expand Up @@ -171,7 +174,6 @@ func (context *Context) finalize(block *ast.Node, lineNum int) {
context.listFinalize(block)
}

context.parseKramdownIAL(block)
context.Tip = parent
}

Expand Down
12 changes: 7 additions & 5 deletions render/html_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ func (r *HtmlRenderer) renderParagraph(node *ast.Node, entering bool) ast.WalkSt

if entering {
r.Newline()
r.tag("p", nil, false)
r.tag("p", node.KramdownIAL, false)
if r.Option.ChineseParagraphBeginningSpace && ast.NodeDocument == node.Parent.Type {
r.WriteString("&emsp;&emsp;")
}
Expand Down Expand Up @@ -691,7 +691,7 @@ func (r *HtmlRenderer) renderStrongU8eCloseMarker(node *ast.Node, entering bool)
func (r *HtmlRenderer) renderBlockquote(node *ast.Node, entering bool) ast.WalkStatus {
if entering {
r.Newline()
r.WriteString("<blockquote>")
r.tag("blockquote", node.KramdownIAL, false)
r.Newline()
} else {
r.Newline()
Expand Down Expand Up @@ -750,6 +750,7 @@ func (r *HtmlRenderer) renderList(node *ast.Node, entering bool) ast.WalkStatus
if 0 == node.BulletChar && 1 != node.Start {
attrs = append(attrs, []string{"start", strconv.Itoa(node.Start)})
}
attrs = append(attrs, node.KramdownIAL...)
r.tag(tag, attrs, false)
r.Newline()
} else {
Expand All @@ -762,12 +763,13 @@ func (r *HtmlRenderer) renderList(node *ast.Node, entering bool) ast.WalkStatus

func (r *HtmlRenderer) renderListItem(node *ast.Node, entering bool) ast.WalkStatus {
if entering {
var attrs [][]string
attrs = append(attrs, node.KramdownIAL...)
if 3 == node.ListData.Typ && "" != r.Option.GFMTaskListItemClass &&
nil != node.FirstChild && nil != node.FirstChild.FirstChild && ast.NodeTaskListItemMarker == node.FirstChild.FirstChild.Type {
r.tag("li", [][]string{{"class", r.Option.GFMTaskListItemClass}}, false)
} else {
r.tag("li", nil, false)
attrs = append(attrs, []string{"class", r.Option.GFMTaskListItemClass})
}
r.tag("li", attrs, false)
} else {
r.tag("/li", nil, false)
r.Newline()
Expand Down
38 changes: 38 additions & 0 deletions test/kramdown_ial_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Lute - 一款对中文语境优化的 Markdown 引擎,支持 Go 和 JavaScript
// Copyright (c) 2019-present, b3log.org
//
// Lute is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

package test

import (
"testing"

"github.com/88250/lute"
)

var kramIALTests = []parseTest{

{"4", "* foo\n{: id=\"fooid\"}\n", "<blockquote id=\"bqid\">\n<p id=\"fooid\">foo</p>\n<p id=\"bazid\">baz</p>\n</blockquote>\n"},
{"3", "> foo\n> {: id=\"fooid\"}\n>\n> baz\n> {: id=\"bazid\"}\n>\n{: id=\"bqid\"}\n", "<blockquote id=\"bqid\">\n<p id=\"fooid\">foo</p>\n<p id=\"bazid\">baz</p>\n</blockquote>\n"},
{"2", "> foo\n> {: id=\"fooid\"}\n>\n{: id=\"bqid\"}\n", "<blockquote id=\"bqid\">\n<p id=\"fooid\">foo</p>\n</blockquote>\n"},
{"1", "> foo\n> {: id=\"fooid\" name=\"bar\"}\n", "<blockquote>\n<p id=\"fooid\" name=\"bar\">foo</p>\n</blockquote>\n"},
{"0", "foo\n{: id=\"fooid\" class=\"bar\"}\n", "<p id=\"fooid\" class=\"bar\">foo</p>\n"},
}

func TestKramIALs(t *testing.T) {
luteEngine := lute.New()
luteEngine.KramdownIAL = true

for _, test := range kramIALTests {
html := luteEngine.MarkdownStr(test.name, test.from)
if test.to != html {
t.Fatalf("test case [%s] failed\nexpected\n\t%q\ngot\n\t%q\noriginal markdown text\n\t%q", test.name, test.to, html, test.from)
}
}
}

0 comments on commit e7fe5f0

Please sign in to comment.