Skip to content

Commit

Permalink
Parse labels in [[wiki links | label]] and support escaped characters
Browse files Browse the repository at this point in the history
  • Loading branch information
mickael-menu committed Feb 28, 2021
1 parent a5b7639 commit 6175a73
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 43 deletions.
104 changes: 69 additions & 35 deletions adapter/markdown/extensions/wikilink.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package extensions
import (
"strings"

"github.com/mickael-menu/zk/core/note"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)

// WikiLink is an extension parsing wiki links and neuron's Folgezettel.
// WikiLink is an extension parsing wiki links and Neuron's Folgezettel.
//
// For example, [[wiki link]], [[[legacy downlink]]], #[[uplink]], [[downlink]]#.
var WikiLink = &wikiLink{}
Expand All @@ -34,32 +35,46 @@ func (p *wlParser) Trigger() []byte {
func (p *wlParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, _ := block.PeekLine()

openerCharCount := 0
opened := false
closed := false
content := []byte{}
closerCharCount := 0
endPos := 0
var (
href string
label string
rel note.LinkRelation
)

var (
opened = false // Found at least [[
closed = false // Found at least ]]
escaping = false // Found a backslash, next character will be literal
parsingLabel = false // Found a | in a Wikilink, now we parse the link's label
openerCharCount = 0 // Number of [ encountered
closerCharCount = 0 // Number of ] encountered
endPos = 0 // Last position of the link in the line
)

// Folgezettel direction: -1 down, 0 unknown, 1 up
direction := 0
appendChar := func(c byte) {
if parsingLabel {
label += string(c)
} else {
href += string(c)
}
}

for i, char := range line {
endPos = i

if closed {
// Supports trailing hash syntax for neuron's Folgezettel, e.g. [[id]]#
// Supports trailing hash syntax for Neuron's Folgezettel, e.g. [[id]]#
if char == '#' {
direction = -1
rel = note.LinkRelationDown
}
break
}

if !opened {
switch char {
// Supports leading hash syntax for neuron's Folgezettel, e.g. #[[id]]
// Supports leading hash syntax for Neuron's Folgezettel, e.g. #[[id]]
case '#':
direction = 1
rel = note.LinkRelationUp
continue
case '[':
openerCharCount += 1
Expand All @@ -72,40 +87,59 @@ func (p *wlParser) Parse(parent ast.Node, block text.Reader, pc parser.Context)
}
opened = true

if char == ']' {
closerCharCount += 1
if closerCharCount == openerCharCount {
closed = true
// neuron's legacy [[[Folgezettel]]].
if closerCharCount == 3 {
direction = -1
if !escaping {
switch char {

case '|': // [[href | label]]
parsingLabel = true
continue

case '\\':
escaping = true
continue

case ']':
closerCharCount += 1
if closerCharCount == openerCharCount {
closed = true
// Neuron's legacy [[[Folgezettel]]].
if closerCharCount == 3 {
rel = note.LinkRelationDown
}
}
continue
}
} else {
if closerCharCount > 0 {
content = append(content, strings.Repeat("]", closerCharCount)...)
closerCharCount = 0
}
escaping = false

// Found incomplete number of closing brackets to close the link.
// We add them to the HREF and reset the count.
if closerCharCount > 0 {
for i := 0; i < closerCharCount; i++ {
appendChar(']')
}
content = append(content, char)
closerCharCount = 0
}
appendChar(char)
}

if !closed || len(content) == 0 {
if !closed || len(href) == 0 {
return nil
}

block.Advance(endPos)

link := ast.NewLink()
link.Destination = content

// Title will be parsed as the link's rels.
switch direction {
case -1:
link.Title = []byte("down")
case 1:
link.Title = []byte("up")
href = strings.TrimSpace(href)
label = strings.TrimSpace(label)
if len(label) == 0 {
label = href
}

link := ast.NewLink()
link.Destination = []byte(href)
// Title will be parsed as the link's rel by the Markdown parser.
link.Title = []byte(rel)
link.AppendChild(link, ast.NewString([]byte(label)))

return link
}
44 changes: 38 additions & 6 deletions adapter/markdown/markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,18 @@ A link can have [one relation](one "rel-1") or [several relations](several "rel-
An https://inline-link.com and http://another-inline-link.com.
A [[Wiki link]] is surrounded by two brackets.
A [[Wiki link]] is surrounded by [[2-brackets | two brackets]].
It can contain [[esca]\]ped \[chara\\cters]].
A [[[Folgezettel link]]] is surrounded by three brackets.
Neuron also supports a [[trailing hash]]# for Folgezettel links.
A #[[leading hash]] is used for #uplinks.
Neuron links with titles: [[trailing|Trailing link]]# #[[leading | Leading link]]
[External links](http://example.com) are marked [as such](ftp://domain).
`, []note.Link{
{
Expand Down Expand Up @@ -234,33 +238,61 @@ A link can have [one relation](one "rel-1") or [several relations](several "rel-
Snippet: "An https://inline-link.com and http://another-inline-link.com.",
},
{
Title: "",
Title: "Wiki link",
Href: "Wiki link",
External: false,
Rels: []string{},
Snippet: "A [[Wiki link]] is surrounded by two brackets.",
Snippet: "A [[Wiki link]] is surrounded by [[2-brackets | two brackets]].",
},
{
Title: "two brackets",
Href: "2-brackets",
External: false,
Rels: []string{},
Snippet: "A [[Wiki link]] is surrounded by [[2-brackets | two brackets]].",
},
{
Title: `esca]]ped [chara\cters`,
Href: `esca]]ped [chara\cters`,
External: false,
Rels: []string{},
Snippet: `It can contain [[esca]\]ped \[chara\\cters]].`,
},
{
Title: "",
Title: "Folgezettel link",
Href: "Folgezettel link",
External: false,
Rels: []string{"down"},
Snippet: "A [[[Folgezettel link]]] is surrounded by three brackets.",
},
{
Title: "",
Title: "trailing hash",
Href: "trailing hash",
External: false,
Rels: []string{"down"},
Snippet: "Neuron also supports a [[trailing hash]]# for Folgezettel links.",
},
{
Title: "",
Title: "leading hash",
Href: "leading hash",
External: false,
Rels: []string{"up"},
Snippet: "A #[[leading hash]] is used for #uplinks.",
},
{
Title: "Trailing link",
Href: "trailing",
External: false,
Rels: []string{"down"},
Snippet: "Neuron links with titles: [[trailing|Trailing link]]# #[[leading | Leading link]]",
},
{
Title: "Leading link",
Href: "leading",
External: false,
Rels: []string{"up"},
Snippet: "Neuron links with titles: [[trailing|Trailing link]]# #[[leading | Leading link]]",
},
{
Title: "External links",
Href: "http://example.com",
Expand Down
10 changes: 10 additions & 0 deletions core/note/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ type Link struct {
Snippet string
}

// LinkRelation defines the relationship between a link's source and target.
type LinkRelation string

const (
// LinkRelationDown defines the target note as a child of the source.
LinkRelationDown LinkRelation = "down"
// LinkRelationDown defines the target note as a parent of the source.
LinkRelationUp LinkRelation = "up"
)

type Parser interface {
Parse(source string) (*Content, error)
}
2 changes: 0 additions & 2 deletions run

This file was deleted.

0 comments on commit 6175a73

Please sign in to comment.