mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04410ff159 | ||
|
|
f7b2d24c09 | ||
|
|
ba49c5c69d | ||
|
|
c05fb087a4 | ||
|
|
b39daae79e | ||
|
|
d9c03f07f0 | ||
|
|
65dcf6cd0a | ||
|
|
ad1565131a | ||
|
|
bc993b4f59 | ||
|
|
41273a4d07 |
14 changed files with 463 additions and 106 deletions
|
|
@ -2,7 +2,7 @@ goldmark
|
|||
==========================================
|
||||
|
||||
[](https://pkg.go.dev/github.com/yuin/goldmark)
|
||||
[](https://github.com/yuin/goldmark/actions?query=workflow:test)
|
||||
[](https://github.com/yuin/goldmark/actions?query=workflow:test)
|
||||
[](https://coveralls.io/github/yuin/goldmark)
|
||||
[](https://goreportcard.com/report/github.com/yuin/goldmark)
|
||||
|
||||
|
|
@ -493,7 +493,7 @@ Extensions
|
|||
- [goldmark-d2](https://github.com/FurqanSoftware/goldmark-d2): Adds support for [D2](https://d2lang.com/) diagrams.
|
||||
- [goldmark-katex](https://github.com/FurqanSoftware/goldmark-katex): Adds support for [KaTeX](https://katex.org/) math and equations.
|
||||
- [goldmark-img64](https://github.com/tenkoh/goldmark-img64): Adds support for embedding images into the document as DataURL (base64 encoded).
|
||||
- [goldmark-enclave](https://github.com/quail-ink/goldmark-enclave): Adds support for embedding youtube/bilibili video, X's [oembed tweet](https://publish.twitter.com/), [tradingview](https://www.tradingview.com/widget/)'s chart, [quail](https://quail.ink)'s widget into the document.
|
||||
- [goldmark-enclave](https://github.com/quailyquaily/goldmark-enclave): Adds support for embedding youtube/bilibili video, X's [oembed X](https://publish.x.com/), [tradingview chart](https://www.tradingview.com/widget/)'s chart, [quaily widget](https://quaily.com), [spotify embeds](https://developer.spotify.com/documentation/embeds), [dify embed](https://dify.ai/) and html audio into the document.
|
||||
- [goldmark-wiki-table](https://github.com/movsb/goldmark-wiki-table): Adds support for embedding Wiki Tables.
|
||||
- [goldmark-tgmd](https://github.com/Mad-Pixels/goldmark-tgmd): A Telegram markdown renderer that can be passed to `goldmark.WithRenderer()`.
|
||||
|
||||
|
|
|
|||
|
|
@ -385,7 +385,8 @@ a* b c d *e*
|
|||
//- - - - - - - - -//
|
||||
x
|
||||
//- - - - - - - - -//
|
||||
<pre><code> x</code></pre>
|
||||
<pre><code> x
|
||||
</code></pre>
|
||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||
|
||||
26: NUL bytes must be replaced with U+FFFD
|
||||
|
|
@ -821,7 +822,7 @@ text" /></p>
|
|||
</blockquote>
|
||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||
|
||||
66: EOF should be rendered as a newline with an unclosed block
|
||||
66: EOF should be rendered as a newline with an unclosed block(w/ TAB)
|
||||
//- - - - - - - - -//
|
||||
> ```
|
||||
> 0
|
||||
|
|
@ -831,3 +832,14 @@ text" /></p>
|
|||
</code></pre>
|
||||
</blockquote>
|
||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||
|
||||
67: EOF should be rendered as a newline with an unclosed block
|
||||
//- - - - - - - - -//
|
||||
> ```
|
||||
> 0
|
||||
//- - - - - - - - -//
|
||||
<blockquote>
|
||||
<pre><code> 0
|
||||
</code></pre>
|
||||
</blockquote>
|
||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||
|
|
|
|||
15
ast/ast.go
15
ast/ast.go
|
|
@ -123,6 +123,12 @@ type Node interface {
|
|||
Dump(source []byte, level int)
|
||||
|
||||
// Text returns text values of this node.
|
||||
// This method is valid only for some inline nodes.
|
||||
// If this node is a block node, Text returns a text value as reasonable as possible.
|
||||
// Notice that there are no 'correct' text values for the block nodes.
|
||||
// Result for the block nodes may be different from your expectation.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. Pragraph.Lines, Text.Value).
|
||||
Text(source []byte) []byte
|
||||
|
||||
// HasBlankPreviousLines returns true if the row before this node is blank,
|
||||
|
|
@ -374,11 +380,18 @@ func (n *BaseNode) OwnerDocument() *Document {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Text implements Node.Text .
|
||||
// Text implements Node.Text .
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. Pragraph.Lines, Text.Value).
|
||||
func (n *BaseNode) Text(source []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
for c := n.firstChild; c != nil; c = c.NextSibling() {
|
||||
buf.Write(c.Text(source))
|
||||
if sb, ok := c.(interface {
|
||||
SoftLineBreak() bool
|
||||
}); ok && sb.SoftLineBreak() {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,10 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/yuin/goldmark/text"
|
||||
)
|
||||
|
||||
func TestRemoveChildren(t *testing.T) {
|
||||
root := NewDocument()
|
||||
|
||||
node1 := NewDocument()
|
||||
|
||||
node2 := NewDocument()
|
||||
|
||||
root.AppendChild(root, node1)
|
||||
root.AppendChild(root, node2)
|
||||
|
||||
root.RemoveChildren(root)
|
||||
|
||||
t.Logf("%+v", node2.PreviousSibling())
|
||||
}
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -76,48 +58,3 @@ func node(n Node, children ...Node) Node {
|
|||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func TestBaseBlock_Text(t *testing.T) {
|
||||
source := []byte(`# Heading
|
||||
|
||||
code block here
|
||||
and also here
|
||||
|
||||
A paragraph
|
||||
|
||||
` + "```" + `somelang
|
||||
fenced code block
|
||||
` + "```" + `
|
||||
|
||||
The end`)
|
||||
|
||||
t.Run("fetch text from code block", func(t *testing.T) {
|
||||
block := NewCodeBlock()
|
||||
block.lines = text.NewSegments()
|
||||
block.lines.Append(text.Segment{Start: 15, Stop: 31})
|
||||
block.lines.Append(text.Segment{Start: 32, Stop: 46})
|
||||
|
||||
expected := []byte("code block here\nand also here\n")
|
||||
if !bytes.Equal(expected, block.Text(source)) {
|
||||
t.Errorf("Expected: %q, got: %q", string(expected), string(block.Text(source)))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fetch text from fenced code block", func(t *testing.T) {
|
||||
block := NewFencedCodeBlock(&Text{
|
||||
Segment: text.Segment{Start: 63, Stop: 71},
|
||||
})
|
||||
block.lines = text.NewSegments()
|
||||
block.lines.Append(text.Segment{Start: 72, Stop: 90})
|
||||
|
||||
expectedLang := []byte("somelang")
|
||||
if !bytes.Equal(expectedLang, block.Language(source)) {
|
||||
t.Errorf("Expected: %q, got: %q", string(expectedLang), string(block.Language(source)))
|
||||
}
|
||||
|
||||
expected := []byte("fenced code block\n")
|
||||
if !bytes.Equal(expected, block.Text(source)) {
|
||||
t.Errorf("Expected: %q, got: %q", string(expected), string(block.Text(source)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
49
ast/block.go
49
ast/block.go
|
|
@ -1,7 +1,6 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
|
@ -48,15 +47,6 @@ func (b *BaseBlock) SetLines(v *textm.Segments) {
|
|||
b.lines = v
|
||||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
func (b *BaseBlock) Text(source []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
for _, line := range b.Lines().Sliced(0, b.Lines().Len()) {
|
||||
buf.Write(line.Value(source))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// A Document struct is a root node of Markdown text.
|
||||
type Document struct {
|
||||
BaseBlock
|
||||
|
|
@ -140,6 +130,13 @@ func (n *TextBlock) Kind() NodeKind {
|
|||
return KindTextBlock
|
||||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. TextBlock.Lines).
|
||||
func (n *TextBlock) Text(source []byte) []byte {
|
||||
return n.Lines().Value(source)
|
||||
}
|
||||
|
||||
// NewTextBlock returns a new TextBlock node.
|
||||
func NewTextBlock() *TextBlock {
|
||||
return &TextBlock{
|
||||
|
|
@ -165,6 +162,13 @@ func (n *Paragraph) Kind() NodeKind {
|
|||
return KindParagraph
|
||||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. Paragraph.Lines).
|
||||
func (n *Paragraph) Text(source []byte) []byte {
|
||||
return n.Lines().Value(source)
|
||||
}
|
||||
|
||||
// NewParagraph returns a new Paragraph node.
|
||||
func NewParagraph() *Paragraph {
|
||||
return &Paragraph{
|
||||
|
|
@ -259,6 +263,13 @@ func (n *CodeBlock) Kind() NodeKind {
|
|||
return KindCodeBlock
|
||||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. CodeBlock.Lines).
|
||||
func (n *CodeBlock) Text(source []byte) []byte {
|
||||
return n.Lines().Value(source)
|
||||
}
|
||||
|
||||
// NewCodeBlock returns a new CodeBlock node.
|
||||
func NewCodeBlock() *CodeBlock {
|
||||
return &CodeBlock{
|
||||
|
|
@ -314,6 +325,13 @@ func (n *FencedCodeBlock) Kind() NodeKind {
|
|||
return KindFencedCodeBlock
|
||||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. FencedCodeBlock.Lines).
|
||||
func (n *FencedCodeBlock) Text(source []byte) []byte {
|
||||
return n.Lines().Value(source)
|
||||
}
|
||||
|
||||
// NewFencedCodeBlock return a new FencedCodeBlock node.
|
||||
func NewFencedCodeBlock(info *Text) *FencedCodeBlock {
|
||||
return &FencedCodeBlock{
|
||||
|
|
@ -508,6 +526,17 @@ func (n *HTMLBlock) Kind() NodeKind {
|
|||
return KindHTMLBlock
|
||||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. HTMLBlock.Lines).
|
||||
func (n *HTMLBlock) Text(source []byte) []byte {
|
||||
ret := n.Lines().Value(source)
|
||||
if n.HasClosure() {
|
||||
ret = append(ret, n.ClosureLine.Value(source)...)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// NewHTMLBlock returns a new HTMLBlock node.
|
||||
func NewHTMLBlock(typ HTMLBlockType) *HTMLBlock {
|
||||
return &HTMLBlock{
|
||||
|
|
|
|||
|
|
@ -143,17 +143,25 @@ func (n *Text) Merge(node Node, source []byte) bool {
|
|||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. Text.Value).
|
||||
func (n *Text) Text(source []byte) []byte {
|
||||
return n.Segment.Value(source)
|
||||
}
|
||||
|
||||
// Value returns a value of this node.
|
||||
// SoftLineBreaks are not included in the returned value.
|
||||
func (n *Text) Value(source []byte) []byte {
|
||||
return n.Segment.Value(source)
|
||||
}
|
||||
|
||||
// Dump implements Node.Dump.
|
||||
func (n *Text) Dump(source []byte, level int) {
|
||||
fs := textFlagsString(n.flags)
|
||||
if len(fs) != 0 {
|
||||
fs = "(" + fs + ")"
|
||||
}
|
||||
fmt.Printf("%sText%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Text(source)), "\n"))
|
||||
fmt.Printf("%sText%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Value(source)), "\n"))
|
||||
}
|
||||
|
||||
// KindText is a NodeKind of the Text node.
|
||||
|
|
@ -258,6 +266,8 @@ func (n *String) SetCode(v bool) {
|
|||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. String.Value).
|
||||
func (n *String) Text(source []byte) []byte {
|
||||
return n.Value
|
||||
}
|
||||
|
|
@ -492,15 +502,22 @@ func (n *AutoLink) URL(source []byte) []byte {
|
|||
ret := make([]byte, 0, len(n.Protocol)+s.Len()+3)
|
||||
ret = append(ret, n.Protocol...)
|
||||
ret = append(ret, ':', '/', '/')
|
||||
ret = append(ret, n.value.Text(source)...)
|
||||
ret = append(ret, n.value.Value(source)...)
|
||||
return ret
|
||||
}
|
||||
return n.value.Text(source)
|
||||
return n.value.Value(source)
|
||||
}
|
||||
|
||||
// Label returns a label of this node.
|
||||
func (n *AutoLink) Label(source []byte) []byte {
|
||||
return n.value.Text(source)
|
||||
return n.value.Value(source)
|
||||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. AutoLink.Label).
|
||||
func (n *AutoLink) Text(source []byte) []byte {
|
||||
return n.value.Value(source)
|
||||
}
|
||||
|
||||
// NewAutoLink returns a new AutoLink node.
|
||||
|
|
@ -541,6 +558,13 @@ func (n *RawHTML) Kind() NodeKind {
|
|||
return KindRawHTML
|
||||
}
|
||||
|
||||
// Text implements Node.Text.
|
||||
//
|
||||
// Deprecated: Use other properties of the node to get the text value(i.e. RawHTML.Segments).
|
||||
func (n *RawHTML) Text(source []byte) []byte {
|
||||
return n.Segments.Value(source)
|
||||
}
|
||||
|
||||
// NewRawHTML returns a new RawHTML node.
|
||||
func NewRawHTML() *RawHTML {
|
||||
return &RawHTML{
|
||||
|
|
|
|||
204
ast_test.go
Normal file
204
ast_test.go
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
package goldmark_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
. "github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/testutil"
|
||||
"github.com/yuin/goldmark/text"
|
||||
)
|
||||
|
||||
func TestASTBlockNodeText(t *testing.T) {
|
||||
var cases = []struct {
|
||||
Name string
|
||||
Source string
|
||||
T1 string
|
||||
T2 string
|
||||
C bool
|
||||
}{
|
||||
{
|
||||
Name: "AtxHeading",
|
||||
Source: `# l1
|
||||
|
||||
a
|
||||
|
||||
# l2`,
|
||||
T1: `l1`,
|
||||
T2: `l2`,
|
||||
},
|
||||
{
|
||||
Name: "SetextHeading",
|
||||
Source: `l1
|
||||
l2
|
||||
===============
|
||||
|
||||
a
|
||||
|
||||
l3
|
||||
l4
|
||||
==============`,
|
||||
T1: `l1
|
||||
l2`,
|
||||
T2: `l3
|
||||
l4`,
|
||||
},
|
||||
{
|
||||
Name: "CodeBlock",
|
||||
Source: ` l1
|
||||
l2
|
||||
|
||||
a
|
||||
|
||||
l3
|
||||
l4`,
|
||||
T1: `l1
|
||||
l2
|
||||
`,
|
||||
T2: `l3
|
||||
l4
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "FencedCodeBlock",
|
||||
Source: "```" + `
|
||||
l1
|
||||
l2
|
||||
` + "```" + `
|
||||
|
||||
a
|
||||
|
||||
` + "```" + `
|
||||
l3
|
||||
l4`,
|
||||
T1: `l1
|
||||
l2
|
||||
`,
|
||||
T2: `l3
|
||||
l4
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "Blockquote",
|
||||
Source: `> l1
|
||||
> l2
|
||||
|
||||
a
|
||||
|
||||
> l3
|
||||
> l4`,
|
||||
T1: `l1
|
||||
l2`,
|
||||
T2: `l3
|
||||
l4`,
|
||||
},
|
||||
{
|
||||
Name: "List",
|
||||
Source: `- l1
|
||||
l2
|
||||
|
||||
a
|
||||
|
||||
- l3
|
||||
l4`,
|
||||
T1: `l1
|
||||
l2`,
|
||||
T2: `l3
|
||||
l4`,
|
||||
C: true,
|
||||
},
|
||||
{
|
||||
Name: "HTMLBlock",
|
||||
Source: `<div>
|
||||
l1
|
||||
l2
|
||||
</div>
|
||||
|
||||
a
|
||||
|
||||
<div>
|
||||
l3
|
||||
l4`,
|
||||
T1: `<div>
|
||||
l1
|
||||
l2
|
||||
</div>
|
||||
`,
|
||||
T2: `<div>
|
||||
l3
|
||||
l4`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cs := range cases {
|
||||
t.Run(cs.Name, func(t *testing.T) {
|
||||
s := []byte(cs.Source)
|
||||
md := New()
|
||||
n := md.Parser().Parse(text.NewReader(s))
|
||||
c1 := n.FirstChild()
|
||||
c2 := c1.NextSibling().NextSibling()
|
||||
if cs.C {
|
||||
c1 = c1.FirstChild()
|
||||
c2 = c2.FirstChild()
|
||||
}
|
||||
if !bytes.Equal(c1.Text(s), []byte(cs.T1)) { // nolint: staticcheck
|
||||
|
||||
t.Errorf("%s unmatch: %s", cs.Name, testutil.DiffPretty(c1.Text(s), []byte(cs.T1))) // nolint: staticcheck
|
||||
|
||||
}
|
||||
if !bytes.Equal(c2.Text(s), []byte(cs.T2)) { // nolint: staticcheck
|
||||
|
||||
t.Errorf("%s(EOF) unmatch: %s", cs.Name, testutil.DiffPretty(c2.Text(s), []byte(cs.T2))) // nolint: staticcheck
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestASTInlineNodeText(t *testing.T) {
|
||||
var cases = []struct {
|
||||
Name string
|
||||
Source string
|
||||
T1 string
|
||||
}{
|
||||
{
|
||||
Name: "CodeSpan",
|
||||
Source: "`c1`",
|
||||
T1: `c1`,
|
||||
},
|
||||
{
|
||||
Name: "Emphasis",
|
||||
Source: `*c1 **c2***`,
|
||||
T1: `c1 c2`,
|
||||
},
|
||||
{
|
||||
Name: "Link",
|
||||
Source: `[label](url)`,
|
||||
T1: `label`,
|
||||
},
|
||||
{
|
||||
Name: "AutoLink",
|
||||
Source: `<http://url>`,
|
||||
T1: `http://url`,
|
||||
},
|
||||
{
|
||||
Name: "RawHTML",
|
||||
Source: `<span>c1</span>`,
|
||||
T1: `<span>`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cs := range cases {
|
||||
t.Run(cs.Name, func(t *testing.T) {
|
||||
s := []byte(cs.Source)
|
||||
md := New()
|
||||
n := md.Parser().Parse(text.NewReader(s))
|
||||
c1 := n.FirstChild().FirstChild()
|
||||
if !bytes.Equal(c1.Text(s), []byte(cs.T1)) { // nolint: staticcheck
|
||||
t.Errorf("%s unmatch:\n%s", cs.Name, testutil.DiffPretty(c1.Text(s), []byte(cs.T1))) // nolint: staticcheck
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -150,7 +150,8 @@ on two lines.</p>
|
|||
//- - - - - - - - -//
|
||||
<dl>
|
||||
<dt>0</dt>
|
||||
<dd><pre><code> 0</code></pre>
|
||||
<dd><pre><code> 0
|
||||
</code></pre>
|
||||
</dd>
|
||||
</dl>
|
||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||
|
|
|
|||
123
extension/ast_test.go
Normal file
123
extension/ast_test.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
package extension
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
"github.com/yuin/goldmark/testutil"
|
||||
"github.com/yuin/goldmark/text"
|
||||
)
|
||||
|
||||
func TestASTBlockNodeText(t *testing.T) {
|
||||
var cases = []struct {
|
||||
Name string
|
||||
Source string
|
||||
T1 string
|
||||
T2 string
|
||||
C bool
|
||||
}{
|
||||
{
|
||||
Name: "DefinitionList",
|
||||
Source: `c1
|
||||
: c2
|
||||
c3
|
||||
|
||||
a
|
||||
|
||||
c4
|
||||
: c5
|
||||
c6`,
|
||||
T1: `c1c2
|
||||
c3`,
|
||||
T2: `c4c5
|
||||
c6`,
|
||||
},
|
||||
{
|
||||
Name: "Table",
|
||||
Source: `| h1 | h2 |
|
||||
| -- | -- |
|
||||
| c1 | c2 |
|
||||
|
||||
a
|
||||
|
||||
|
||||
| h3 | h4 |
|
||||
| -- | -- |
|
||||
| c3 | c4 |`,
|
||||
|
||||
T1: `h1h2c1c2`,
|
||||
T2: `h3h4c3c4`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cs := range cases {
|
||||
t.Run(cs.Name, func(t *testing.T) {
|
||||
s := []byte(cs.Source)
|
||||
md := goldmark.New(
|
||||
goldmark.WithRendererOptions(
|
||||
html.WithUnsafe(),
|
||||
),
|
||||
goldmark.WithExtensions(
|
||||
DefinitionList,
|
||||
Table,
|
||||
),
|
||||
)
|
||||
n := md.Parser().Parse(text.NewReader(s))
|
||||
c1 := n.FirstChild()
|
||||
c2 := c1.NextSibling().NextSibling()
|
||||
if cs.C {
|
||||
c1 = c1.FirstChild()
|
||||
c2 = c2.FirstChild()
|
||||
}
|
||||
if !bytes.Equal(c1.Text(s), []byte(cs.T1)) { // nolint: staticcheck
|
||||
|
||||
t.Errorf("%s unmatch:\n%s", cs.Name, testutil.DiffPretty(c1.Text(s), []byte(cs.T1))) // nolint: staticcheck
|
||||
|
||||
}
|
||||
if !bytes.Equal(c2.Text(s), []byte(cs.T2)) { // nolint: staticcheck
|
||||
|
||||
t.Errorf("%s(EOF) unmatch: %s", cs.Name, testutil.DiffPretty(c2.Text(s), []byte(cs.T2))) // nolint: staticcheck
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestASTInlineNodeText(t *testing.T) {
|
||||
var cases = []struct {
|
||||
Name string
|
||||
Source string
|
||||
T1 string
|
||||
}{
|
||||
{
|
||||
Name: "Strikethrough",
|
||||
Source: `~c1 *c2*~`,
|
||||
T1: `c1 c2`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cs := range cases {
|
||||
t.Run(cs.Name, func(t *testing.T) {
|
||||
s := []byte(cs.Source)
|
||||
md := goldmark.New(
|
||||
goldmark.WithRendererOptions(
|
||||
html.WithUnsafe(),
|
||||
),
|
||||
goldmark.WithExtensions(
|
||||
Strikethrough,
|
||||
),
|
||||
)
|
||||
n := md.Parser().Parse(text.NewReader(s))
|
||||
c1 := n.FirstChild().FirstChild()
|
||||
if !bytes.Equal(c1.Text(s), []byte(cs.T1)) { // nolint: staticcheck
|
||||
|
||||
t.Errorf("%s unmatch:\n%s", cs.Name, testutil.DiffPretty(c1.Text(s), []byte(cs.T1))) // nolint: staticcheck
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ func (b *codeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context)
|
|||
if segment.Padding != 0 {
|
||||
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
||||
}
|
||||
segment.ForceNewline = true
|
||||
node.Lines().Append(segment)
|
||||
reader.Advance(segment.Len() - 1)
|
||||
return node, NoChildren
|
||||
|
|
@ -59,6 +60,7 @@ func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
|
|||
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
||||
}
|
||||
|
||||
segment.ForceNewline = true
|
||||
node.Lines().Append(segment)
|
||||
reader.Advance(segment.Len() - 1)
|
||||
return Continue | NoChildren
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc C
|
|||
if padding != 0 {
|
||||
preserveLeadingTabInCodeBlock(&seg, reader, fdata.indent)
|
||||
}
|
||||
seg.ForceNewline = true // EOF as newline
|
||||
node.Lines().Append(seg)
|
||||
reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
|
||||
return Continue | NoChildren
|
||||
|
|
|
|||
|
|
@ -878,12 +878,6 @@ func (p *parser) Parse(reader text.Reader, opts ...ParseOption) ast.Node {
|
|||
blockReader := text.NewBlockReader(reader.Source(), nil)
|
||||
p.walkBlock(root, func(node ast.Node) {
|
||||
p.parseBlock(blockReader, node, pc)
|
||||
lines := node.Lines()
|
||||
if lines != nil && lines.Len() != 0 {
|
||||
s := lines.At(lines.Len() - 1)
|
||||
s.EOB = true
|
||||
lines.Set(lines.Len()-1, s)
|
||||
}
|
||||
})
|
||||
for _, at := range p.astTransformers {
|
||||
at.Transform(root, reader, pc)
|
||||
|
|
|
|||
|
|
@ -680,7 +680,7 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, e
|
|||
_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
||||
}
|
||||
_, _ = w.WriteString(`" alt="`)
|
||||
r.renderAttribute(w, source, n)
|
||||
r.renderTexts(w, source, n)
|
||||
_ = w.WriteByte('"')
|
||||
if n.Title != nil {
|
||||
_, _ = w.WriteString(` title="`)
|
||||
|
|
@ -737,7 +737,7 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en
|
|||
if r.EastAsianLineBreaks != EastAsianLineBreaksNone && len(value) != 0 {
|
||||
sibling := node.NextSibling()
|
||||
if sibling != nil && sibling.Kind() == ast.KindText {
|
||||
if siblingText := sibling.(*ast.Text).Text(source); len(siblingText) != 0 {
|
||||
if siblingText := sibling.(*ast.Text).Value(source); len(siblingText) != 0 {
|
||||
thisLastRune := util.ToRune(value, len(value)-1)
|
||||
siblingFirstRune, _ := utf8.DecodeRune(siblingText)
|
||||
if r.EastAsianLineBreaks.softLineBreak(thisLastRune, siblingFirstRune) {
|
||||
|
|
@ -770,19 +770,14 @@ func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node,
|
|||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *Renderer) renderAttribute(w util.BufWriter, source []byte, n ast.Node) {
|
||||
func (r *Renderer) renderTexts(w util.BufWriter, source []byte, n ast.Node) {
|
||||
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||
if s, ok := c.(*ast.String); ok {
|
||||
_, _ = r.renderString(w, source, s, true)
|
||||
} else if t, ok := c.(*ast.String); ok {
|
||||
} else if t, ok := c.(*ast.Text); ok {
|
||||
_, _ = r.renderText(w, source, t, true)
|
||||
} else if !c.HasChildren() {
|
||||
r.Writer.Write(w, c.Text(source))
|
||||
if t, ok := c.(*ast.Text); ok && t.SoftLineBreak() {
|
||||
_ = w.WriteByte('\n')
|
||||
}
|
||||
} else {
|
||||
r.renderAttribute(w, source, c)
|
||||
r.renderTexts(w, source, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,19 @@ type Segment struct {
|
|||
// Padding is a padding length of the segment.
|
||||
Padding int
|
||||
|
||||
// EOB is true if the segment is end of the block.
|
||||
EOB bool
|
||||
// ForceNewline is true if the segment should be ended with a newline.
|
||||
// Some elements(i.e. CodeBlock, FencedCodeBlock) does not trim trailing
|
||||
// newlines. Spec defines that EOF is treated as a newline, so we need to
|
||||
// add a newline to the end of the segment if it is not empty.
|
||||
//
|
||||
// i.e.:
|
||||
//
|
||||
// ```go
|
||||
// const test = "test"
|
||||
//
|
||||
// This code does not close the code block and ends with EOF. In this case,
|
||||
// we need to add a newline to the end of the last line like `const test = "test"\n`.
|
||||
ForceNewline bool
|
||||
}
|
||||
|
||||
// NewSegment return a new Segment.
|
||||
|
|
@ -44,13 +55,15 @@ func NewSegmentPadding(start, stop, n int) Segment {
|
|||
|
||||
// Value returns a value of the segment.
|
||||
func (t *Segment) Value(buffer []byte) []byte {
|
||||
var result []byte
|
||||
if t.Padding == 0 {
|
||||
return buffer[t.Start:t.Stop]
|
||||
result = buffer[t.Start:t.Stop]
|
||||
} else {
|
||||
result = make([]byte, 0, t.Padding+t.Stop-t.Start+1)
|
||||
result = append(result, bytes.Repeat(space, t.Padding)...)
|
||||
result = append(result, buffer[t.Start:t.Stop]...)
|
||||
}
|
||||
result := make([]byte, 0, t.Padding+t.Stop-t.Start+1)
|
||||
result = append(result, bytes.Repeat(space, t.Padding)...)
|
||||
result = append(result, buffer[t.Start:t.Stop]...)
|
||||
if t.EOB && len(result) > 0 && result[len(result)-1] != '\n' {
|
||||
if t.ForceNewline && len(result) > 0 && result[len(result)-1] != '\n' {
|
||||
result = append(result, '\n')
|
||||
}
|
||||
return result
|
||||
|
|
@ -215,3 +228,12 @@ func (s *Segments) Unshift(v Segment) {
|
|||
s.values = append(s.values[0:1], s.values[0:]...)
|
||||
s.values[0] = v
|
||||
}
|
||||
|
||||
// Value returns a string value of the collection.
|
||||
func (s *Segments) Value(buffer []byte) []byte {
|
||||
var result []byte
|
||||
for _, v := range s.values {
|
||||
result = append(result, v.Value(buffer)...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue