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(`]*>`) // 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") + } +} + +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") + } + 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 @@ -

    + +

    Simple Headline [1/2]

    -

    +

    DONE #33: Wrong output when mixing html with Org mode

    @@ -133,7 +209,7 @@ deploy: -

    +

    DONE #46: Support for symbols like ndash and mdash

    @@ -164,7 +240,7 @@ note that —— is replaced with 2 mdashes and …. becomes ellipsis+. and so o

    -

    +

    DONE #47: Consecutive code wrapped text gets joined

    @@ -173,7 +249,7 @@ either this or that foo. either this or that foo.

    -

    +

    DONE #50: LineBreaks in lists are preserved

    @@ -208,7 +284,7 @@ foo

    -

    +

    DONE #68: Quote block with inline markup

    @@ -217,12 +293,12 @@ foo this is markup!

    -

    +

    DONE #72: Support for #+ATTR_HTML

    Go is fine though. -

    +

    DONE #75: Not parsing nested lists correctly

    @@ -240,11 +316,11 @@ sub bullet -

    +

    DONE #77: Recognize code— as code plus dash

    -

    +

    DONE #78: Emphasis at beginning of line

    @@ -255,33 +331,33 @@ sub bullet Text italics

    -

    +

    DONE #82: Crash on empty headline

    -

    +

    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 2

    -

    +

    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 /home/amos/Pictures/Screenshots/img-2017-09-11-165647.png, 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