From 6dc04b4b0200cca2ffe283acbde432de5b36bbb6 Mon Sep 17 00:00:00 2001 From: Niklas Fasching Date: Sun, 7 Jul 2019 09:52:23 +0200 Subject: [PATCH] Refactor footnote handling - Remove unused footnote section title option - Move away from maintaining a list of footnotes in the document (only needed for html export, potential maintainance overhead when modifying the document) and rather only build it on export when required. - HTML export: Rename all footnotes to numbers (so we can support anonymous footnote references by assigning them a number) and export footnotes in order of reference, not definition. The implementation of this makes it natural to also stop exporting unused footnote definitions so we do that as well. --- org/document.go | 17 +--------- org/footnote.go | 23 ------------- org/html_writer.go | 52 ++++++++++++++++++++-------- org/inline.go | 1 - org/testdata/footnotes.html | 66 ++++++++++++++---------------------- org/testdata/misc.html | 6 ++-- org/testdata/misc.org | 2 +- org/testdata/misc.pretty_org | 2 +- 8 files changed, 69 insertions(+), 100 deletions(-) diff --git a/org/document.go b/org/document.go index 8fdfc1c..6fa8530 100644 --- a/org/document.go +++ b/org/document.go @@ -35,7 +35,6 @@ type Document struct { Path string // Path of the file containing the parse input - used to resolve relative paths during parsing (e.g. INCLUDE). tokens []token Nodes []Node - Footnotes Footnotes Outline Outline // Outline is a Table Of Contents for the document and contains all sections (headline + content). BufferSettings map[string]string // Settings contains all settings that were parsed from keywords. Error error @@ -113,11 +112,7 @@ func (d *Document) Write(w Writer) (out string, err error) { func (c *Configuration) Parse(input io.Reader, path string) (d *Document) { outlineSection := &Section{} d = &Document{ - Configuration: c, - Footnotes: Footnotes{ - Title: "Footnotes", - Definitions: map[string]*FootnoteDefinition{}, - }, + Configuration: c, Outline: Outline{outlineSection, outlineSection, 0}, BufferSettings: map[string]string{}, Path: path, @@ -243,16 +238,6 @@ func (d *Document) parseMany(i int, stop stopFn) (int, []Node) { return i - start, nodes } -func (d *Document) addFootnote(name string, definition *FootnoteDefinition) { - if definition != nil { - if _, exists := d.Footnotes.Definitions[name]; exists { - d.Log.Printf("Footnote [fn:%s] redefined! %#v", name, definition) - } - d.Footnotes.Definitions[name] = definition - } - d.Footnotes.addOrder = append(d.Footnotes.addOrder, name) -} - func (d *Document) addHeadline(headline *Headline) int { current := &Section{Headline: headline} d.Outline.last.add(current) diff --git a/org/footnote.go b/org/footnote.go index 628452c..660e244 100644 --- a/org/footnote.go +++ b/org/footnote.go @@ -4,12 +4,6 @@ import ( "regexp" ) -type Footnotes struct { - Title string - Definitions map[string]*FootnoteDefinition - addOrder []string -} - type FootnoteDefinition struct { Name string Children []Node @@ -35,24 +29,7 @@ func (d *Document) parseFootnoteDefinition(i int, parentStop stopFn) (int, Node) } consumed, nodes := d.parseMany(i, stop) definition := FootnoteDefinition{name, nodes, false} - d.addFootnote(name, &definition) return consumed, definition } -func (fs *Footnotes) Ordered() []FootnoteDefinition { - m := map[string]bool{} - definitions, inlineDefinitions := []FootnoteDefinition{}, []FootnoteDefinition{} - for _, name := range fs.addOrder { - if isDuplicate := m[name]; !isDuplicate { - m[name] = true - if definition := *fs.Definitions[name]; definition.Inline { - inlineDefinitions = append(inlineDefinitions, definition) - } else { - definitions = append(definitions, definition) - } - } - } - return append(definitions, inlineDefinitions...) -} - func (n FootnoteDefinition) String() string { return orgWriter.nodesAsString(n) } diff --git a/org/html_writer.go b/org/html_writer.go index 4e38274..33f3acc 100644 --- a/org/html_writer.go +++ b/org/html_writer.go @@ -19,6 +19,12 @@ type HTMLWriter struct { HighlightCodeBlock func(source, lang string) string htmlEscape bool log *log.Logger + footnotes footnotes +} + +type footnotes struct { + mapping map[string]int + list []*FootnoteDefinition } var emphasisTags = map[string][]string{ @@ -55,6 +61,9 @@ func NewHTMLWriter() *HTMLWriter { HighlightCodeBlock: func(source, lang string) string { return fmt.Sprintf("%s\n
\n%s\n
\n", `
`, html.EscapeString(source)) }, + footnotes: footnotes{ + mapping: map[string]int{}, + }, } } @@ -129,26 +138,23 @@ func (w *HTMLWriter) WriteInclude(i Include) { WriteNodes(w, i.Resolve()) } -func (w *HTMLWriter) WriteFootnoteDefinition(FootnoteDefinition) {} - -func (w *HTMLWriter) writeFootnoteDefinition(f FootnoteDefinition) { - n := f.Name - w.WriteString(`
` + "\n") - w.WriteString(fmt.Sprintf(`%s`, n, n, n) + "\n") - w.WriteString(`
` + "\n") - WriteNodes(w, f.Children...) - w.WriteString("
\n
\n") +func (w *HTMLWriter) WriteFootnoteDefinition(f FootnoteDefinition) { + w.footnotes.updateDefinition(f) } func (w *HTMLWriter) WriteFootnotes(d *Document) { - if !w.document.GetOption("f") || len(d.Footnotes.Definitions) == 0 { + if !w.document.GetOption("f") || len(w.footnotes.list) == 0 { return } w.WriteString(`
` + "\n") w.WriteString(`
` + "\n") w.WriteString(`
` + "\n") - for _, definition := range d.Footnotes.Ordered() { - w.writeFootnoteDefinition(definition) + for i, definition := range w.footnotes.list { + w.WriteString(`
` + "\n") + w.WriteString(fmt.Sprintf(`%d`, i, i, i) + "\n") + w.WriteString(`
` + "\n") + WriteNodes(w, definition.Children...) + w.WriteString("
\n
\n") } w.WriteString("
\n
\n") } @@ -245,8 +251,8 @@ func (w *HTMLWriter) WriteFootnoteLink(l FootnoteLink) { if !w.document.GetOption("f") { return } - n := html.EscapeString(l.Name) - w.WriteString(fmt.Sprintf(`%s`, n, n, n)) + id := w.footnotes.add(l) + w.WriteString(fmt.Sprintf(`%d`, id, id, id)) } func (w *HTMLWriter) WriteTimestamp(t Timestamp) { @@ -445,3 +451,21 @@ func setHTMLAttribute(attributes []h.Attribute, k, v string) []h.Attribute { } return append(attributes, h.Attribute{Namespace: "", Key: k, Val: v}) } + +func (fs *footnotes) add(f FootnoteLink) int { + if i, ok := fs.mapping[f.Name]; ok && f.Name != "" { + return i + } + fs.list = append(fs.list, f.Definition) + i := len(fs.list) - 1 + if f.Name != "" { + fs.mapping[f.Name] = i + } + return i +} + +func (fs *footnotes) updateDefinition(f FootnoteDefinition) { + if i, ok := fs.mapping[f.Name]; ok { + fs.list[i] = &f + } +} diff --git a/org/inline.go b/org/inline.go index ab58b7b..53f60d3 100644 --- a/org/inline.go +++ b/org/inline.go @@ -171,7 +171,6 @@ func (d *Document) parseFootnoteReference(input string, start int) (int, Node) { link := FootnoteLink{name, nil} if definition != "" { link.Definition = &FootnoteDefinition{name, []Node{Paragraph{d.parseInline(definition)}}, true} - d.addFootnote(name, link.Definition) } return len(m[0]), link } diff --git a/org/testdata/footnotes.html b/org/testdata/footnotes.html index d449b1c..d71b549 100644 --- a/org/testdata/footnotes.html +++ b/org/testdata/footnotes.html @@ -12,12 +12,12 @@ Using some footnotes @@ -36,33 +36,16 @@ Rather, they are gathered and exported at the end of the document in the footnot Footnotes

-Please note that the footnotes section is not automatically excluded from the export like in emacs. 7 +Please note that the footnotes section is not automatically excluded from the export like in emacs. 4

-this is not part of 7 anymore as there are 2 blank lines in between! +this is not part of 4 anymore as there are 2 blank lines in between!


-4 -
-

-so this definition will not be at the end of this section in the exported document. -Rather, it will be somewhere down below in the footnotes section. -

-
-
-
-5 -
-

-another unused footnote (this definition overwrites the previous definition of fn:5) -

-
-
-
-1 +0

https://www.example.com @@ -115,15 +98,7 @@ and tables

-
-6 +1

Footnotes break after two consecutive empty lines - just like paragraphs - see https://orgmode.org/worg/dev/org-syntax.html. @@ -132,15 +107,6 @@ This shouldn't happen when the definition line and the line after that are e

-7 -
-

-There's multiple reasons for that. Among others, doing so requires i18n (to recognize the section) and silently -hides content before and after the footnotes. -

-
-
-
2

@@ -148,5 +114,23 @@ the inline footnote definition

+
+3 +
+

+so this definition will not be at the end of this section in the exported document. +Rather, it will be somewhere down below in the footnotes section. +

+
+
+
+4 +
+

+There's multiple reasons for that. Among others, doing so requires i18n (to recognize the section) and silently +hides content before and after the footnotes. +

+
+
diff --git a/org/testdata/misc.html b/org/testdata/misc.html index 6339c94..0ec917c 100644 --- a/org/testdata/misc.html +++ b/org/testdata/misc.html @@ -409,7 +409,7 @@ src/example/export blocks should not be converted! #87: Markup in footnotes is rendered literally

-footnotes can contain markup - and other elements and stuff 2 +footnotes can contain markup - and other elements and stuff 0 1

DONE @@ -472,7 +472,7 @@ Footnotes
-1 +0

a footnote with markup @@ -492,7 +492,7 @@ because that's possible

-2 +1

that also goes for inline footnote definitions diff --git a/org/testdata/misc.org b/org/testdata/misc.org index 9615290..864618d 100644 --- a/org/testdata/misc.org +++ b/org/testdata/misc.org @@ -108,7 +108,7 @@ also, consecutive dashes inside : --, --- *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/87][#87]]: Markup in footnotes is rendered literally -footnotes can contain *markup* - and other elements and stuff [fn:2:that also goes for *inline* footnote /definitions/] +footnotes can contain *markup* - and other elements and stuff [fn:1] [fn:2:that also goes for *inline* footnote /definitions/] *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/92][#92]]: src blocks only render in caps The behaviour of Org mode =