diff --git a/link.go b/link.go index 2b3e4a1..3b42b1c 100644 --- a/link.go +++ b/link.go @@ -13,6 +13,15 @@ import ( "golang.org/x/text/cases" ) +type RefStyle int + +const ( + NoRef RefStyle = iota + Full + Collpased + Shortcut +) + // Note: Link and Image are the same underlying struct by design, // so that code can safely convert between *Link and *Image. @@ -24,6 +33,9 @@ type Link struct { URL string Title string TitleChar byte // ', " or ) + + Label string + RefStyle RefStyle } // An Image is an [Inline] representing an [image] ( tag). @@ -34,6 +46,9 @@ type Image struct { URL string Title string TitleChar byte + + Label string + RefStyle RefStyle } func (*Link) Inline() {} @@ -57,14 +72,25 @@ func (x *Link) printMarkdown(p *printer) { for _, c := range x.Inner { c.printMarkdown(p) } - p.WriteString("](") - u := mdLinkEscaper.Replace(x.URL) - if u == "" || strings.ContainsAny(u, " ") { - u = "<" + u + ">" + p.WriteByte(']') + switch x.RefStyle { + case NoRef: + p.WriteByte('(') + u := mdLinkEscaper.Replace(x.URL) + if u == "" || strings.ContainsAny(u, " ") { + u = "<" + u + ">" + } + p.WriteString(u) + printLinkTitleMarkdown(p, x.Title, x.TitleChar) + p.WriteByte(')') + case Full: + p.WriteByte('[') + p.WriteString(x.Label) + p.WriteByte(']') + case Collpased: + p.WriteString("[]") + case Shortcut: } - p.WriteString(u) - printLinkTitleMarkdown(p, x.Title, x.TitleChar) - p.WriteByte(')') } func printLinkTitleMarkdown(p *printer, title string, titleChar byte) { @@ -193,8 +219,9 @@ func parseLinkClose(p *parser, s string, start int, open *openPlain) (*Link, int if !ok { break } - if link, ok := p.links[normalizeLabel(label)]; ok { - return &Link{URL: link.URL, Title: link.Title}, i, true + label = normalizeLabel(label) + if link, ok := p.links[label]; ok { + return &Link{URL: link.URL, Title: link.Title, Label: label, RefStyle: Full}, i, true } // Note: Could break here, but CommonMark dingus does not // fall back to trying Text for [Text][Label] when Label is unknown. @@ -205,12 +232,15 @@ func parseLinkClose(p *parser, s string, start int, open *openPlain) (*Link, int // Collapsed or shortcut reference link: [Text][] or [Text]. end := i + 1 + refStyle := Shortcut if strings.HasPrefix(s[end:], "[]") { end += 2 + refStyle = Collpased } - if link, ok := p.links[normalizeLabel(s[open.i:i])]; ok { - return &Link{URL: link.URL, Title: link.Title}, end, true + label := normalizeLabel(s[open.i:i]) + if link, ok := p.links[label]; ok { + return &Link{URL: link.URL, Title: link.Title, Label: label, RefStyle: refStyle}, end, true } return nil, 0, false } diff --git a/md_test.go b/md_test.go index 4af400b..bf3df04 100644 --- a/md_test.go +++ b/md_test.go @@ -43,6 +43,7 @@ var roundTripFailures = map[string]bool{ "TestToHTML/spec0.29/57": true, // setext heading "TestToHTML/spec0.29/63": true, // setext heading "TestToHTML/spec0.29/65": true, // newline in heading + "TestToHTML/spec0.29/163": true, // escaped bracket in label "TestToHTML/spec0.29/171": true, // link ref def "TestToHTML/spec0.29/208": true, // weird list "TestToHTML/spec0.29/227": true, // weird list @@ -58,6 +59,7 @@ var roundTripFailures = map[string]bool{ "TestToHTML/spec0.29/331": true, // backtick spaces "TestToHTML/spec0.29/349": true, // backticks "TestToHTML/spec0.29/502": true, // escape quotes + "TestToHTML/spec0.29/545": true, // escaped bracket in label "TestToHTML/spec0.30/26": true, // escape plain "TestToHTML/spec0.30/37": true, // escape plain @@ -72,6 +74,7 @@ var roundTripFailures = map[string]bool{ "TestToHTML/spec0.30/87": true, // setext heading "TestToHTML/spec0.30/93": true, // setext heading "TestToHTML/spec0.30/95": true, // newline in heading + "TestToHTML/spec0.30/194": true, // escaped bracket in label "TestToHTML/spec0.30/202": true, // link ref def "TestToHTML/spec0.30/238": true, // weird list "TestToHTML/spec0.30/257": true, // weird list @@ -81,6 +84,7 @@ var roundTripFailures = map[string]bool{ "TestToHTML/spec0.30/331": true, // backtick spaces "TestToHTML/spec0.30/349": true, // backticks "TestToHTML/spec0.30/505": true, // escape quotes + "TestToHTML/spec0.30/548": true, // escaped bracket in label "TestToHTML/spec0.31.2/26": true, // escape plain "TestToHTML/spec0.31.2/37": true, // escape plain @@ -95,6 +99,7 @@ var roundTripFailures = map[string]bool{ "TestToHTML/spec0.31.2/87": true, // setext heading "TestToHTML/spec0.31.2/93": true, // setext heading "TestToHTML/spec0.31.2/95": true, // newline in heading + "TestToHTML/spec0.31.2/194": true, // escaped bracket in label "TestToHTML/spec0.31.2/202": true, // link ref def "TestToHTML/spec0.31.2/238": true, // weird list "TestToHTML/spec0.31.2/257": true, // weird list @@ -104,6 +109,7 @@ var roundTripFailures = map[string]bool{ "TestToHTML/spec0.31.2/331": true, // backtick spaces "TestToHTML/spec0.31.2/349": true, // backticks "TestToHTML/spec0.31.2/506": true, // escape quotes + "TestToHTML/spec0.31.2/549": true, // escaped bracket in label "TestToHTML/table/gfm200": true, // table "TestToHTML/table/2": true, // table diff --git a/testdata/linkref_fmt.txt b/testdata/linkref_fmt.txt index 35f95e8..f0a23c0 100644 --- a/testdata/linkref_fmt.txt +++ b/testdata/linkref_fmt.txt @@ -37,3 +37,15 @@ A document. [r1]: u1 (title1) [r2]: u2 "title2" [r3]: u3 'title3' +-- reflink-full -- +[Foo bar][baz] + +[baz]: u1 +-- reflink-collapsed -- +[Foo bar][] + +[foo bar]: u1 +-- reflink-shortcut -- +[Foo bar] + +[foo bar]: u1