Improve footnote handling

- Footnotes separator rather than headline to get around i18n
- Warn on footnote redefinition
- Do not export footnote definitions at point of definition, only in the
  footnote section.
- Do not automatically exclude Footnotes section to get around possibly hiding
  other content of such a section - and i18n.
  The user has the choice of explicitly hiding the section via a :noexport:
  tag.

and some other refactoring
This commit is contained in:
Niklas Fasching 2018-12-26 15:23:23 +01:00
parent beff0c0d8a
commit eb7db9b968
11 changed files with 114 additions and 60 deletions

View file

@ -14,7 +14,7 @@ type Document struct {
Path string
tokens []token
Nodes []Node
Footnotes *Footnotes
Footnotes Footnotes
StatusKeywords []string
MaxEmphasisNewLines int
AutoLink bool
@ -72,7 +72,7 @@ func FrontMatterHandler(fm FrontMatter, k, v string) error {
func NewDocument() *Document {
return &Document{
Footnotes: &Footnotes{
Footnotes: Footnotes{
Title: "Footnotes",
Definitions: map[string]*FootnoteDefinition{},
},
@ -222,6 +222,16 @@ 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 tokenize(line string) token {
for _, lexFn := range lexFns {
if token, ok := lexFn(line); ok {

View file

@ -35,17 +35,10 @@ func (d *Document) parseFootnoteDefinition(i int, parentStop stopFn) (int, Node)
}
consumed, nodes := d.parseMany(i, stop)
definition := FootnoteDefinition{name, nodes, false}
d.Footnotes.add(name, &definition)
d.addFootnote(name, &definition)
return consumed, definition
}
func (fs *Footnotes) add(name string, definition *FootnoteDefinition) {
if definition != nil {
fs.Definitions[name] = definition
}
fs.addOrder = append(fs.addOrder, name)
}
func (fs *Footnotes) Ordered() []FootnoteDefinition {
m := map[string]bool{}
definitions, inlineDefinitions := []FootnoteDefinition{}, []FootnoteDefinition{}

View file

@ -13,11 +13,10 @@ import (
type HTMLWriter struct {
stringBuilder
HighlightCodeBlock func(source, lang string) string
FootnotesHeadingTitle string
htmlEscape bool
excludeTags []string
log *log.Logger
HighlightCodeBlock func(source, lang string) string
htmlEscape bool
excludeTags []string
log *log.Logger
}
var emphasisTags = map[string][]string{
@ -45,8 +44,7 @@ var listItemStatuses = map[string]string{
func NewHTMLWriter() *HTMLWriter {
return &HTMLWriter{
htmlEscape: true,
FootnotesHeadingTitle: "Footnotes",
htmlEscape: true,
HighlightCodeBlock: func(source, lang string) string {
return fmt.Sprintf("%s\n<pre>\n%s\n</pre>\n</div>", `<div class="highlight">`, html.EscapeString(source))
},
@ -95,7 +93,7 @@ func (w *HTMLWriter) writeNodes(ns ...Node) {
continue
case FootnoteDefinition:
w.writeFootnoteDefinition(n)
continue
case List:
w.writeList(n)
@ -191,24 +189,19 @@ func (w *HTMLWriter) writeFootnoteDefinition(f FootnoteDefinition) {
}
func (w *HTMLWriter) writeFootnotes(d *Document) {
fs := d.Footnotes
if len(fs.Definitions) == 0 {
if len(d.Footnotes.Definitions) == 0 {
return
}
w.WriteString(`<div class="footnotes">` + "\n")
w.WriteString(`<h1 class="footnotes-title">` + fs.Title + `</h1>` + "\n")
w.WriteString(`<hr class="footnotes-separatator">` + "\n")
w.WriteString(`<div class="footnote-definitions">` + "\n")
for _, definition := range d.Footnotes.Ordered() {
w.writeNodes(definition)
w.writeFootnoteDefinition(definition)
}
w.WriteString("</div>\n</div>\n")
}
func (w *HTMLWriter) writeHeadline(h Headline) {
title := w.nodesAsString(h.Title...)
if h.Lvl == 1 && title == w.FootnotesHeadingTitle {
return
}
for _, excludeTag := range w.excludeTags {
for _, tag := range h.Tags {
if excludeTag == tag {
@ -230,7 +223,7 @@ func (w *HTMLWriter) writeHeadline(h Headline) {
w.WriteString(fmt.Sprintf(`<span class="priority">[%s]</span>`, h.Priority) + "\n")
}
w.WriteString(title)
w.writeNodes(h.Title...)
if len(h.Tags) != 0 {
tags := make([]string, len(h.Tags))
for i, tag := range h.Tags {
@ -337,7 +330,7 @@ func (w *HTMLWriter) writeDescriptiveListItem(di DescriptiveListItem) {
}
func (w *HTMLWriter) writeParagraph(p Paragraph) {
if isEmptyLineParagraph(p) {
if len(p.Children) == 0 {
return
}
w.WriteString("<p>")

View file

@ -157,7 +157,7 @@ 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.Footnotes.add(name, link.Definition)
d.addFootnote(name, link.Definition)
}
return len(m[0]), link
}

View file

@ -3,6 +3,7 @@ package org
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
@ -175,10 +176,11 @@ func (w *OrgWriter) writePropertyDrawer(d PropertyDrawer) {
func (w *OrgWriter) writeFootnoteDefinition(f FootnoteDefinition) {
w.WriteString(fmt.Sprintf("[fn:%s]", f.Name))
if !(len(f.Children) >= 1 && isEmptyLineParagraph(f.Children[0])) {
content := w.nodesAsString(f.Children...)
if content != "" && !unicode.IsSpace(rune(content[0])) {
w.WriteString(" ")
}
w.writeNodes(f.Children...)
w.WriteString(content)
}
func (w *OrgWriter) writeParagraph(p Paragraph) {

View file

@ -17,11 +17,43 @@ further references to the same footnote should not <sup class="footnote-referenc
inline footnotes are also supported via <sup class="footnote-reference"><a id="footnote-reference-2" href="#footnote-2">2</a></sup>.
</p>
</li>
<li>
<p>
Footnote definitions are not printed where they appear.
Rather, they are gathered and exported at the end of the document in the footnote section. <sup class="footnote-reference"><a id="footnote-reference-4" href="#footnote-4">4</a></sup>
</p>
</li>
</ul>
<h1>
Footnotes
</h1>
<p>
Please note that the footnotes section is not automatically excluded from the export like in emacs. <sup class="footnote-reference"><a id="footnote-reference-7" href="#footnote-7">7</a></sup>
</p>
<p>
this is not part of <sup class="footnote-reference"><a id="footnote-reference-7" href="#footnote-7">7</a></sup> anymore as there are 2 blank lines in between!
</p>
<div class="footnotes">
<h1 class="footnotes-title">Footnotes</h1>
<hr class="footnotes-separatator">
<div class="footnote-definitions">
<div class="footnote-definition">
<sup id="footnote-4"><a href="#footnote-reference-4">4</a></sup>
<div class="footnote-body">
<p>
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.
</p>
</div>
</div>
<div class="footnote-definition">
<sup id="footnote-5"><a href="#footnote-reference-5">5</a></sup>
<div class="footnote-body">
<p>
another unused footnote (this definition overwrites the previous definition of <code class="verbatim">fn:5</code>)
</p>
</div>
</div>
<div class="footnote-definition">
<sup id="footnote-1"><a href="#footnote-reference-1">1</a></sup>
<div class="footnote-body">
<p>
@ -83,22 +115,6 @@ and tables
</div>
</div>
<div class="footnote-definition">
<sup id="footnote-4"><a href="#footnote-reference-4">4</a></sup>
<div class="footnote-body">
<p>
another unused footnote
</p>
</div>
</div>
<div class="footnote-definition">
<sup id="footnote-5"><a href="#footnote-reference-5">5</a></sup>
<div class="footnote-body">
<p>
another unused footnote
</p>
</div>
</div>
<div class="footnote-definition">
<sup id="footnote-6"><a href="#footnote-reference-6">6</a></sup>
<div class="footnote-body">
<p>
@ -108,6 +124,15 @@ This shouldn&#39;t happen when the definition line and the line after that are e
</div>
</div>
<div class="footnote-definition">
<sup id="footnote-7"><a href="#footnote-reference-7">7</a></sup>
<div class="footnote-body">
<p>
There&#39;s multiple reasons for that. Among others, doing so requires i18n (to recognize the section) and silently
hides content before and after the footnotes.
</p>
</div>
</div>
<div class="footnote-definition">
<sup id="footnote-2"><a href="#footnote-reference-2">2</a></sup>
<div class="footnote-body">
<p>

View file

@ -2,8 +2,17 @@
- normal footnote reference [fn:1] [fn:6]
- further references to the same footnote should not [fn:1] render duplicates in the footnote list
- inline footnotes are also supported via [fn:2:the inline footnote definition].
- Footnote definitions are not printed where they appear.
Rather, they are gathered and exported at the end of the document in the footnote section. [fn:4]
[fn: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.
[fn:5] this definition will also not be exported here - not only that, it will be overwritten by a definition
of the same name later on in the document. That will log a warning but carry on nonetheless.
* Footnotes
Please note that the footnotes section is not automatically excluded from the export like in emacs. [fn:7]
[fn:1] https://www.example.com
- footnotes can contain *markup*
- and other elements
@ -18,11 +27,18 @@
[fn:3] [[http://example.com/unused-footnote][example.com/unused-footnote]]
[fn:4] another unused footnote
[fn:5] another unused footnote
[fn:5] another unused footnote (this definition overwrites the previous definition of =fn:5=)
[fn:6]
Footnotes break after two consecutive empty lines - just like paragraphs - see https://orgmode.org/worg/dev/org-syntax.html.
This shouldn't happen when the definition line and the line after that are empty.
[fn: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.
this is not part of [fn:7] anymore as there are 2 blank lines in between!

View file

@ -2,8 +2,17 @@
- normal footnote reference [fn:1] [fn:6]
- further references to the same footnote should not [fn:1] render duplicates in the footnote list
- inline footnotes are also supported via [fn:2:the inline footnote definition].
- Footnote definitions are not printed where they appear.
Rather, they are gathered and exported at the end of the document in the footnote section. [fn:4]
[fn: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.
[fn:5] this definition will also not be exported here - not only that, it will be overwritten by a definition
of the same name later on in the document. That will log a warning but carry on nonetheless.
* Footnotes
Please note that the footnotes section is not automatically excluded from the export like in emacs. [fn:7]
[fn:1] https://www.example.com
- footnotes can contain *markup*
- and other elements
@ -18,11 +27,18 @@
[fn:3] [[http://example.com/unused-footnote][example.com/unused-footnote]]
[fn:4] another unused footnote
[fn:5] another unused footnote
[fn:5] another unused footnote (this definition overwrites the previous definition of =fn:5=)
[fn:6]
Footnotes break after two consecutive empty lines - just like paragraphs - see https://orgmode.org/worg/dev/org-syntax.html.
This shouldn't happen when the definition line and the line after that are empty.
[fn: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.
this is not part of [fn:7] anymore as there are 2 blank lines in between!

View file

@ -359,8 +359,11 @@ When inserting an image link like <img src="/home/amos/Pictures/Screenshots/img-
(ansi-term))
</pre>
</div>
<h1>
Footnotes
</h1>
<div class="footnotes">
<h1 class="footnotes-title">Footnotes</h1>
<hr class="footnotes-separatator">
<div class="footnote-definitions">
<div class="footnote-definition">
<sup id="footnote-1"><a href="#footnote-reference-1">1</a></sup>

View file

@ -11,11 +11,6 @@ func isSecondBlankLine(d *Document, i int) bool {
return false
}
func isEmptyLineParagraph(n Node) bool {
p, ok := n.(Paragraph)
return ok && len(p.Children) == 0
}
func isImageOrVideoLink(n Node) bool {
if l, ok := n.(RegularLink); ok && l.Kind() == "video" || l.Kind() == "image" {
return true