mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Compare commits
No commits in common. "master" and "v1.7.1" have entirely different histories.
28 changed files with 2205 additions and 2826 deletions
12
.github/workflows/test.yaml
vendored
12
.github/workflows/test.yaml
vendored
|
|
@ -5,7 +5,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x, 1.22.x]
|
go-version: [1.19.x, 1.20.x]
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -16,18 +16,16 @@ jobs:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Run lints
|
- name: Run lints
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
if: "matrix.platform == 'ubuntu-latest'" # gofmt linter fails on Windows for CRLF problems
|
if: "matrix.platform == 'ubuntu-latest'" # gofmt linter fails on Windows for CRLF problems
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
env:
|
|
||||||
GOLDMARK_TEST_TIMEOUT_MULTIPLIER: 5
|
|
||||||
run: go test -v ./... -covermode=count -coverprofile=coverage.out -coverpkg=./...
|
run: go test -v ./... -covermode=count -coverprofile=coverage.out -coverpkg=./...
|
||||||
- name: Install goveralls
|
|
||||||
run: go install github.com/mattn/goveralls@latest
|
|
||||||
- name: Send coverage
|
- name: Send coverage
|
||||||
if: "matrix.platform == 'ubuntu-latest'"
|
if: "matrix.platform == 'ubuntu-latest'"
|
||||||
env:
|
env:
|
||||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: goveralls -coverprofile=coverage.out -service=github
|
run: |
|
||||||
|
GO111MODULE=off go get github.com/mattn/goveralls
|
||||||
|
$(go env GOPATH)/bin/goveralls -coverprofile=coverage.out -service=github
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ goldmark
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/yuin/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://coveralls.io/github/yuin/goldmark)
|
||||||
[](https://goreportcard.com/report/github.com/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-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-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-img64](https://github.com/tenkoh/goldmark-img64): Adds support for embedding images into the document as DataURL (base64 encoded).
|
||||||
- [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-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-wiki-table](https://github.com/movsb/goldmark-wiki-table): Adds support for embedding Wiki Tables.
|
- [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()`.
|
- [goldmark-tgmd](https://github.com/Mad-Pixels/goldmark-tgmd): A Telegram markdown renderer that can be passed to `goldmark.WithRenderer()`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ require (
|
||||||
github.com/88250/lute v1.7.5
|
github.com/88250/lute v1.7.5
|
||||||
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
|
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
|
||||||
github.com/russross/blackfriday/v2 v2.1.0
|
github.com/russross/blackfriday/v2 v2.1.0
|
||||||
github.com/yuin/goldmark v0.0.0
|
github.com/yuin/goldmark v1.2.1
|
||||||
gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
|
gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -22,4 +22,3 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
replace gopkg.in/russross/blackfriday.v2 v2.0.1 => github.com/russross/blackfriday/v2 v2.0.1
|
replace gopkg.in/russross/blackfriday.v2 v2.0.1 => github.com/russross/blackfriday/v2 v2.0.1
|
||||||
replace github.com/yuin/goldmark v0.0.0 => ../../
|
|
||||||
|
|
|
||||||
|
|
@ -385,8 +385,7 @@ a* b c d *e*
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
x
|
x
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<pre><code> x
|
<pre><code> x</code></pre>
|
||||||
</code></pre>
|
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
26: NUL bytes must be replaced with U+FFFD
|
26: NUL bytes must be replaced with U+FFFD
|
||||||
|
|
@ -781,65 +780,3 @@ text](logo.png)
|
||||||
<p><img src="logo.png" alt="alt
|
<p><img src="logo.png" alt="alt
|
||||||
text" /></p>
|
text" /></p>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
62: Image alt with an escaped character
|
|
||||||
//- - - - - - - - -//
|
|
||||||

|
|
||||||
//- - - - - - - - -//
|
|
||||||
<p><img src="https://example.com/img.png" alt="`alt" /></p>
|
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
|
||||||
|
|
||||||
63: Emphasis in link label
|
|
||||||
//- - - - - - - - -//
|
|
||||||
[*[a]*](b)
|
|
||||||
//- - - - - - - - -//
|
|
||||||
<p><a href="b"><em>[a]</em></a></p>
|
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
|
||||||
|
|
||||||
64: Nested list under an empty list item
|
|
||||||
//- - - - - - - - -//
|
|
||||||
-
|
|
||||||
- foo
|
|
||||||
//- - - - - - - - -//
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<ul>
|
|
||||||
<li>foo</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
|
||||||
|
|
||||||
65: Nested fenced code block with tab
|
|
||||||
//- - - - - - - - -//
|
|
||||||
> ```
|
|
||||||
> 0
|
|
||||||
> ```
|
|
||||||
//- - - - - - - - -//
|
|
||||||
<blockquote>
|
|
||||||
<pre><code> 0
|
|
||||||
</code></pre>
|
|
||||||
</blockquote>
|
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
|
||||||
|
|
||||||
66: EOF should be rendered as a newline with an unclosed block(w/ TAB)
|
|
||||||
//- - - - - - - - -//
|
|
||||||
> ```
|
|
||||||
> 0
|
|
||||||
//- - - - - - - - -//
|
|
||||||
<blockquote>
|
|
||||||
<pre><code> 0
|
|
||||||
</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,12 +123,6 @@ type Node interface {
|
||||||
Dump(source []byte, level int)
|
Dump(source []byte, level int)
|
||||||
|
|
||||||
// Text returns text values of this node.
|
// 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
|
Text(source []byte) []byte
|
||||||
|
|
||||||
// HasBlankPreviousLines returns true if the row before this node is blank,
|
// HasBlankPreviousLines returns true if the row before this node is blank,
|
||||||
|
|
@ -380,18 +374,11 @@ func (n *BaseNode) OwnerDocument() *Document {
|
||||||
return nil
|
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 {
|
func (n *BaseNode) Text(source []byte) []byte {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
for c := n.firstChild; c != nil; c = c.NextSibling() {
|
for c := n.firstChild; c != nil; c = c.NextSibling() {
|
||||||
buf.Write(c.Text(source))
|
buf.Write(c.Text(source))
|
||||||
if sb, ok := c.(interface {
|
|
||||||
SoftLineBreak() bool
|
|
||||||
}); ok && sb.SoftLineBreak() {
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,21 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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) {
|
func TestWalk(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
39
ast/block.go
39
ast/block.go
|
|
@ -130,13 +130,6 @@ func (n *TextBlock) Kind() NodeKind {
|
||||||
return KindTextBlock
|
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.
|
// NewTextBlock returns a new TextBlock node.
|
||||||
func NewTextBlock() *TextBlock {
|
func NewTextBlock() *TextBlock {
|
||||||
return &TextBlock{
|
return &TextBlock{
|
||||||
|
|
@ -162,13 +155,6 @@ func (n *Paragraph) Kind() NodeKind {
|
||||||
return KindParagraph
|
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.
|
// NewParagraph returns a new Paragraph node.
|
||||||
func NewParagraph() *Paragraph {
|
func NewParagraph() *Paragraph {
|
||||||
return &Paragraph{
|
return &Paragraph{
|
||||||
|
|
@ -263,13 +249,6 @@ func (n *CodeBlock) Kind() NodeKind {
|
||||||
return KindCodeBlock
|
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.
|
// NewCodeBlock returns a new CodeBlock node.
|
||||||
func NewCodeBlock() *CodeBlock {
|
func NewCodeBlock() *CodeBlock {
|
||||||
return &CodeBlock{
|
return &CodeBlock{
|
||||||
|
|
@ -325,13 +304,6 @@ func (n *FencedCodeBlock) Kind() NodeKind {
|
||||||
return KindFencedCodeBlock
|
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.
|
// NewFencedCodeBlock return a new FencedCodeBlock node.
|
||||||
func NewFencedCodeBlock(info *Text) *FencedCodeBlock {
|
func NewFencedCodeBlock(info *Text) *FencedCodeBlock {
|
||||||
return &FencedCodeBlock{
|
return &FencedCodeBlock{
|
||||||
|
|
@ -526,17 +498,6 @@ func (n *HTMLBlock) Kind() NodeKind {
|
||||||
return KindHTMLBlock
|
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.
|
// NewHTMLBlock returns a new HTMLBlock node.
|
||||||
func NewHTMLBlock(typ HTMLBlockType) *HTMLBlock {
|
func NewHTMLBlock(typ HTMLBlockType) *HTMLBlock {
|
||||||
return &HTMLBlock{
|
return &HTMLBlock{
|
||||||
|
|
|
||||||
|
|
@ -143,25 +143,17 @@ func (n *Text) Merge(node Node, source []byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text implements Node.Text.
|
// 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 {
|
func (n *Text) Text(source []byte) []byte {
|
||||||
return n.Segment.Value(source)
|
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.
|
// Dump implements Node.Dump.
|
||||||
func (n *Text) Dump(source []byte, level int) {
|
func (n *Text) Dump(source []byte, level int) {
|
||||||
fs := textFlagsString(n.flags)
|
fs := textFlagsString(n.flags)
|
||||||
if len(fs) != 0 {
|
if len(fs) != 0 {
|
||||||
fs = "(" + fs + ")"
|
fs = "(" + fs + ")"
|
||||||
}
|
}
|
||||||
fmt.Printf("%sText%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Value(source)), "\n"))
|
fmt.Printf("%sText%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Text(source)), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// KindText is a NodeKind of the Text node.
|
// KindText is a NodeKind of the Text node.
|
||||||
|
|
@ -266,8 +258,6 @@ func (n *String) SetCode(v bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text implements Node.Text.
|
// 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 {
|
func (n *String) Text(source []byte) []byte {
|
||||||
return n.Value
|
return n.Value
|
||||||
}
|
}
|
||||||
|
|
@ -502,22 +492,15 @@ func (n *AutoLink) URL(source []byte) []byte {
|
||||||
ret := make([]byte, 0, len(n.Protocol)+s.Len()+3)
|
ret := make([]byte, 0, len(n.Protocol)+s.Len()+3)
|
||||||
ret = append(ret, n.Protocol...)
|
ret = append(ret, n.Protocol...)
|
||||||
ret = append(ret, ':', '/', '/')
|
ret = append(ret, ':', '/', '/')
|
||||||
ret = append(ret, n.value.Value(source)...)
|
ret = append(ret, n.value.Text(source)...)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
return n.value.Value(source)
|
return n.value.Text(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label returns a label of this node.
|
// Label returns a label of this node.
|
||||||
func (n *AutoLink) Label(source []byte) []byte {
|
func (n *AutoLink) Label(source []byte) []byte {
|
||||||
return n.value.Value(source)
|
return n.value.Text(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.
|
// NewAutoLink returns a new AutoLink node.
|
||||||
|
|
@ -558,13 +541,6 @@ func (n *RawHTML) Kind() NodeKind {
|
||||||
return KindRawHTML
|
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.
|
// NewRawHTML returns a new RawHTML node.
|
||||||
func NewRawHTML() *RawHTML {
|
func NewRawHTML() *RawHTML {
|
||||||
return &RawHTML{
|
return &RawHTML{
|
||||||
|
|
|
||||||
204
ast_test.go
204
ast_test.go
|
|
@ -1,204 +0,0 @@
|
||||||
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,8 +150,7 @@ on two lines.</p>
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<dl>
|
<dl>
|
||||||
<dt>0</dt>
|
<dt>0</dt>
|
||||||
<dd><pre><code> 0
|
<dd><pre><code> 0</code></pre>
|
||||||
</code></pre>
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
<p><del>Hi</del> Hello, world!</p>
|
<p><del>Hi</del> Hello, world!</p>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2
|
2
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
This ~~has a
|
This ~~has a
|
||||||
|
|
@ -14,26 +16,3 @@ new paragraph~~.
|
||||||
<p>This ~~has a</p>
|
<p>This ~~has a</p>
|
||||||
<p>new paragraph~~.</p>
|
<p>new paragraph~~.</p>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
3
|
|
||||||
//- - - - - - - - -//
|
|
||||||
~Hi~ Hello, world!
|
|
||||||
//- - - - - - - - -//
|
|
||||||
<p><del>Hi</del> Hello, world!</p>
|
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
|
||||||
|
|
||||||
4: Three or more tildes do not create a strikethrough
|
|
||||||
//- - - - - - - - -//
|
|
||||||
This will ~~~not~~~ strike.
|
|
||||||
//- - - - - - - - -//
|
|
||||||
<p>This will ~~~not~~~ strike.</p>
|
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
|
||||||
|
|
||||||
5: Leading three or more tildes do not create a strikethrough, create a code block
|
|
||||||
//- - - - - - - - -//
|
|
||||||
~~~Hi~~~ Hello, world!
|
|
||||||
//- - - - - - - - -//
|
|
||||||
<pre><code class="language-Hi~~~"></code></pre>
|
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -46,11 +46,10 @@ func (s *strikethroughParser) Trigger() []byte {
|
||||||
func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
||||||
before := block.PrecendingCharacter()
|
before := block.PrecendingCharacter()
|
||||||
line, segment := block.PeekLine()
|
line, segment := block.PeekLine()
|
||||||
node := parser.ScanDelimiter(line, before, 1, defaultStrikethroughDelimiterProcessor)
|
node := parser.ScanDelimiter(line, before, 2, defaultStrikethroughDelimiterProcessor)
|
||||||
if node == nil || node.OriginalLength > 2 || before == '~' {
|
if node == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Segment = segment.WithStop(segment.Start + node.OriginalLength)
|
node.Segment = segment.WithStop(segment.Start + node.OriginalLength)
|
||||||
block.Advance(node.OriginalLength)
|
block.Advance(node.OriginalLength)
|
||||||
pc.PushDelimiter(node)
|
pc.PushDelimiter(node)
|
||||||
|
|
|
||||||
|
|
@ -184,11 +184,11 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
|
||||||
func (b *tableParagraphTransformer) parseRow(segment text.Segment,
|
func (b *tableParagraphTransformer) parseRow(segment text.Segment,
|
||||||
alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
|
alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
|
||||||
source := reader.Source()
|
source := reader.Source()
|
||||||
segment = segment.TrimLeftSpace(source)
|
|
||||||
segment = segment.TrimRightSpace(source)
|
|
||||||
line := segment.Value(source)
|
line := segment.Value(source)
|
||||||
pos := 0
|
pos := 0
|
||||||
|
pos += util.TrimLeftSpaceLength(line)
|
||||||
limit := len(line)
|
limit := len(line)
|
||||||
|
limit -= util.TrimRightSpaceLength(line)
|
||||||
row := ast.NewTableRow(alignments)
|
row := ast.NewTableRow(alignments)
|
||||||
if len(line) > 0 && line[pos] == '|' {
|
if len(line) > 0 && line[pos] == '|' {
|
||||||
pos++
|
pos++
|
||||||
|
|
@ -492,7 +492,7 @@ func (r *TableHTMLRenderer) renderTableCell(
|
||||||
tag = "th"
|
tag = "th"
|
||||||
}
|
}
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = fmt.Fprintf(w, "<%s", tag)
|
fmt.Fprintf(w, "<%s", tag)
|
||||||
if n.Alignment != ast.AlignNone {
|
if n.Alignment != ast.AlignNone {
|
||||||
amethod := r.TableConfig.TableCellAlignMethod
|
amethod := r.TableConfig.TableCellAlignMethod
|
||||||
if amethod == TableCellAlignDefault {
|
if amethod == TableCellAlignDefault {
|
||||||
|
|
@ -505,7 +505,7 @@ func (r *TableHTMLRenderer) renderTableCell(
|
||||||
switch amethod {
|
switch amethod {
|
||||||
case TableCellAlignAttribute:
|
case TableCellAlignAttribute:
|
||||||
if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
|
if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
|
||||||
_, _ = fmt.Fprintf(w, ` align="%s"`, n.Alignment.String())
|
fmt.Fprintf(w, ` align="%s"`, n.Alignment.String())
|
||||||
}
|
}
|
||||||
case TableCellAlignStyle:
|
case TableCellAlignStyle:
|
||||||
v, ok := n.AttributeString("style")
|
v, ok := n.AttributeString("style")
|
||||||
|
|
@ -528,7 +528,7 @@ func (r *TableHTMLRenderer) renderTableCell(
|
||||||
}
|
}
|
||||||
_ = w.WriteByte('>')
|
_ = w.WriteByte('>')
|
||||||
} else {
|
} else {
|
||||||
_, _ = fmt.Fprintf(w, "</%s>\n", tag)
|
fmt.Fprintf(w, "</%s>\n", tag)
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -355,40 +355,3 @@ bar | baz
|
||||||
t,
|
t,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableFuzzedPanics(t *testing.T) {
|
|
||||||
markdown := goldmark.New(
|
|
||||||
goldmark.WithRendererOptions(
|
|
||||||
html.WithXHTML(),
|
|
||||||
html.WithUnsafe(),
|
|
||||||
),
|
|
||||||
goldmark.WithExtensions(
|
|
||||||
NewTable(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
testutil.DoTestCase(
|
|
||||||
markdown,
|
|
||||||
testutil.MarkdownTestCase{
|
|
||||||
No: 1,
|
|
||||||
Description: "This should not panic",
|
|
||||||
Markdown: "* 0\n-|\n\t0",
|
|
||||||
Expected: `<ul>
|
|
||||||
<li>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>0</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>0</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</li>
|
|
||||||
</ul>`,
|
|
||||||
},
|
|
||||||
t,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@
|
||||||
package goldmark
|
package goldmark
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
"github.com/yuin/goldmark/renderer"
|
"github.com/yuin/goldmark/renderer"
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultParser returns a new Parser that is configured by default values.
|
// DefaultParser returns a new Parser that is configured by default values.
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,12 @@ func (b *blockquoteParser) process(reader text.Reader) bool {
|
||||||
reader.Advance(pos)
|
reader.Advance(pos)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
reader.Advance(pos)
|
|
||||||
if line[pos] == ' ' || line[pos] == '\t' {
|
if line[pos] == ' ' || line[pos] == '\t' {
|
||||||
padding := 0
|
pos++
|
||||||
if line[pos] == '\t' {
|
}
|
||||||
padding = util.TabWidth(reader.LineOffset()) - 1
|
reader.Advance(pos)
|
||||||
}
|
if line[pos-1] == '\t' {
|
||||||
reader.AdvanceAndSetPadding(1, padding)
|
reader.SetPadding(2)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ func (b *codeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context)
|
||||||
if segment.Padding != 0 {
|
if segment.Padding != 0 {
|
||||||
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
||||||
}
|
}
|
||||||
segment.ForceNewline = true
|
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
reader.Advance(segment.Len() - 1)
|
reader.Advance(segment.Len() - 1)
|
||||||
return node, NoChildren
|
return node, NoChildren
|
||||||
|
|
@ -60,7 +59,6 @@ func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
|
||||||
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
segment.ForceNewline = true
|
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
reader.Advance(segment.Len() - 1)
|
reader.Advance(segment.Len() - 1)
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,6 @@ func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc C
|
||||||
if padding != 0 {
|
if padding != 0 {
|
||||||
preserveLeadingTabInCodeBlock(&seg, reader, fdata.indent)
|
preserveLeadingTabInCodeBlock(&seg, reader, fdata.indent)
|
||||||
}
|
}
|
||||||
seg.ForceNewline = true // EOF as newline
|
|
||||||
node.Lines().Append(seg)
|
node.Lines().Append(seg)
|
||||||
reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
|
reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
|
|
|
||||||
|
|
@ -126,13 +126,13 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
if line[0] == '!' {
|
if line[0] == '!' {
|
||||||
if len(line) > 1 && line[1] == '[' {
|
if len(line) > 1 && line[1] == '[' {
|
||||||
block.Advance(1)
|
block.Advance(1)
|
||||||
pushLinkBottom(pc)
|
pc.Set(linkBottom, pc.LastDelimiter())
|
||||||
return processLinkLabelOpen(block, segment.Start+1, true, pc)
|
return processLinkLabelOpen(block, segment.Start+1, true, pc)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if line[0] == '[' {
|
if line[0] == '[' {
|
||||||
pushLinkBottom(pc)
|
pc.Set(linkBottom, pc.LastDelimiter())
|
||||||
return processLinkLabelOpen(block, segment.Start, false, pc)
|
return processLinkLabelOpen(block, segment.Start, false, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,7 +143,6 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
}
|
}
|
||||||
last := tlist.(*linkLabelState).Last
|
last := tlist.(*linkLabelState).Last
|
||||||
if last == nil {
|
if last == nil {
|
||||||
_ = popLinkBottom(pc)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
block.Advance(1)
|
block.Advance(1)
|
||||||
|
|
@ -152,13 +151,11 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
// > A link label can have at most 999 characters inside the square brackets.
|
// > A link label can have at most 999 characters inside the square brackets.
|
||||||
if linkLabelStateLength(tlist.(*linkLabelState)) > 998 {
|
if linkLabelStateLength(tlist.(*linkLabelState)) > 998 {
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
_ = popLinkBottom(pc)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !last.IsImage && s.containsLink(last) { // a link in a link text is not allowed
|
if !last.IsImage && s.containsLink(last) { // a link in a link text is not allowed
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
_ = popLinkBottom(pc)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,7 +169,6 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
link, hasValue = s.parseReferenceLink(parent, last, block, pc)
|
link, hasValue = s.parseReferenceLink(parent, last, block, pc)
|
||||||
if link == nil && hasValue {
|
if link == nil && hasValue {
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
_ = popLinkBottom(pc)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,14 +182,12 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
// > A link label can have at most 999 characters inside the square brackets.
|
// > A link label can have at most 999 characters inside the square brackets.
|
||||||
if len(maybeReference) > 999 {
|
if len(maybeReference) > 999 {
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
_ = popLinkBottom(pc)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
|
ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
|
||||||
if !ok {
|
if !ok {
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
_ = popLinkBottom(pc)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
link = ast.NewLink()
|
link = ast.NewLink()
|
||||||
|
|
@ -236,7 +230,11 @@ func processLinkLabelOpen(block text.Reader, pos int, isImage bool, pc Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *linkLabelState, pc Context) {
|
func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *linkLabelState, pc Context) {
|
||||||
bottom := popLinkBottom(pc)
|
var bottom ast.Node
|
||||||
|
if v := pc.Get(linkBottom); v != nil {
|
||||||
|
bottom = v.(ast.Node)
|
||||||
|
}
|
||||||
|
pc.Set(linkBottom, nil)
|
||||||
ProcessDelimiters(bottom, pc)
|
ProcessDelimiters(bottom, pc)
|
||||||
for c := last.NextSibling(); c != nil; {
|
for c := last.NextSibling(); c != nil; {
|
||||||
next := c.NextSibling()
|
next := c.NextSibling()
|
||||||
|
|
@ -397,43 +395,6 @@ func parseLinkTitle(block text.Reader) ([]byte, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func pushLinkBottom(pc Context) {
|
|
||||||
bottoms := pc.Get(linkBottom)
|
|
||||||
b := pc.LastDelimiter()
|
|
||||||
if bottoms == nil {
|
|
||||||
pc.Set(linkBottom, b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s, ok := bottoms.([]ast.Node); ok {
|
|
||||||
pc.Set(linkBottom, append(s, b))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pc.Set(linkBottom, []ast.Node{bottoms.(ast.Node), b})
|
|
||||||
}
|
|
||||||
|
|
||||||
func popLinkBottom(pc Context) ast.Node {
|
|
||||||
bottoms := pc.Get(linkBottom)
|
|
||||||
if bottoms == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if v, ok := bottoms.(ast.Node); ok {
|
|
||||||
pc.Set(linkBottom, nil)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
s := bottoms.([]ast.Node)
|
|
||||||
v := s[len(s)-1]
|
|
||||||
n := s[0 : len(s)-1]
|
|
||||||
switch len(n) {
|
|
||||||
case 0:
|
|
||||||
pc.Set(linkBottom, nil)
|
|
||||||
case 1:
|
|
||||||
pc.Set(linkBottom, n[0])
|
|
||||||
default:
|
|
||||||
pc.Set(linkBottom, s[0:len(s)-1])
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
|
func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
|
||||||
pc.Set(linkBottom, nil)
|
pc.Set(linkBottom, nil)
|
||||||
tlist := pc.Get(linkLabelStateKey)
|
tlist := pc.Get(linkLabelStateKey)
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ func (b *listItemParser) Continue(node ast.Node, reader text.Reader, pc Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := lastOffset(node.Parent())
|
offset := lastOffset(node.Parent())
|
||||||
isEmpty := node.ChildCount() == 0 && pc.Get(emptyListItemWithBlankLines) != nil
|
isEmpty := node.ChildCount() == 0
|
||||||
indent, _ := util.IndentWidth(line, reader.LineOffset())
|
indent, _ := util.IndentWidth(line, reader.LineOffset())
|
||||||
if (isEmpty || indent < offset) && indent < 4 {
|
if (isEmpty || indent < offset) && indent < 4 {
|
||||||
_, typ := matchesListItem(line, true)
|
_, typ := matchesListItem(line, true)
|
||||||
|
|
|
||||||
|
|
@ -882,7 +882,6 @@ func (p *parser) Parse(reader text.Reader, opts ...ParseOption) ast.Node {
|
||||||
for _, at := range p.astTransformers {
|
for _, at := range p.astTransformers {
|
||||||
at.Transform(root, reader, pc)
|
at.Transform(root, reader, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// root.Dump(reader.Source(), 0)
|
// root.Dump(reader.Source(), 0)
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
@ -1257,5 +1256,4 @@ func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context)
|
||||||
for _, ip := range p.closeBlockers {
|
for _, ip := range p.closeBlockers {
|
||||||
ip.CloseBlock(parent, block, pc)
|
ip.CloseBlock(parent, block, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -445,7 +445,7 @@ func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
_ = w.WriteByte('<')
|
_ = w.WriteByte('<')
|
||||||
_, _ = w.WriteString(tag)
|
_, _ = w.WriteString(tag)
|
||||||
if n.IsOrdered() && n.Start != 1 {
|
if n.IsOrdered() && n.Start != 1 {
|
||||||
_, _ = fmt.Fprintf(w, " start=\"%d\"", n.Start)
|
fmt.Fprintf(w, " start=\"%d\"", n.Start)
|
||||||
}
|
}
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
RenderAttributes(w, n, ListAttributeFilter)
|
RenderAttributes(w, n, ListAttributeFilter)
|
||||||
|
|
@ -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.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
||||||
}
|
}
|
||||||
_, _ = w.WriteString(`" alt="`)
|
_, _ = w.WriteString(`" alt="`)
|
||||||
r.renderTexts(w, source, n)
|
_, _ = w.Write(nodeToHTMLText(n, source))
|
||||||
_ = w.WriteByte('"')
|
_ = w.WriteByte('"')
|
||||||
if n.Title != nil {
|
if n.Title != nil {
|
||||||
_, _ = w.WriteString(` title="`)
|
_, _ = 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 {
|
if r.EastAsianLineBreaks != EastAsianLineBreaksNone && len(value) != 0 {
|
||||||
sibling := node.NextSibling()
|
sibling := node.NextSibling()
|
||||||
if sibling != nil && sibling.Kind() == ast.KindText {
|
if sibling != nil && sibling.Kind() == ast.KindText {
|
||||||
if siblingText := sibling.(*ast.Text).Value(source); len(siblingText) != 0 {
|
if siblingText := sibling.(*ast.Text).Text(source); len(siblingText) != 0 {
|
||||||
thisLastRune := util.ToRune(value, len(value)-1)
|
thisLastRune := util.ToRune(value, len(value)-1)
|
||||||
siblingFirstRune, _ := utf8.DecodeRune(siblingText)
|
siblingFirstRune, _ := utf8.DecodeRune(siblingText)
|
||||||
if r.EastAsianLineBreaks.softLineBreak(thisLastRune, siblingFirstRune) {
|
if r.EastAsianLineBreaks.softLineBreak(thisLastRune, siblingFirstRune) {
|
||||||
|
|
@ -770,18 +770,6 @@ func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node,
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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.Text); ok {
|
|
||||||
_, _ = r.renderText(w, source, t, true)
|
|
||||||
} else {
|
|
||||||
r.renderTexts(w, source, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataPrefix = []byte("data-")
|
var dataPrefix = []byte("data-")
|
||||||
|
|
||||||
// RenderAttributes renders given node's attributes.
|
// RenderAttributes renders given node's attributes.
|
||||||
|
|
@ -1019,3 +1007,20 @@ func IsDangerousURL(url []byte) bool {
|
||||||
return hasPrefix(url, bJs) || hasPrefix(url, bVb) ||
|
return hasPrefix(url, bJs) || hasPrefix(url, bVb) ||
|
||||||
hasPrefix(url, bFile) || hasPrefix(url, bData)
|
hasPrefix(url, bFile) || hasPrefix(url, bData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nodeToHTMLText(n ast.Node, source []byte) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
if s, ok := c.(*ast.String); ok && s.IsCode() {
|
||||||
|
buf.Write(s.Text(source))
|
||||||
|
} else if !c.HasChildren() {
|
||||||
|
buf.Write(util.EscapeHTML(c.Text(source)))
|
||||||
|
if t, ok := c.(*ast.Text); ok && t.SoftLineBreak() {
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.Write(nodeToHTMLText(c, source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package text
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -19,20 +18,6 @@ type Segment struct {
|
||||||
|
|
||||||
// Padding is a padding length of the segment.
|
// Padding is a padding length of the segment.
|
||||||
Padding int
|
Padding int
|
||||||
|
|
||||||
// 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.
|
// NewSegment return a new Segment.
|
||||||
|
|
@ -55,18 +40,12 @@ func NewSegmentPadding(start, stop, n int) Segment {
|
||||||
|
|
||||||
// Value returns a value of the segment.
|
// Value returns a value of the segment.
|
||||||
func (t *Segment) Value(buffer []byte) []byte {
|
func (t *Segment) Value(buffer []byte) []byte {
|
||||||
var result []byte
|
|
||||||
if t.Padding == 0 {
|
if t.Padding == 0 {
|
||||||
result = buffer[t.Start:t.Stop]
|
return 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]...)
|
|
||||||
}
|
}
|
||||||
if t.ForceNewline && len(result) > 0 && result[len(result)-1] != '\n' {
|
result := make([]byte, 0, t.Padding+t.Stop-t.Start+1)
|
||||||
result = append(result, '\n')
|
result = append(result, bytes.Repeat(space, t.Padding)...)
|
||||||
}
|
return append(result, buffer[t.Start:t.Stop]...)
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns a length of the segment.
|
// Len returns a length of the segment.
|
||||||
|
|
@ -228,12 +207,3 @@ func (s *Segments) Unshift(v Segment) {
|
||||||
s.values = append(s.values[0:1], s.values[0:]...)
|
s.values = append(s.values[0:1], s.values[0:]...)
|
||||||
s.values[0] = v
|
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -166,13 +166,7 @@ func IndentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, pad
|
||||||
w := 0
|
w := 0
|
||||||
i := 0
|
i := 0
|
||||||
l := len(bs)
|
l := len(bs)
|
||||||
p := paddingv
|
|
||||||
for ; i < l; i++ {
|
for ; i < l; i++ {
|
||||||
if p > 0 {
|
|
||||||
p--
|
|
||||||
w++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if bs[i] == '\t' && w < width {
|
if bs[i] == '\t' && w < width {
|
||||||
w += TabWidth(currentPos + w)
|
w += TabWidth(currentPos + w)
|
||||||
} else if bs[i] == ' ' && w < width {
|
} else if bs[i] == ' ' && w < width {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//go:build !appengine && !js && !go1.21
|
//go:build !appengine && !js
|
||||||
// +build !appengine,!js,!go1.21
|
// +build !appengine,!js
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
//go:build !appengine && !js && go1.21
|
|
||||||
// +build !appengine,!js,go1.21
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BytesToReadOnlyString returns a string converted from given bytes.
|
|
||||||
func BytesToReadOnlyString(b []byte) string {
|
|
||||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringToReadOnlyBytes returns bytes converted from given string.
|
|
||||||
func StringToReadOnlyBytes(s string) []byte {
|
|
||||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue