diff --git a/block.go b/block.go
index 4a7df43..42bed26 100644
--- a/block.go
+++ b/block.go
@@ -1380,7 +1380,10 @@ func (p *parser) paragraph(data []byte) int {
// line: index of 1st char of current line
// i: index of cursor/end of current line
var prev, line, i int
-
+ tabSize := TabSizeDefault
+ if p.flags&TabSizeEight != 0 {
+ tabSize = TabSizeDouble
+ }
// keep going until we find something to mark the end of the paragraph
for i < len(data) {
// mark the beginning of the current line
@@ -1388,6 +1391,14 @@ func (p *parser) paragraph(data []byte) int {
current := data[i:]
line = i
+ // did we find a reference or a footnote? If so, end a paragraph
+ // preceding it and report that we have consumed up to the end of that
+ // reference:
+ if refEnd := isReference(p, current, tabSize); refEnd > 0 {
+ p.renderParagraph(data[:i])
+ return i + refEnd
+ }
+
// did we find a blank line marking the end of the paragraph?
if n := p.isEmpty(current); n > 0 {
// did this blank line followed by a definition list item?
diff --git a/html.go b/html.go
index 3f3cf32..54f2bea 100644
--- a/html.go
+++ b/html.go
@@ -589,10 +589,11 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
tagName = "dl"
}
if entering {
- // var start = node.listStart;
- // if (start !== null && start !== 1) {
- // attrs.push(['start', start.toString()]);
- // }
+ if node.IsFootnotesList {
+ r.out(w, []byte("\n
\n"))
+ }
}
case Item:
tagName := "li"
diff --git a/inline_test.go b/inline_test.go
index 4be754c..598e5e6 100644
--- a/inline_test.go
+++ b/inline_test.go
@@ -922,6 +922,12 @@ what happens here
`,
+
+ `This text does not reference a footnote.
+
+[^footnote]: But it has a footnote! And it gets omitted.
+`,
+ "This text does not reference a footnote.
\n",
}
func TestFootnotes(t *testing.T) {
diff --git a/markdown.go b/markdown.go
index 71b41e5..7fa0e20 100644
--- a/markdown.go
+++ b/markdown.go
@@ -75,7 +75,7 @@ const (
ListTypeTerm
ListItemContainsBlock
- ListItemBeginningOfList
+ ListItemBeginningOfList // TODO: figure out if this is of any use now
ListItemEndOfList
)
@@ -387,8 +387,7 @@ func Parse(input []byte, opts Options) *Node {
p.notes = make([]*reference, 0)
}
- first := firstPass(p, input)
- secondPass(p, first)
+ p.block(preprocess(p, input))
// Walk the tree and finish up some of unfinished blocks
for p.tip != nil {
p.finalize(p.tip)
@@ -410,9 +409,8 @@ func (p *parser) parseRefsToAST() {
return
}
p.tip = p.doc
- finalizeHTMLBlock(p.addBlock(HTMLBlock, []byte(`")))
block.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Paragraph || node.Type == Header {
p.inline(node, node.content)
@@ -445,12 +442,11 @@ func (p *parser) parseRefsToAST() {
})
}
-// first pass:
+// preprocess does a preparatory first pass over the input:
// - normalize newlines
-// - extract references (outside of fenced code blocks)
// - expand tabs (outside of fenced code blocks)
// - copy everything else
-func firstPass(p *parser, input []byte) []byte {
+func preprocess(p *parser, input []byte) []byte {
var out bytes.Buffer
tabSize := TabSizeDefault
if p.flags&TabSizeEight != 0 {
@@ -479,9 +475,6 @@ func firstPass(p *parser, input []byte) []byte {
if end > beg {
if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
out.Write(input[beg:end])
- } else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 {
- beg += refEnd
- continue
} else {
expandTabs(&out, input[beg:end], tabSize)
}
@@ -506,29 +499,6 @@ func firstPass(p *parser, input []byte) []byte {
return out.Bytes()
}
-// second pass: actual rendering
-func secondPass(p *parser, input []byte) {
- p.block(input)
-
- if p.flags&Footnotes != 0 && len(p.notes) > 0 {
- flags := ListItemBeginningOfList
- for i := 0; i < len(p.notes); i++ {
- ref := p.notes[i]
- if ref.hasBlock {
- flags |= ListItemContainsBlock
- p.block(ref.title)
- } else {
- p.inline(nil, ref.title)
- }
- flags &^= ListItemBeginningOfList | ListItemContainsBlock
- }
- }
-
- if p.nesting != 0 {
- panic("Nesting level did not end at zero")
- }
-}
-
//
// Link references
//
@@ -558,13 +528,50 @@ func secondPass(p *parser, input []byte) {
//
// are not yet supported.
-// References are parsed and stored in this struct.
+// reference holds all information necessary for a reference-style links or
+// footnotes.
+//
+// Consider this markdown with reference-style links:
+//
+// [link][ref]
+//
+// [ref]: /url/ "tooltip title"
+//
+// It will be ultimately converted to this HTML:
+//
+// link
+//
+// And a reference structure will be populated as follows:
+//
+// p.refs["ref"] = &reference{
+// link: "/url/",
+// title: "tooltip title",
+// }
+//
+// Alternatively, reference can contain information about a footnote. Consider
+// this markdown:
+//
+// Text needing a footnote.[^a]
+//
+// [^a]: This is the note
+//
+// A reference structure will be populated as follows:
+//
+// p.refs["a"] = &reference{
+// link: "a",
+// title: "This is the note",
+// noteID: ,
+// }
+//
+// TODO: As you can see, it begs for splitting into two dedicated structures
+// for refs and for footnotes.
type reference struct {
link []byte
title []byte
noteID int // 0 if not a footnote ref
hasBlock bool
- text []byte
+
+ text []byte // only gets populated by refOverride feature with Reference.Text
}
func (r *reference) String() string {
@@ -610,7 +617,11 @@ func isReference(p *parser, data []byte, tabSize int) int {
return 0
}
idEnd := i
-
+ // footnotes can have empty ID, like this: [^], but a reference can not be
+ // empty like this: []. Break early if it's not a footnote and there's no ID
+ if noteID == 0 && idOffset == idEnd {
+ return 0
+ }
// spacer: colon (space | tab)* newline? (space | tab)*
i++
if i >= len(data) || data[i] != ':' {
diff --git a/node.go b/node.go
index 3cebd11..983ec10 100644
--- a/node.go
+++ b/node.go
@@ -69,20 +69,21 @@ func (t NodeType) String() string {
return nodeTypeNames[t]
}
-// ListData contains fields relevant to a List node type.
+// ListData contains fields relevant to a List and Item node type.
type ListData struct {
- ListFlags ListType
- Tight bool // Skip s around list item data if true
- BulletChar byte // '*', '+' or '-' in bullet lists
- Delimiter byte // '.' or ')' after the number in ordered lists
- RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
+ ListFlags ListType
+ Tight bool // Skip
s around list item data if true
+ BulletChar byte // '*', '+' or '-' in bullet lists
+ Delimiter byte // '.' or ')' after the number in ordered lists
+ RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
+ IsFootnotesList bool // This is a list of footnotes
}
// LinkData contains fields relevant to a Link node type.
type LinkData struct {
- Destination []byte
- Title []byte
- NoteID int
+ Destination []byte // Destination is what goes into a href
+ Title []byte // Title is the tooltip thing that goes in a title attribute
+ NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote
}
// CodeBlockData contains fields relevant to a CodeBlock node type.