Merge pull request #302 from russross/v2-move-footnotest-to-html

v2: move footnotes to html
This commit is contained in:
Vytautas Šaltenis 2016-09-10 11:32:03 +03:00 committed by GitHub
commit b91b5719eb
5 changed files with 86 additions and 53 deletions

View File

@ -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?

12
html.go
View File

@ -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<div class=\"footnotes\">\n\n"))
r.out(w, tag("hr", attrs, r.Flags&UseXHTML != 0))
r.cr(w)
}
r.cr(w)
if node.Parent.Type == Item && node.Parent.Parent.Tight {
r.cr(w)
@ -611,6 +612,9 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
r.cr(w)
}
if node.IsFootnotesList {
r.out(w, []byte("\n</div>\n"))
}
}
case Item:
tagName := "li"

View File

@ -922,6 +922,12 @@ what happens here
</div>
`,
`This text does not reference a footnote.
[^footnote]: But it has a footnote! And it gets omitted.
`,
"<p>This text does not reference a footnote.</p>\n",
}
func TestFootnotes(t *testing.T) {

View File

@ -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(`<div class="footnotes">`)))
p.addBlock(HorizontalRule, nil)
block := p.addBlock(List, nil)
block.IsFootnotesList = true
block.ListFlags = ListTypeOrdered
flags := ListItemBeginningOfList
// Note: this loop is intentionally explicit, not range-form. This is
@ -422,7 +420,7 @@ func (p *parser) parseRefsToAST() {
for i := 0; i < len(p.notes); i++ {
ref := p.notes[i]
block := p.addBlock(Item, nil)
block.ListFlags = ListTypeOrdered
block.ListFlags = flags | ListTypeOrdered
block.RefLink = ref.link
if ref.hasBlock {
flags |= ListItemContainsBlock
@ -435,7 +433,6 @@ func (p *parser) parseRefsToAST() {
above := block.Parent
finalizeList(block)
p.tip = above
finalizeHTMLBlock(p.addBlock(HTMLBlock, []byte("</div>")))
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:
//
// <p><a href=\"/url/\" title=\"title\">link</a></p>
//
// 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: <some positive int>,
// }
//
// 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] != ':' {

19
node.go
View File

@ -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 <p>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 <p>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.