mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Fix #237
This commit is contained in:
parent
d44652d174
commit
829d874034
6 changed files with 203 additions and 82 deletions
|
|
@ -471,3 +471,13 @@ x \f
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<p>x \f</p>
|
<p>x \f</p>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
35: A link reference definition can contain a new line
|
||||||
|
//- - - - - - - - -//
|
||||||
|
This is a [test][foo
|
||||||
|
bar] 1...2..3...
|
||||||
|
|
||||||
|
[foo bar]: /
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p>This is a <a href="/">test</a> 1...2..3...</p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,26 @@ func TestSpec(t *testing.T) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
cases := []testutil.MarkdownTestCase{}
|
cases := []testutil.MarkdownTestCase{}
|
||||||
|
nos := testutil.ParseCliCaseArg()
|
||||||
for _, c := range testCases {
|
for _, c := range testCases {
|
||||||
|
shouldAdd := len(nos) == 0
|
||||||
|
if !shouldAdd {
|
||||||
|
for _, no := range nos {
|
||||||
|
if c.Example == no {
|
||||||
|
shouldAdd = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldAdd {
|
||||||
cases = append(cases, testutil.MarkdownTestCase{
|
cases = append(cases, testutil.MarkdownTestCase{
|
||||||
No: c.Example,
|
No: c.Example,
|
||||||
Markdown: c.Markdown,
|
Markdown: c.Markdown,
|
||||||
Expected: c.HTML,
|
Expected: c.HTML,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
markdown := New(WithRendererOptions(
|
markdown := New(WithRendererOptions(
|
||||||
html.WithXHTML(),
|
html.WithXHTML(),
|
||||||
html.WithUnsafe(),
|
html.WithUnsafe(),
|
||||||
|
|
|
||||||
|
|
@ -221,21 +221,33 @@ func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *lin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var linkFindClosureOptions text.FindClosureOptions = text.FindClosureOptions{
|
||||||
|
Nesting: false,
|
||||||
|
Newline: true,
|
||||||
|
Advance: true,
|
||||||
|
}
|
||||||
|
|
||||||
func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState, block text.Reader, pc Context) (*ast.Link, bool) {
|
func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState, block text.Reader, pc Context) (*ast.Link, bool) {
|
||||||
_, orgpos := block.Position()
|
_, orgpos := block.Position()
|
||||||
block.Advance(1) // skip '['
|
block.Advance(1) // skip '['
|
||||||
line, segment := block.PeekLine()
|
segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
|
||||||
endIndex := util.FindClosure(line, '[', ']', false, true)
|
if !found {
|
||||||
if endIndex < 0 {
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
block.Advance(endIndex + 1)
|
var maybeReference []byte
|
||||||
ssegment := segment.WithStop(segment.Start + endIndex)
|
if segments.Len() == 1 { // avoid allocate a new byte slice
|
||||||
maybeReference := block.Value(ssegment)
|
maybeReference = block.Value(segments.At(0))
|
||||||
|
} else {
|
||||||
|
maybeReference = []byte{}
|
||||||
|
for i := 0; i < segments.Len(); i++ {
|
||||||
|
s := segments.At(i)
|
||||||
|
maybeReference = append(maybeReference, block.Value(s)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
if util.IsBlank(maybeReference) { // collapsed reference link
|
if util.IsBlank(maybeReference) { // collapsed reference link
|
||||||
ssegment = text.NewSegment(last.Segment.Stop, orgpos.Start-1)
|
s := text.NewSegment(last.Segment.Stop, orgpos.Start-1)
|
||||||
maybeReference = block.Value(ssegment)
|
maybeReference = block.Value(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
|
ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
|
||||||
|
|
@ -338,32 +350,21 @@ func parseLinkTitle(block text.Reader) ([]byte, bool) {
|
||||||
if opener == '(' {
|
if opener == '(' {
|
||||||
closer = ')'
|
closer = ')'
|
||||||
}
|
}
|
||||||
savedLine, savedPosition := block.Position()
|
block.Advance(1)
|
||||||
|
segments, found := block.FindClosure(opener, closer, linkFindClosureOptions)
|
||||||
|
if found {
|
||||||
|
if segments.Len() == 1 {
|
||||||
|
return block.Value(segments.At(0)), true
|
||||||
|
}
|
||||||
var title []byte
|
var title []byte
|
||||||
for i := 0; ; i++ {
|
for i := 0; i < segments.Len(); i++ {
|
||||||
line, _ := block.PeekLine()
|
s := segments.At(i)
|
||||||
if line == nil {
|
title = append(title, block.Value(s)...)
|
||||||
block.SetPosition(savedLine, savedPosition)
|
}
|
||||||
|
return title, true
|
||||||
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
offset := 0
|
|
||||||
if i == 0 {
|
|
||||||
offset = 1
|
|
||||||
}
|
|
||||||
pos := util.FindClosure(line[offset:], opener, closer, false, true)
|
|
||||||
if pos < 0 {
|
|
||||||
title = append(title, line[offset:]...)
|
|
||||||
block.AdvanceLine()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pos += offset + 1 // 1: closer
|
|
||||||
block.Advance(pos)
|
|
||||||
if i == 0 { // avoid allocating new slice
|
|
||||||
return line[offset : pos-1], true
|
|
||||||
}
|
|
||||||
return append(title, line[offset:pos-1]...), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
|
func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
|
||||||
tlist := pc.Get(linkLabelStateKey)
|
tlist := pc.Get(linkLabelStateKey)
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ func (p *linkReferenceParagraphTransformer) Transform(node *ast.Paragraph, reade
|
||||||
|
|
||||||
func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) {
|
func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) {
|
||||||
block.SkipSpaces()
|
block.SkipSpaces()
|
||||||
line, segment := block.PeekLine()
|
line, _ := block.PeekLine()
|
||||||
if line == nil {
|
if line == nil {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
@ -67,39 +67,34 @@ func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) {
|
||||||
if line[pos] != '[' {
|
if line[pos] != '[' {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
open := segment.Start + pos + 1
|
|
||||||
closes := -1
|
|
||||||
block.Advance(pos + 1)
|
block.Advance(pos + 1)
|
||||||
for {
|
segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
|
||||||
line, segment = block.PeekLine()
|
if !found {
|
||||||
if line == nil {
|
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
closure := util.FindClosure(line, '[', ']', false, false)
|
var label []byte
|
||||||
if closure > -1 {
|
if segments.Len() == 1 {
|
||||||
closes = segment.Start + closure
|
label = block.Value(segments.At(0))
|
||||||
next := closure + 1
|
} else {
|
||||||
if next >= len(line) || line[next] != ':' {
|
for i := 0; i < segments.Len(); i++ {
|
||||||
return -1, -1
|
s := segments.At(i)
|
||||||
|
label = append(label, block.Value(s)...)
|
||||||
}
|
}
|
||||||
block.Advance(next + 1)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
block.AdvanceLine()
|
|
||||||
}
|
|
||||||
if closes < 0 {
|
|
||||||
return -1, -1
|
|
||||||
}
|
|
||||||
label := block.Value(text.NewSegment(open, closes))
|
|
||||||
if util.IsBlank(label) {
|
if util.IsBlank(label) {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
block.SkipSpaces()
|
block.SkipSpaces()
|
||||||
|
if block.Peek() != ':' {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
block.Advance(1)
|
||||||
|
block.SkipSpaces()
|
||||||
destination, ok := parseLinkDestination(block)
|
destination, ok := parseLinkDestination(block)
|
||||||
if !ok {
|
if !ok {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
line, segment = block.PeekLine()
|
line, _ = block.PeekLine()
|
||||||
isNewLine := line == nil || util.IsBlank(line)
|
isNewLine := line == nil || util.IsBlank(line)
|
||||||
|
|
||||||
endLine, _ := block.Position()
|
endLine, _ := block.Position()
|
||||||
|
|
@ -117,30 +112,12 @@ func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
block.Advance(1)
|
block.Advance(1)
|
||||||
open = -1
|
|
||||||
closes = -1
|
|
||||||
closer := opener
|
closer := opener
|
||||||
if opener == '(' {
|
if opener == '(' {
|
||||||
closer = ')'
|
closer = ')'
|
||||||
}
|
}
|
||||||
for {
|
segments, found = block.FindClosure(opener, closer, linkFindClosureOptions)
|
||||||
line, segment = block.PeekLine()
|
if !found {
|
||||||
if line == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if open < 0 {
|
|
||||||
open = segment.Start
|
|
||||||
}
|
|
||||||
closure := util.FindClosure(line, opener, closer, false, true)
|
|
||||||
if closure > -1 {
|
|
||||||
closes = segment.Start + closure
|
|
||||||
block.Advance(closure + 1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
block.AdvanceLine()
|
|
||||||
}
|
|
||||||
|
|
||||||
if closes < 0 {
|
|
||||||
if !isNewLine {
|
if !isNewLine {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
@ -148,20 +125,26 @@ func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) {
|
||||||
pc.AddReference(ref)
|
pc.AddReference(ref)
|
||||||
return startLine, endLine
|
return startLine, endLine
|
||||||
}
|
}
|
||||||
|
var title []byte
|
||||||
|
if segments.Len() == 1 {
|
||||||
|
title = block.Value(segments.At(0))
|
||||||
|
} else {
|
||||||
|
for i := 0; i < segments.Len(); i++ {
|
||||||
|
s := segments.At(i)
|
||||||
|
title = append(title, block.Value(s)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
line, segment = block.PeekLine()
|
line, _ = block.PeekLine()
|
||||||
if line != nil && !util.IsBlank(line) {
|
if line != nil && !util.IsBlank(line) {
|
||||||
if !isNewLine {
|
if !isNewLine {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
title := block.Value(text.NewSegment(open, closes))
|
|
||||||
ref := NewReference(label, destination, title)
|
ref := NewReference(label, destination, title)
|
||||||
pc.AddReference(ref)
|
pc.AddReference(ref)
|
||||||
return startLine, endLine
|
return startLine, endLine
|
||||||
}
|
}
|
||||||
|
|
||||||
title := block.Value(text.NewSegment(open, closes))
|
|
||||||
|
|
||||||
endLine, _ = block.Position()
|
endLine, _ = block.Position()
|
||||||
ref := NewReference(label, destination, title)
|
ref := NewReference(label, destination, title)
|
||||||
pc.AddReference(ref)
|
pc.AddReference(ref)
|
||||||
|
|
|
||||||
110
text/reader.go
110
text/reader.go
|
|
@ -70,6 +70,28 @@ type Reader interface {
|
||||||
|
|
||||||
// Match performs regular expression searching to current line.
|
// Match performs regular expression searching to current line.
|
||||||
FindSubMatch(reg *regexp.Regexp) [][]byte
|
FindSubMatch(reg *regexp.Regexp) [][]byte
|
||||||
|
|
||||||
|
// FindClosure finds corresponding closure.
|
||||||
|
FindClosure(opener, closer byte, options FindClosureOptions) (*Segments, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindClosureOptions is options for Reader.FindClosure
|
||||||
|
type FindClosureOptions struct {
|
||||||
|
// CodeSpan is a flag for the FindClosure. If this is set to true,
|
||||||
|
// FindClosure ignores closers in codespans.
|
||||||
|
CodeSpan bool
|
||||||
|
|
||||||
|
// Nesting is a flag for the FindClosure. If this is set to true,
|
||||||
|
// FindClosure allows nesting.
|
||||||
|
Nesting bool
|
||||||
|
|
||||||
|
// Newline is a flag for the FindClosure. If this is set to true,
|
||||||
|
// FindClosure searches for a closer over multiple lines.
|
||||||
|
Newline bool
|
||||||
|
|
||||||
|
// Advance is a flag for the FindClosure. If this is set to true,
|
||||||
|
// FindClosure advances pointers when closer is found.
|
||||||
|
Advance bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type reader struct {
|
type reader struct {
|
||||||
|
|
@ -92,6 +114,10 @@ func NewReader(source []byte) Reader {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *reader) FindClosure(opener, closer byte, options FindClosureOptions) (*Segments, bool) {
|
||||||
|
return findClosureReader(r, opener, closer, options)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *reader) ResetPosition() {
|
func (r *reader) ResetPosition() {
|
||||||
r.line = -1
|
r.line = -1
|
||||||
r.head = 0
|
r.head = 0
|
||||||
|
|
@ -272,6 +298,10 @@ func NewBlockReader(source []byte, segments *Segments) BlockReader {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *blockReader) FindClosure(opener, closer byte, options FindClosureOptions) (*Segments, bool) {
|
||||||
|
return findClosureReader(r, opener, closer, options)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *blockReader) ResetPosition() {
|
func (r *blockReader) ResetPosition() {
|
||||||
r.line = -1
|
r.line = -1
|
||||||
r.head = 0
|
r.head = 0
|
||||||
|
|
@ -541,3 +571,83 @@ func readRuneReader(r Reader) (rune, int, error) {
|
||||||
r.Advance(size)
|
r.Advance(size)
|
||||||
return rn, size, nil
|
return rn, size, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findClosureReader(r Reader, opener, closer byte, opts FindClosureOptions) (*Segments, bool) {
|
||||||
|
opened := 1
|
||||||
|
codeSpanOpener := 0
|
||||||
|
closed := false
|
||||||
|
orgline, orgpos := r.Position()
|
||||||
|
var ret *Segments
|
||||||
|
|
||||||
|
for {
|
||||||
|
bs, seg := r.PeekLine()
|
||||||
|
if bs == nil {
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for i < len(bs) {
|
||||||
|
c := bs[i]
|
||||||
|
if opts.CodeSpan && codeSpanOpener != 0 && c == '`' {
|
||||||
|
codeSpanCloser := 0
|
||||||
|
for ; i < len(bs); i++ {
|
||||||
|
if bs[i] == '`' {
|
||||||
|
codeSpanCloser++
|
||||||
|
} else {
|
||||||
|
i--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if codeSpanCloser == codeSpanOpener {
|
||||||
|
codeSpanOpener = 0
|
||||||
|
}
|
||||||
|
} else if codeSpanOpener == 0 && c == '\\' && i < len(bs)-1 && util.IsPunct(bs[i+1]) {
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
} else if opts.CodeSpan && codeSpanOpener == 0 && c == '`' {
|
||||||
|
for ; i < len(bs); i++ {
|
||||||
|
if bs[i] == '`' {
|
||||||
|
codeSpanOpener++
|
||||||
|
} else {
|
||||||
|
i--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (opts.CodeSpan && codeSpanOpener == 0) || !opts.CodeSpan {
|
||||||
|
if c == closer {
|
||||||
|
opened--
|
||||||
|
if opened == 0 {
|
||||||
|
if ret == nil {
|
||||||
|
ret = NewSegments()
|
||||||
|
}
|
||||||
|
ret.Append(seg.WithStop(seg.Start + i))
|
||||||
|
r.Advance(i + 1)
|
||||||
|
closed = true
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
} else if c == opener {
|
||||||
|
if !opts.Nesting {
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
opened++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if !opts.Newline {
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
r.AdvanceLine()
|
||||||
|
if ret == nil {
|
||||||
|
ret = NewSegments()
|
||||||
|
}
|
||||||
|
ret.Append(seg)
|
||||||
|
}
|
||||||
|
end:
|
||||||
|
if !opts.Advance {
|
||||||
|
r.SetPosition(orgline, orgpos)
|
||||||
|
}
|
||||||
|
if closed {
|
||||||
|
return ret, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,10 @@ func FirstNonSpacePosition(bs []byte) int {
|
||||||
// If codeSpan is set true, it ignores characters in code spans.
|
// If codeSpan is set true, it ignores characters in code spans.
|
||||||
// If allowNesting is set true, closures correspond to nested opener will be
|
// If allowNesting is set true, closures correspond to nested opener will be
|
||||||
// ignored.
|
// ignored.
|
||||||
|
//
|
||||||
|
// Deprecated: This function can not handle newlines. Many elements
|
||||||
|
// can be existed over multiple lines(e.g. link labels).
|
||||||
|
// Use text.Reader.FindClosure.
|
||||||
func FindClosure(bs []byte, opener, closure byte, codeSpan, allowNesting bool) int {
|
func FindClosure(bs []byte, opener, closure byte, codeSpan, allowNesting bool) int {
|
||||||
i := 0
|
i := 0
|
||||||
opened := 1
|
opened := 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue