mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Compare commits
42 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04410ff159 | ||
|
|
f7b2d24c09 | ||
|
|
ba49c5c69d | ||
|
|
c05fb087a4 | ||
|
|
b39daae79e | ||
|
|
d9c03f07f0 | ||
|
|
65dcf6cd0a | ||
|
|
ad1565131a | ||
|
|
bc993b4f59 | ||
|
|
41273a4d07 | ||
|
|
d80ac9397c | ||
|
|
15000ac6a1 | ||
|
|
14d91f957f | ||
|
|
3847ca20c6 | ||
|
|
697e44ce88 | ||
|
|
fa88006eee | ||
|
|
fe34ea5d96 | ||
|
|
fd14edc9bc | ||
|
|
e367755421 | ||
|
|
e44645afbb | ||
|
|
15ade8aace | ||
|
|
dc32f35808 | ||
|
|
25bdeb0fee | ||
|
|
a590622b15 | ||
|
|
fde4948b4d | ||
|
|
9c09ae0019 | ||
|
|
c15e394c27 | ||
|
|
e405d57be0 | ||
|
|
ce6424aa0e | ||
|
|
09afa2feba | ||
|
|
4f3074451e | ||
|
|
4675c66d3d | ||
|
|
848dc66530 | ||
|
|
b8d6d3a9b7 | ||
|
|
90c46e0829 | ||
|
|
4bade05173 | ||
|
|
2b845f2615 | ||
|
|
e3d8ed9725 | ||
|
|
697cd509b1 | ||
|
|
ff3285aa2a | ||
|
|
c2167685c1 | ||
|
|
39a50c623e |
39 changed files with 4499 additions and 3867 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.19.x, 1.20.x]
|
go-version: [1.21.x, 1.22.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,16 +16,18 @@ 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@v3
|
uses: golangci/golangci-lint-action@v6
|
||||||
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: |
|
run: goveralls -coverprofile=coverage.out -service=github
|
||||||
GO111MODULE=off go get github.com/mattn/goveralls
|
|
||||||
$(go env GOPATH)/bin/goveralls -coverprofile=coverage.out -service=github
|
|
||||||
|
|
|
||||||
41
README.md
41
README.md
|
|
@ -2,13 +2,15 @@ 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)
|
||||||
|
|
||||||
> A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured.
|
> A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured.
|
||||||
|
|
||||||
goldmark is compliant with CommonMark 0.30.
|
goldmark is compliant with CommonMark 0.31.2.
|
||||||
|
|
||||||
|
- [goldmark playground](https://yuin.github.io/goldmark/playground/) : Try goldmark online. This playground is built with WASM(5-10MB).
|
||||||
|
|
||||||
Motivation
|
Motivation
|
||||||
----------------------
|
----------------------
|
||||||
|
|
@ -260,7 +262,7 @@ You can override autolinking patterns via options.
|
||||||
|
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `extension.WithLinkifyAllowedProtocols` | `[][]byte` | List of allowed protocols such as `[][]byte{ []byte("http:") }` |
|
| `extension.WithLinkifyAllowedProtocols` | `[][]byte \| []string` | List of allowed protocols such as `[]string{ "http:" }` |
|
||||||
| `extension.WithLinkifyURLRegexp` | `*regexp.Regexp` | Regexp that defines URLs, including protocols |
|
| `extension.WithLinkifyURLRegexp` | `*regexp.Regexp` | Regexp that defines URLs, including protocols |
|
||||||
| `extension.WithLinkifyWWWRegexp` | `*regexp.Regexp` | Regexp that defines URL starting with `www.`. This pattern corresponds to [the extended www autolink](https://github.github.com/gfm/#extended-www-autolink) |
|
| `extension.WithLinkifyWWWRegexp` | `*regexp.Regexp` | Regexp that defines URL starting with `www.`. This pattern corresponds to [the extended www autolink](https://github.github.com/gfm/#extended-www-autolink) |
|
||||||
| `extension.WithLinkifyEmailRegexp` | `*regexp.Regexp` | Regexp that defines email addresses` |
|
| `extension.WithLinkifyEmailRegexp` | `*regexp.Regexp` | Regexp that defines email addresses` |
|
||||||
|
|
@ -277,12 +279,12 @@ markdown := goldmark.New(
|
||||||
),
|
),
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
extension.NewLinkify(
|
extension.NewLinkify(
|
||||||
extension.WithLinkifyAllowedProtocols([][]byte{
|
extension.WithLinkifyAllowedProtocols([]string{
|
||||||
[]byte("http:"),
|
"http:",
|
||||||
[]byte("https:"),
|
"https:",
|
||||||
}),
|
}),
|
||||||
extension.WithLinkifyURLRegexp(
|
extension.WithLinkifyURLRegexp(
|
||||||
xurls.Strict,
|
xurls.Strict(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -297,13 +299,13 @@ This extension has some options:
|
||||||
|
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `extension.WithFootnoteIDPrefix` | `[]byte` | a prefix for the id attributes.|
|
| `extension.WithFootnoteIDPrefix` | `[]byte \| string` | a prefix for the id attributes.|
|
||||||
| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.|
|
| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.|
|
||||||
| `extension.WithFootnoteLinkTitle` | `[]byte` | an optional title attribute for footnote links.|
|
| `extension.WithFootnoteLinkTitle` | `[]byte \| string` | an optional title attribute for footnote links.|
|
||||||
| `extension.WithFootnoteBacklinkTitle` | `[]byte` | an optional title attribute for footnote backlinks. |
|
| `extension.WithFootnoteBacklinkTitle` | `[]byte \| string` | an optional title attribute for footnote backlinks. |
|
||||||
| `extension.WithFootnoteLinkClass` | `[]byte` | a class for footnote links. This defaults to `footnote-ref`. |
|
| `extension.WithFootnoteLinkClass` | `[]byte \| string` | a class for footnote links. This defaults to `footnote-ref`. |
|
||||||
| `extension.WithFootnoteBacklinkClass` | `[]byte` | a class for footnote backlinks. This defaults to `footnote-backref`. |
|
| `extension.WithFootnoteBacklinkClass` | `[]byte \| string` | a class for footnote backlinks. This defaults to `footnote-backref`. |
|
||||||
| `extension.WithFootnoteBacklinkHTML` | `[]byte` | a class for footnote backlinks. This defaults to `↩︎`. |
|
| `extension.WithFootnoteBacklinkHTML` | `[]byte \| string` | a class for footnote backlinks. This defaults to `↩︎`. |
|
||||||
|
|
||||||
Some options can have special substitutions. Occurrences of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurrences of “%%” will be replaced by a number for the reference (footnotes can have multiple references).
|
Some options can have special substitutions. Occurrences of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurrences of “%%” will be replaced by a number for the reference (footnotes can have multiple references).
|
||||||
|
|
||||||
|
|
@ -319,7 +321,7 @@ for _, path := range files {
|
||||||
markdown := goldmark.New(
|
markdown := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
NewFootnote(
|
NewFootnote(
|
||||||
WithFootnoteIDPrefix([]byte(path)),
|
WithFootnoteIDPrefix(path),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -379,7 +381,7 @@ This extension provides additional options for CJK users.
|
||||||
|
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `extension.WithEastAsianLineBreaks` | `...extension.EastAsianLineBreaksStyle` | Soft line breaks are rendered as a newline. Some asian users will see it as an unnecessary space. With this option, soft line breaks between east asian wide characters will be ignored. |
|
| `extension.WithEastAsianLineBreaks` | `...extension.EastAsianLineBreaksStyle` | Soft line breaks are rendered as a newline. Some asian users will see it as an unnecessary space. With this option, soft line breaks between east asian wide characters will be ignored. This defaults to `EastAsianLineBreaksStyleSimple`. |
|
||||||
| `extension.WithEscapedSpace` | `-` | Without spaces around an emphasis started with east asian punctuations, it is not interpreted as an emphasis(as defined in CommonMark spec). With this option, you can avoid this inconvenient behavior by putting 'not rendered' spaces around an emphasis like `太郎は\ **「こんにちわ」**\ といった`. |
|
| `extension.WithEscapedSpace` | `-` | Without spaces around an emphasis started with east asian punctuations, it is not interpreted as an emphasis(as defined in CommonMark spec). With this option, you can avoid this inconvenient behavior by putting 'not rendered' spaces around an emphasis like `太郎は\ **「こんにちわ」**\ といった`. |
|
||||||
|
|
||||||
#### Styles of Line Breaking
|
#### Styles of Line Breaking
|
||||||
|
|
@ -467,6 +469,7 @@ As you can see, goldmark's performance is on par with cmark's.
|
||||||
|
|
||||||
Extensions
|
Extensions
|
||||||
--------------------
|
--------------------
|
||||||
|
### List of extensions
|
||||||
|
|
||||||
- [goldmark-meta](https://github.com/yuin/goldmark-meta): A YAML metadata
|
- [goldmark-meta](https://github.com/yuin/goldmark-meta): A YAML metadata
|
||||||
extension for the goldmark Markdown parser.
|
extension for the goldmark Markdown parser.
|
||||||
|
|
@ -490,6 +493,14 @@ 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-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()`.
|
||||||
|
|
||||||
|
### Loading extensions at runtime
|
||||||
|
[goldmark-dynamic](https://github.com/yuin/goldmark-dynamic) allows you to write a goldmark extension in Lua and load it at runtime without re-compilation.
|
||||||
|
|
||||||
|
Please refer to [goldmark-dynamic](https://github.com/yuin/goldmark-dynamic) for details.
|
||||||
|
|
||||||
|
|
||||||
goldmark internal(for extension developers)
|
goldmark internal(for extension developers)
|
||||||
|
|
|
||||||
|
|
@ -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 v1.2.1
|
github.com/yuin/goldmark v0.0.0
|
||||||
gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
|
gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -22,3 +22,4 @@ 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,7 +385,8 @@ a* b c d *e*
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
x
|
x
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<pre><code> x</code></pre>
|
<pre><code> x
|
||||||
|
</code></pre>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
26: NUL bytes must be replaced with U+FFFD
|
26: NUL bytes must be replaced with U+FFFD
|
||||||
|
|
@ -780,3 +781,65 @@ 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>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
3184
_test/spec.json
3184
_test/spec.json
File diff suppressed because it is too large
Load diff
15
ast/ast.go
15
ast/ast.go
|
|
@ -123,6 +123,12 @@ 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,
|
||||||
|
|
@ -374,11 +380,18 @@ 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,21 +5,6 @@ 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,6 +130,13 @@ 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{
|
||||||
|
|
@ -155,6 +162,13 @@ 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{
|
||||||
|
|
@ -249,6 +263,13 @@ 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{
|
||||||
|
|
@ -304,6 +325,13 @@ 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{
|
||||||
|
|
@ -498,6 +526,17 @@ 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,17 +143,25 @@ 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.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.
|
// KindText is a NodeKind of the Text node.
|
||||||
|
|
@ -258,6 +266,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|
@ -492,15 +502,22 @@ 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.Text(source)...)
|
ret = append(ret, n.value.Value(source)...)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
return n.value.Text(source)
|
return n.value.Value(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.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.
|
// NewAutoLink returns a new AutoLink node.
|
||||||
|
|
@ -541,6 +558,13 @@ 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
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ package goldmark_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/yuin/goldmark"
|
. "github.com/yuin/goldmark"
|
||||||
|
|
@ -20,7 +20,7 @@ type commonmarkSpecTestCase struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpec(t *testing.T) {
|
func TestSpec(t *testing.T) {
|
||||||
bs, err := ioutil.ReadFile("_test/spec.json")
|
bs, err := os.ReadFile("_test/spec.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,8 @@ on two lines.</p>
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<dl>
|
<dl>
|
||||||
<dt>0</dt>
|
<dt>0</dt>
|
||||||
<dd><pre><code> 0</code></pre>
|
<dd><pre><code> 0
|
||||||
|
</code></pre>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
<p><del>Hi</del> Hello, world!</p>
|
<p><del>Hi</del> Hello, world!</p>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2
|
2
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
This ~~has a
|
This ~~has a
|
||||||
|
|
@ -16,3 +14,26 @@ 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>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
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
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -382,8 +382,8 @@ func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
|
// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
|
||||||
func WithFootnoteIDPrefix(a []byte) FootnoteOption {
|
func WithFootnoteIDPrefix[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteIDPrefix{a}
|
return &withFootnoteIDPrefix{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
|
const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
|
||||||
|
|
@ -420,8 +420,8 @@ func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
|
// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
|
||||||
func WithFootnoteLinkTitle(a []byte) FootnoteOption {
|
func WithFootnoteLinkTitle[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteLinkTitle{a}
|
return &withFootnoteLinkTitle{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
|
const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
|
||||||
|
|
@ -439,8 +439,8 @@ func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
|
// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
|
||||||
func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
|
func WithFootnoteBacklinkTitle[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteBacklinkTitle{a}
|
return &withFootnoteBacklinkTitle{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
|
const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
|
||||||
|
|
@ -458,8 +458,8 @@ func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteLinkClass is a functional option that is a class for footnote links.
|
// WithFootnoteLinkClass is a functional option that is a class for footnote links.
|
||||||
func WithFootnoteLinkClass(a []byte) FootnoteOption {
|
func WithFootnoteLinkClass[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteLinkClass{a}
|
return &withFootnoteLinkClass{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
|
const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
|
||||||
|
|
@ -477,8 +477,8 @@ func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
|
// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
|
||||||
func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
|
func WithFootnoteBacklinkClass[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteBacklinkClass{a}
|
return &withFootnoteBacklinkClass{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
|
const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
|
||||||
|
|
@ -496,8 +496,8 @@ func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
|
// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
|
||||||
func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
|
func WithFootnoteBacklinkHTML[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteBacklinkHTML{a}
|
return &withFootnoteBacklinkHTML{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ func TestFootnoteOptions(t *testing.T) {
|
||||||
),
|
),
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
NewFootnote(
|
NewFootnote(
|
||||||
WithFootnoteIDPrefix([]byte("article12-")),
|
WithFootnoteIDPrefix("article12-"),
|
||||||
WithFootnoteLinkClass([]byte("link-class")),
|
WithFootnoteLinkClass("link-class"),
|
||||||
WithFootnoteBacklinkClass([]byte("backlink-class")),
|
WithFootnoteBacklinkClass("backlink-class"),
|
||||||
WithFootnoteLinkTitle([]byte("link-title-%%-^^")),
|
WithFootnoteLinkTitle("link-title-%%-^^"),
|
||||||
WithFootnoteBacklinkTitle([]byte("backlink-title")),
|
WithFootnoteBacklinkTitle("backlink-title"),
|
||||||
WithFootnoteBacklinkHTML([]byte("^")),
|
WithFootnoteBacklinkHTML("^"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -66,10 +66,12 @@ func (o *withLinkifyAllowedProtocols) SetLinkifyOption(p *LinkifyConfig) {
|
||||||
// WithLinkifyAllowedProtocols is a functional option that specify allowed
|
// WithLinkifyAllowedProtocols is a functional option that specify allowed
|
||||||
// protocols in autolinks. Each protocol must end with ':' like
|
// protocols in autolinks. Each protocol must end with ':' like
|
||||||
// 'http:' .
|
// 'http:' .
|
||||||
func WithLinkifyAllowedProtocols(value [][]byte) LinkifyOption {
|
func WithLinkifyAllowedProtocols[T []byte | string](value []T) LinkifyOption {
|
||||||
return &withLinkifyAllowedProtocols{
|
opt := &withLinkifyAllowedProtocols{}
|
||||||
value: value,
|
for _, v := range value {
|
||||||
|
opt.value = append(opt.value, []byte(v))
|
||||||
}
|
}
|
||||||
|
return opt
|
||||||
}
|
}
|
||||||
|
|
||||||
type withLinkifyURLRegexp struct {
|
type withLinkifyURLRegexp struct {
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ func TestLinkifyWithAllowedProtocols(t *testing.T) {
|
||||||
),
|
),
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
NewLinkify(
|
NewLinkify(
|
||||||
WithLinkifyAllowedProtocols([][]byte{
|
WithLinkifyAllowedProtocols([]string{
|
||||||
[]byte("ssh:"),
|
"ssh:",
|
||||||
}),
|
}),
|
||||||
WithLinkifyURLRegexp(
|
WithLinkifyURLRegexp(
|
||||||
regexp.MustCompile(`\w+://[^\s]+`),
|
regexp.MustCompile(`\w+://[^\s]+`),
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,11 @@ 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, 2, defaultStrikethroughDelimiterProcessor)
|
node := parser.ScanDelimiter(line, before, 1, defaultStrikethroughDelimiterProcessor)
|
||||||
if node == nil {
|
if node == nil || node.OriginalLength > 2 || before == '~' {
|
||||||
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,3 +355,40 @@ 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,10 +115,10 @@ func (o *withTypographicSubstitutions) SetTypographerOption(p *TypographerConfig
|
||||||
|
|
||||||
// WithTypographicSubstitutions is a functional otpion that specify replacement text
|
// WithTypographicSubstitutions is a functional otpion that specify replacement text
|
||||||
// for punctuations.
|
// for punctuations.
|
||||||
func WithTypographicSubstitutions(values map[TypographicPunctuation][]byte) TypographerOption {
|
func WithTypographicSubstitutions[T []byte | string](values map[TypographicPunctuation]T) TypographerOption {
|
||||||
replacements := newDefaultSubstitutions()
|
replacements := newDefaultSubstitutions()
|
||||||
for k, v := range values {
|
for k, v := range values {
|
||||||
replacements[k] = v
|
replacements[k] = []byte(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &withTypographicSubstitutions{replacements}
|
return &withTypographicSubstitutions{replacements}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package fuzz
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
|
|
@ -42,7 +42,7 @@ func fuzz(f *testing.F) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func FuzzDefault(f *testing.F) {
|
func FuzzDefault(f *testing.F) {
|
||||||
bs, err := ioutil.ReadFile("../_test/spec.json")
|
bs, err := os.ReadFile("../_test/spec.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -1,3 +1,3 @@
|
||||||
module github.com/yuin/goldmark
|
module github.com/yuin/goldmark
|
||||||
|
|
||||||
go 1.18
|
go 1.19
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
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,12 +28,13 @@ func (b *blockquoteParser) process(reader text.Reader) bool {
|
||||||
reader.Advance(pos)
|
reader.Advance(pos)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if line[pos] == ' ' || line[pos] == '\t' {
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
reader.Advance(pos)
|
reader.Advance(pos)
|
||||||
if line[pos-1] == '\t' {
|
if line[pos] == ' ' || line[pos] == '\t' {
|
||||||
reader.SetPadding(2)
|
padding := 0
|
||||||
|
if line[pos] == '\t' {
|
||||||
|
padding = util.TabWidth(reader.LineOffset()) - 1
|
||||||
|
}
|
||||||
|
reader.AdvanceAndSetPadding(1, padding)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ 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
|
||||||
|
|
@ -59,6 +60,7 @@ 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,6 +100,7 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ var allowedBlockTags = map[string]bool{
|
||||||
"option": true,
|
"option": true,
|
||||||
"p": true,
|
"p": true,
|
||||||
"param": true,
|
"param": true,
|
||||||
|
"search": true,
|
||||||
"section": true,
|
"section": true,
|
||||||
"source": true,
|
|
||||||
"summary": true,
|
"summary": true,
|
||||||
"table": true,
|
"table": true,
|
||||||
"tbody": true,
|
"tbody": true,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
pc.Set(linkBottom, pc.LastDelimiter())
|
pushLinkBottom(pc)
|
||||||
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] == '[' {
|
||||||
pc.Set(linkBottom, pc.LastDelimiter())
|
pushLinkBottom(pc)
|
||||||
return processLinkLabelOpen(block, segment.Start, false, pc)
|
return processLinkLabelOpen(block, segment.Start, false, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,6 +143,7 @@ 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)
|
||||||
|
|
@ -151,11 +152,13 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,6 +172,7 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,12 +186,14 @@ 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()
|
||||||
|
|
@ -230,11 +236,7 @@ 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) {
|
||||||
var bottom ast.Node
|
bottom := popLinkBottom(pc)
|
||||||
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()
|
||||||
|
|
@ -395,6 +397,43 @@ 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
|
isEmpty := node.ChildCount() == 0 && pc.Get(emptyListItemWithBlankLines) != nil
|
||||||
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,6 +882,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
@ -1256,4 +1257,5 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,47 +58,38 @@ var closeProcessingInstruction = []byte("?>")
|
||||||
var openCDATA = []byte("<![CDATA[")
|
var openCDATA = []byte("<![CDATA[")
|
||||||
var closeCDATA = []byte("]]>")
|
var closeCDATA = []byte("]]>")
|
||||||
var closeDecl = []byte(">")
|
var closeDecl = []byte(">")
|
||||||
var emptyComment = []byte("<!---->")
|
var emptyComment1 = []byte("<!-->")
|
||||||
var invalidComment1 = []byte("<!-->")
|
var emptyComment2 = []byte("<!--->")
|
||||||
var invalidComment2 = []byte("<!--->")
|
|
||||||
var openComment = []byte("<!--")
|
var openComment = []byte("<!--")
|
||||||
var closeComment = []byte("-->")
|
var closeComment = []byte("-->")
|
||||||
var doubleHyphen = []byte("--")
|
|
||||||
|
|
||||||
func (s *rawHTMLParser) parseComment(block text.Reader, pc Context) ast.Node {
|
func (s *rawHTMLParser) parseComment(block text.Reader, pc Context) ast.Node {
|
||||||
savedLine, savedSegment := block.Position()
|
savedLine, savedSegment := block.Position()
|
||||||
node := ast.NewRawHTML()
|
node := ast.NewRawHTML()
|
||||||
line, segment := block.PeekLine()
|
line, segment := block.PeekLine()
|
||||||
if bytes.HasPrefix(line, emptyComment) {
|
if bytes.HasPrefix(line, emptyComment1) {
|
||||||
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment)))
|
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment1)))
|
||||||
block.Advance(len(emptyComment))
|
block.Advance(len(emptyComment1))
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
if bytes.HasPrefix(line, invalidComment1) || bytes.HasPrefix(line, invalidComment2) {
|
if bytes.HasPrefix(line, emptyComment2) {
|
||||||
return nil
|
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment2)))
|
||||||
|
block.Advance(len(emptyComment2))
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
offset := len(openComment)
|
offset := len(openComment)
|
||||||
line = line[offset:]
|
line = line[offset:]
|
||||||
for {
|
for {
|
||||||
hindex := bytes.Index(line, doubleHyphen)
|
index := bytes.Index(line, closeComment)
|
||||||
if hindex > -1 {
|
if index > -1 {
|
||||||
hindex += offset
|
node.Segments.Append(segment.WithStop(segment.Start + offset + index + len(closeComment)))
|
||||||
}
|
block.Advance(offset + index + len(closeComment))
|
||||||
index := bytes.Index(line, closeComment) + offset
|
return node
|
||||||
if index > -1 && hindex == index {
|
|
||||||
if index == 0 || len(line) < 2 || line[index-offset-1] != '-' {
|
|
||||||
node.Segments.Append(segment.WithStop(segment.Start + index + len(closeComment)))
|
|
||||||
block.Advance(index + len(closeComment))
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hindex > 0 {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
offset = 0
|
||||||
node.Segments.Append(segment)
|
node.Segments.Append(segment)
|
||||||
block.AdvanceLine()
|
block.AdvanceLine()
|
||||||
line, segment = block.PeekLine()
|
line, segment = block.PeekLine()
|
||||||
offset = 0
|
|
||||||
if line == nil {
|
if line == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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="`)
|
||||||
_, _ = w.Write(nodeToHTMLText(n, source))
|
r.renderTexts(w, source, n)
|
||||||
_ = 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).Text(source); len(siblingText) != 0 {
|
if siblingText := sibling.(*ast.Text).Value(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,6 +770,18 @@ 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.
|
||||||
|
|
@ -786,7 +798,14 @@ func RenderAttributes(w util.BufWriter, node ast.Node, filter util.BytesFilter)
|
||||||
_, _ = w.Write(attr.Name)
|
_, _ = w.Write(attr.Name)
|
||||||
_, _ = w.WriteString(`="`)
|
_, _ = w.WriteString(`="`)
|
||||||
// TODO: convert numeric values to strings
|
// TODO: convert numeric values to strings
|
||||||
_, _ = w.Write(util.EscapeHTML(attr.Value.([]byte)))
|
var value []byte
|
||||||
|
switch typed := attr.Value.(type) {
|
||||||
|
case []byte:
|
||||||
|
value = typed
|
||||||
|
case string:
|
||||||
|
value = util.StringToReadOnlyBytes(typed)
|
||||||
|
}
|
||||||
|
_, _ = w.Write(util.EscapeHTML(value))
|
||||||
_ = w.WriteByte('"')
|
_ = w.WriteByte('"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1000,20 +1019,3 @@ 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,6 +2,7 @@ package text
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -18,6 +19,20 @@ 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.
|
||||||
|
|
@ -40,12 +55,18 @@ 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 {
|
||||||
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)
|
if t.ForceNewline && len(result) > 0 && result[len(result)-1] != '\n' {
|
||||||
result = append(result, bytes.Repeat(space, t.Padding)...)
|
result = append(result, '\n')
|
||||||
return append(result, buffer[t.Start:t.Stop]...)
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns a length of the segment.
|
// Len returns a length of the segment.
|
||||||
|
|
@ -207,3 +228,12 @@ 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,7 +166,13 @@ 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 {
|
||||||
|
|
@ -808,7 +814,7 @@ func IsPunct(c byte) bool {
|
||||||
|
|
||||||
// IsPunctRune returns true if the given rune is a punctuation, otherwise false.
|
// IsPunctRune returns true if the given rune is a punctuation, otherwise false.
|
||||||
func IsPunctRune(r rune) bool {
|
func IsPunctRune(r rune) bool {
|
||||||
return int32(r) <= 256 && IsPunct(byte(r)) || unicode.IsPunct(r)
|
return unicode.IsSymbol(r) || unicode.IsPunct(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSpace returns true if the given character is a space, otherwise false.
|
// IsSpace returns true if the given character is a space, otherwise false.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//go:build !appengine && !js
|
//go:build !appengine && !js && !go1.21
|
||||||
// +build !appengine,!js
|
// +build !appengine,!js,!go1.21
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
18
util/util_unsafe_go121.go
Normal file
18
util/util_unsafe_go121.go
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
//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