ox-md: Implement native table of contents

* lisp/ox-md.el (org-md--headline-referred-p): Try hard to limit
  anchors for headlines really referred to, either globally, or
  locally, in a table of contents.
(org-md-keyword): Add support for "TOC" keyword.
(org-md--build-toc): New function.
(org-md-inner-template): Use new function.
This commit is contained in:
Nicolas Goaziou 2017-03-11 15:21:27 +01:00
parent 2d1f73ebf8
commit 2370a885e7
2 changed files with 110 additions and 13 deletions

View File

@ -165,6 +165,10 @@ Where clue > 0
Added a nullary function that returns a list of files as a possible
argument for the scope of the clock table.
*** Export
**** Implement vernacular table of contents in Markdown exporter
Global table of contents are generated using vanilla Markdown syntax
instead of HTML. Also #+TOC keyword, including local table of
contents, are now supported.
**** Add Slovanian translations
**** Implement ~org-export-insert-image-links~
This new function is meant to be used in back-ends supporting images

View File

@ -248,15 +248,42 @@ a communication channel."
"Non-nil when HEADLINE is being referred to.
INFO is a plist used as a communication channel. Links and table
of contents can refer to headlines."
(or (plist-get info :with-toc)
(org-element-map (plist-get info :parse-tree) 'link
(lambda (link)
(eq headline
(pcase (org-element-property :type link)
((or "custom-id" "id") (org-export-resolve-id-link link info))
("fuzzy" (org-export-resolve-fuzzy-link link info))
(_ nil))))
info t)))
(unless (org-element-property :footnote-section-p headline)
(or
;; Global table of contents includes HEADLINE.
(and (plist-get info :with-toc)
(memq headline
(org-export-collect-headlines info (plist-get info :with-toc))))
;; A local table of contents includes HEADLINE.
(cl-some
(lambda (h)
(let ((section (car (org-element-contents h))))
(and
(eq 'section (org-element-type section))
(org-element-map section 'keyword
(lambda (keyword)
(when (equal "TOC" (org-element-property :key keyword))
(let ((case-fold-search t)
(value (org-element-property :value keyword)))
(and (string-match-p "\\<headlines\\>" value)
(let ((n (and
(string-match "\\<[0-9]+\\>" value)
(string-to-number (match-string 0 value))))
(local? (string-match-p "\\<local\\>" value)))
(memq headline
(org-export-collect-headlines
info n (and local? keyword))))))))
info t))))
(org-element-lineage headline))
;; A link refers internally to HEADLINE.
(org-element-map (plist-get info :parse-tree) 'link
(lambda (link)
(eq headline
(pcase (org-element-property :type link)
((or "custom-id" "id") (org-export-resolve-id-link link info))
("fuzzy" (org-export-resolve-fuzzy-link link info))
(_ nil))))
info t))))
(defun org-md--headline-title (style level title &optional anchor tags)
"Generate a headline title in the preferred Markdown headline style.
@ -328,9 +355,19 @@ a communication channel."
"Transcode a KEYWORD element into Markdown format.
CONTENTS is nil. INFO is a plist used as a communication
channel."
(if (member (org-element-property :key keyword) '("MARKDOWN" "MD"))
(org-element-property :value keyword)
(org-export-with-backend 'html keyword contents info)))
(pcase (org-element-property :key keyword)
((or "MARKDOWN" "MD") (org-element-property :value keyword))
("TOC"
(let ((case-fold-search t)
(value (org-element-property :value keyword)))
(cond
((string-match-p "\\<headlines\\>" value)
(let ((depth (and (string-match "\\<[0-9]+\\>" value)
(string-to-number (match-string 0 value))))
(local? (string-match-p "\\<local\\>" value)))
(org-remove-indentation
(org-md--build-toc info depth keyword local?)))))))
(_ (org-export-with-backend 'html keyword contents info))))
;;;; Line Break
@ -512,6 +549,61 @@ a communication channel."
;;;; Template
(defun org-md--build-toc (info &optional n keyword local)
"Return a table of contents.
INFO is a plist used as a communication channel.
Optional argument N, when non-nil, is an integer specifying the
depth of the table.
Optional argument KEYWORD specifies the TOC keyword, if any, from
which the table of contents generation has been initiated.
When optional argument LOCAL is non-nil, build a table of
contents according to the current headline."
(concat
(unless local
(let ((style (plist-get info :md-headline-style))
(title (org-html--translate "Table of Contents" info)))
(org-md--headline-title style 1 title nil)))
(mapconcat
(lambda (headline)
(let* ((indentation
(make-string
(* 4 (1- (org-export-get-relative-level headline info)))
?\s))
(number (format "%d."
(org-last
(org-export-get-headline-number headline info))))
(bullet (concat number (make-string (- 4 (length number)) ?\s)))
(title
(format "[%s](#%s)"
(org-export-data-with-backend
(org-export-get-alt-title headline info)
;; Create an anonymous back-end that will
;; ignore any footnote-reference, link,
;; radio-target and target in table of
;; contents.
(org-export-create-backend
:parent 'md
:transcoders '((footnote-reference . ignore)
(link . (lambda (object c i) c))
(radio-target . (lambda (object c i) c))
(target . ignore)))
info)
(or (org-element-property :CUSTOM_ID headline)
(org-export-get-reference headline info))))
(tags (and (plist-get info :with-tags)
(not (eq 'not-in-toc (plist-get info :with-tags)))
(let ((tags (org-export-get-tags headline info)))
(and tags
(format ":%s:"
(mapconcat #'identity tags ":")))))))
(concat indentation bullet title tags)))
(org-export-collect-headlines info n (and local keyword)) "\n")
"\n"))
(defun org-md--footnote-formatted (footnote info)
"Formats a single footnote entry FOOTNOTE.
FOOTNOTE is a cons cell of the form (number . definition).
@ -548,7 +640,8 @@ holding export options."
(concat
;; Table of contents.
(let ((depth (plist-get info :with-toc)))
(when depth (org-html-toc depth info)))
(when depth
(concat (org-md--build-toc info (and (wholenump depth) depth)) "\n")))
;; Document contents.
contents
"\n"