From d921a68a55bb08db19358ec231dd81558665218d Mon Sep 17 00:00:00 2001
From: Niklas Fasching
Date: Wed, 26 Dec 2018 16:00:27 +0100
Subject: [PATCH] Add support for Table of Contents
---
README.org | 13 ++--
org/document.go | 9 +++
org/headline.go | 33 ++++++++++
org/html.go | 36 +++++++++--
org/testdata/footnotes.html | 14 +++-
org/testdata/headlines.html | 30 +++++++--
org/testdata/misc.html | 124 +++++++++++++++++++++++++++++-------
7 files changed, 218 insertions(+), 41 deletions(-)
diff --git a/README.org b/README.org
index 5ee31b1..069794c 100644
--- a/README.org
+++ b/README.org
@@ -4,12 +4,14 @@ Take a look at [[https://niklasfasching.github.io/go-org/][github pages]] for so
* next
- more keywords: https://orgmode.org/manual/In_002dbuffer-settings.html
- table of contents
+ - see hugo ExtractTOC
+ - loop the headlines and print an hX for each headline, ul for children
- rethink frontmatter
- - keyword customizable mapping. e.g. #+ARRAY_KWS: list kws that should become []string
- - json/yaml values.
- - +lisp syntax from [[https://github.com/kaushalmodi/ox-hugo/blob/master/ox-hugo.el#L2791][ox-hugo]]+
- - too much complexity and not native to Org mode, i.e. does not even increase compatibility
- - use toml/yaml/json frontmatter from hugo (see [[https://github.com/gohugoio/hugo/issues/5436][hugo #5436]])
+ - use toml/yaml/json frontmatter from hugo (see [[https://github.com/gohugoio/hugo/issues/5436][hugo #5436]]):
+ - complex values are a requirement of hugo, not Org mode
+ - by giving up on the ability to mix front matter in org keyword (=#+=) and other formats we save a lot of complexity (that has little benefit)
+ - Maybe allow for []string via KEYWORD[]: Tag Foo Bar - can be done in hugo
+ -> the other problem is that org mode normally allows markup in the title - but we would have to render - and to what format
** headlines
- auto-generate unique ids: see [[https://github.com/kaushalmodi/ox-hugo/blob/8472cf2d8667754c9da3728255634e8001a1da6d/ox-hugo.el#L1785-L1850][ox-hugo]]
- what about name conflicts?
@@ -41,3 +43,4 @@ Nonetheless, the html output can be compared by taking a look in the developer c
- https://code.orgmode.org/bzg/org-mode/src/master/lisp/org-element.el
- mostly those & ox-html.el, but yeah, all of [[https://code.orgmode.org/bzg/org-mode/src/master/lisp/]]
- existing Org mode implementations: [[https://github.com/emacsmirror/org][org]], [[https://github.com/bdewey/org-ruby/blob/master/spec/html_examples][org-ruby]], [[https://github.com/chaseadamsio/goorgeous/][goorgeous]], [[https://github.com/jgm/pandoc/][pandoc]]
+
diff --git a/org/document.go b/org/document.go
index 3b5e8fa..7f12479 100644
--- a/org/document.go
+++ b/org/document.go
@@ -15,6 +15,7 @@ type Document struct {
tokens []token
Nodes []Node
Footnotes Footnotes
+ Outline Outline
StatusKeywords []string
MaxEmphasisNewLines int
AutoLink bool
@@ -71,6 +72,7 @@ func FrontMatterHandler(fm FrontMatter, k, v string) error {
}
func NewDocument() *Document {
+ outlineSection := &Section{}
return &Document{
Footnotes: Footnotes{
Title: "Footnotes",
@@ -78,6 +80,7 @@ func NewDocument() *Document {
},
AutoLink: true,
MaxEmphasisNewLines: 1,
+ Outline: Outline{outlineSection, outlineSection, 0},
BufferSettings: map[string]string{},
DefaultSettings: map[string]string{
"TODO": "TODO | DONE",
@@ -232,6 +235,12 @@ func (d *Document) addFootnote(name string, definition *FootnoteDefinition) {
d.Footnotes.addOrder = append(d.Footnotes.addOrder, name)
}
+func (d *Document) addHeadline(headline *Headline) int {
+ d.Outline.last.add(&Section{Headline: headline})
+ d.Outline.count++
+ return d.Outline.count
+}
+
func tokenize(line string) token {
for _, lexFn := range lexFns {
if token, ok := lexFn(line); ok {
diff --git a/org/headline.go b/org/headline.go
index b082ac3..74ff99e 100644
--- a/org/headline.go
+++ b/org/headline.go
@@ -1,12 +1,26 @@
package org
import (
+ "fmt"
"regexp"
"strings"
"unicode"
)
+type Outline struct {
+ *Section
+ last *Section
+ count int
+}
+
+type Section struct {
+ Headline *Headline
+ Parent *Section
+ Children []*Section
+}
+
type Headline struct {
+ Index int
Lvl int
Status string
Priority string
@@ -29,6 +43,9 @@ func lexHeadline(line string) (token, bool) {
func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
t, headline := d.tokens[i], Headline{}
headline.Lvl = len(t.matches[1])
+
+ headline.Index = d.addHeadline(&headline)
+
text := t.content
todoKeywords := strings.FieldsFunc(d.Get("TODO"), func(r rune) bool { return unicode.IsSpace(r) || r == '|' })
for _, k := range todoKeywords {
@@ -64,3 +81,19 @@ func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
headline.Children = nodes
return consumed + 1, headline
}
+
+func (h Headline) ID() string {
+ if customID, ok := h.Properties.Get("CUSTOM_ID"); ok {
+ return customID
+ }
+ return fmt.Sprintf("headline-%d", h.Index)
+}
+
+func (parent *Section) add(current *Section) {
+ if parent.Headline == nil || parent.Headline.Lvl < current.Headline.Lvl {
+ parent.Children = append(parent.Children, current)
+ current.Parent = parent
+ } else {
+ parent.Parent.add(current)
+ }
+}
diff --git a/org/html.go b/org/html.go
index 4bc048e..b9fa41c 100644
--- a/org/html.go
+++ b/org/html.go
@@ -4,6 +4,7 @@ import (
"fmt"
"html"
"log"
+ "regexp"
"strings"
"unicode"
@@ -42,6 +43,8 @@ var listItemStatuses = map[string]string{
"X": "checked",
}
+var cleanHeadlineTitleForHTMLAnchorRegexp = regexp.MustCompile(`?a[^>]*>`) // nested a tags are not valid HTML
+
func NewHTMLWriter() *HTMLWriter {
return &HTMLWriter{
htmlEscape: true,
@@ -66,6 +69,7 @@ func (w *HTMLWriter) nodesAsString(nodes ...Node) string {
func (w *HTMLWriter) before(d *Document) {
w.excludeTags = strings.Fields(d.Get("EXCLUDE_TAGS"))
w.log = d.Log
+ w.writeOutline(d)
}
func (w *HTMLWriter) after(d *Document) {
@@ -201,6 +205,31 @@ func (w *HTMLWriter) writeFootnotes(d *Document) {
w.WriteString("\n\n")
}
+func (w *HTMLWriter) writeOutline(d *Document) {
+ if len(d.Outline.Children) != 0 {
+ w.WriteString("\n\n")
+ for _, section := range d.Outline.Children {
+ w.writeSection(section)
+ }
+ w.WriteString(" \n \n")
+ }
+}
+
+func (w *HTMLWriter) writeSection(section *Section) {
+ w.WriteString("\n")
+ h := section.Headline
+ title := cleanHeadlineTitleForHTMLAnchorRegexp.ReplaceAllString(w.nodesAsString(h.Title...), "")
+ w.WriteString(fmt.Sprintf("%s \n", h.ID(), title))
+ if len(section.Children) != 0 {
+ w.WriteString("\n")
+ for _, section := range section.Children {
+ w.writeSection(section)
+ }
+ w.WriteString(" \n")
+ }
+ w.WriteString(" \n")
+}
+
func (w *HTMLWriter) writeHeadline(h Headline) {
for _, excludeTag := range w.excludeTags {
for _, tag := range h.Tags {
@@ -210,12 +239,7 @@ func (w *HTMLWriter) writeHeadline(h Headline) {
}
}
- if id, ok := h.Properties.Get("CUSTOM_ID"); ok {
- w.WriteString(fmt.Sprintf(``, h.Lvl, id) + "\n")
- } else {
- w.WriteString(fmt.Sprintf("\n", h.Lvl))
- }
-
+ w.WriteString(fmt.Sprintf(``, h.Lvl, h.ID()) + "\n")
if h.Status != "" {
w.WriteString(fmt.Sprintf(`%s `, h.Status) + "\n")
}
diff --git a/org/testdata/footnotes.html b/org/testdata/footnotes.html
index 2613b79..7f86d9b 100644
--- a/org/testdata/footnotes.html
+++ b/org/testdata/footnotes.html
@@ -1,4 +1,14 @@
-
+
+
+
+
Using some footnotes
@@ -24,7 +34,7 @@ Rather, they are gathered and exported at the end of the document in the footnot
-
+
Footnotes
diff --git a/org/testdata/headlines.html b/org/testdata/headlines.html
index 814a27e..88d9672 100644
--- a/org/testdata/headlines.html
+++ b/org/testdata/headlines.html
@@ -1,4 +1,26 @@
-
just a space as title…
-
+
DONE
#84 : Paragraphs that are not followed by an empty line are not parsed correctly
-
+
Foo
Foo paragraph.
-
+
Bar
Bar paragraph
-
+
DONE
#86 : Multiple hyphens not converted to dashes
@@ -327,17 +403,17 @@ src/example/export blocks should not be converted!
-
+
DONE
#87 : Markup in footnotes is rendered literally
footnotes can contain markup - and other elements and stuff
-
+
issues (wrongly) filed with hugo
-
+
#3874 exporting images in org mode
@@ -346,7 +422,7 @@ Hello, I'm writing hugo blogs using org-mode.
When inserting an image link like , hugo doesn't export the image.
-
+
#4006 source code blocks in org not rendered correctly
@@ -359,7 +435,7 @@ When inserting an image link like
Footnotes