소스 검색

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.
Nicolas Goaziou 8 년 전
부모
커밋
2370a885e7
2개의 변경된 파일110개의 추가작업 그리고 13개의 파일을 삭제
  1. 4 0
      etc/ORG-NEWS
  2. 106 13
      lisp/ox-md.el

+ 4 - 0
etc/ORG-NEWS

@@ -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

+ 106 - 13
lisp/ox-md.el

@@ -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"