ox-md.el (29091B)
1 ;;; ox-md.el --- Markdown Backend for Org Export Engine -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2012-2024 Free Software Foundation, Inc. 4 5 ;; Author: Nicolas Goaziou <n.goaziou@gmail.com> 6 ;; Keywords: org, text, markdown 7 8 ;; This file is part of GNU Emacs. 9 10 ;; GNU Emacs is free software: you can redistribute it and/or modify 11 ;; it under the terms of the GNU General Public License as published by 12 ;; the Free Software Foundation, either version 3 of the License, or 13 ;; (at your option) any later version. 14 15 ;; GNU Emacs is distributed in the hope that it will be useful, 16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 ;; GNU General Public License for more details. 19 20 ;; You should have received a copy of the GNU General Public License 21 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. 22 23 ;;; Commentary: 24 25 ;; This library implements a Markdown backend (vanilla flavor) for 26 ;; Org exporter, based on `html' backend. See Org manual for more 27 ;; information. 28 29 ;;; Code: 30 31 (require 'org-macs) 32 (org-assert-version) 33 34 (require 'cl-lib) 35 (require 'ox-html) 36 (require 'ox-publish) 37 38 39 ;;; User-Configurable Variables 40 41 (defgroup org-export-md nil 42 "Options specific to Markdown export backend." 43 :tag "Org Markdown" 44 :group 'org-export 45 :version "24.4" 46 :package-version '(Org . "8.0")) 47 48 (defcustom org-md-headline-style 'atx 49 "Style used to format headlines. 50 This variable can be set to either `atx', `setext', or `mixed'. 51 52 Mixed style uses Setext style markup for the first two headline levels 53 and uses ATX style markup for the remaining four levels." 54 :group 'org-export-md 55 :type '(choice 56 (const :tag "Use \"atx\" style" atx) 57 (const :tag "Use \"Setext\" style" setext) 58 (const :tag "Use \"mixed\" style" mixed))) 59 60 61 ;;;; Footnotes 62 63 (defcustom org-md-footnotes-section "%s%s" 64 "Format string for the footnotes section. 65 The first %s placeholder will be replaced with the localized Footnotes section 66 heading, the second with the contents of the Footnotes section." 67 :group 'org-export-md 68 :type 'string 69 :version "26.1" 70 :package-version '(Org . "9.0")) 71 72 (defcustom org-md-footnote-format "<sup>%s</sup>" 73 "Format string for the footnote reference. 74 The %s will be replaced by the footnote reference itself." 75 :group 'org-export-md 76 :type 'string 77 :version "26.1" 78 :package-version '(Org . "9.0")) 79 80 (defcustom org-md-toplevel-hlevel 1 81 "Heading level to use for level 1 Org headings in markdown export. 82 83 If this is 1, headline levels will be preserved on export. If this is 84 2, top level Org headings will be exported to level 2 markdown 85 headings, level 2 Org headings will be exported to level 3 markdown 86 headings, and so on. 87 88 Incrementing this value may be helpful when creating markdown to be 89 included into another document or application that reserves top-level 90 headings for its own use." 91 :group 'org-export-md 92 :package-version '(Org . "9.6") 93 ;; Avoid `natnum' because that's not available until Emacs 28.1. 94 :type 'integer) 95 96 97 98 ;;; Define Backend 99 100 (org-export-define-derived-backend 'md 'html 101 :filters-alist '((:filter-parse-tree . org-md-separate-elements)) 102 :menu-entry 103 '(?m "Export to Markdown" 104 ((?M "To temporary buffer" 105 (lambda (a s v b) (org-md-export-as-markdown a s v))) 106 (?m "To file" (lambda (a s v b) (org-md-export-to-markdown a s v))) 107 (?o "To file and open" 108 (lambda (a s v b) 109 (if a (org-md-export-to-markdown t s v) 110 (org-open-file (org-md-export-to-markdown nil s v))))))) 111 :translate-alist '((bold . org-md-bold) 112 (center-block . org-md--convert-to-html) 113 (code . org-md-verbatim) 114 (drawer . org-md--identity) 115 (dynamic-block . org-md--identity) 116 (example-block . org-md-example-block) 117 (export-block . org-md-export-block) 118 (fixed-width . org-md-example-block) 119 (headline . org-md-headline) 120 (horizontal-rule . org-md-horizontal-rule) 121 (inline-src-block . org-md-verbatim) 122 (inlinetask . org-md--convert-to-html) 123 (inner-template . org-md-inner-template) 124 (italic . org-md-italic) 125 (item . org-md-item) 126 (keyword . org-md-keyword) 127 (latex-environment . org-md-latex-environment) 128 (latex-fragment . org-md-latex-fragment) 129 (line-break . org-md-line-break) 130 (link . org-md-link) 131 (node-property . org-md-node-property) 132 (paragraph . org-md-paragraph) 133 (plain-list . org-md-plain-list) 134 (plain-text . org-md-plain-text) 135 (property-drawer . org-md-property-drawer) 136 (quote-block . org-md-quote-block) 137 (section . org-md-section) 138 (special-block . org-md--convert-to-html) 139 (src-block . org-md-example-block) 140 (table . org-md--convert-to-html) 141 (template . org-md-template) 142 (verbatim . org-md-verbatim)) 143 :options-alist 144 '((:md-footnote-format nil nil org-md-footnote-format) 145 (:md-footnotes-section nil nil org-md-footnotes-section) 146 (:md-headline-style nil nil org-md-headline-style) 147 (:md-toplevel-hlevel nil nil org-md-toplevel-hlevel))) 148 149 150 ;;; Filters 151 152 (defun org-md-separate-elements (tree _backend info) 153 "Fix blank lines between elements. 154 155 TREE is the parse tree being exported. BACKEND is the export 156 backend used. INFO is a plist used as a communication channel. 157 158 Enforce a blank line between elements. There are exceptions to this 159 rule: 160 161 1. Preserve blank lines between sibling items in a plain list, 162 163 2. In an item, remove any blank line before the very first 164 paragraph and the next sub-list when the latter ends the 165 current item. 166 167 3. Do not add blank lines after table rows. (This is irrelevant for 168 md exporter, but may surprise derived backends). 169 170 Assume BACKEND is `md'." 171 (org-element-map tree 172 (remq 'table-row (remq 'item org-element-all-elements)) 173 (lambda (e) 174 (org-element-put-property 175 e :post-blank 176 (if (and (org-element-type-p e 'paragraph) 177 (org-element-type-p (org-element-parent e) 'item) 178 (org-export-first-sibling-p e info) 179 (let ((next (org-export-get-next-element e info))) 180 (and (org-element-type-p next 'plain-list) 181 (not (org-export-get-next-element next info))))) 182 0 183 1)))) 184 ;; Return updated tree. 185 tree) 186 187 188 ;;; Internal functions 189 190 (defun org-md--headline-referred-p (headline info) 191 "Non-nil when HEADLINE is being referred to. 192 INFO is a plist used as a communication channel. Links and table 193 of contents can refer to headlines." 194 (unless (org-element-property :footnote-section-p headline) 195 (or 196 ;; Global table of contents includes HEADLINE. 197 (and (plist-get info :with-toc) 198 (memq headline 199 (org-export-collect-headlines info (plist-get info :with-toc)))) 200 ;; A local table of contents includes HEADLINE. 201 (cl-some 202 (lambda (h) 203 (let ((section (car (org-element-contents h)))) 204 (and 205 (org-element-type-p section 'section) 206 (org-element-map section 'keyword 207 (lambda (keyword) 208 (when (equal "TOC" (org-element-property :key keyword)) 209 (let ((case-fold-search t) 210 (value (org-element-property :value keyword))) 211 (and (string-match-p "\\<headlines\\>" value) 212 (let ((n (and 213 (string-match "\\<[0-9]+\\>" value) 214 (string-to-number (match-string 0 value)))) 215 (local? (string-match-p "\\<local\\>" value))) 216 (memq headline 217 (org-export-collect-headlines 218 info n (and local? keyword)))))))) 219 info t)))) 220 (org-element-lineage headline)) 221 ;; A link refers internally to HEADLINE. 222 (org-element-map (plist-get info :parse-tree) 'link 223 (lambda (link) 224 (equal headline 225 ;; Ignore broken links. 226 (condition-case nil 227 (org-export-resolve-id-link link info) 228 (org-link-broken nil)))) 229 info t)))) 230 231 (defun org-md--headline-title (style level title &optional anchor tags) 232 "Generate a headline title in the preferred Markdown headline style. 233 STYLE is the preferred style (`atx' or `setext'). LEVEL is the 234 header level. TITLE is the headline title. ANCHOR is the HTML 235 anchor tag for the section as a string. TAGS are the tags set on 236 the section." 237 (let ((anchor-lines (and anchor (concat anchor "\n\n")))) 238 ;; Use "Setext" style 239 (if (and (memq style '(setext mixed)) (< level 3)) 240 (let* ((underline-char (if (= level 1) ?= ?-)) 241 (underline (concat (make-string (length title) underline-char) 242 "\n"))) 243 (concat "\n" anchor-lines title tags "\n" underline "\n")) 244 ;; Use "Atx" style 245 (let ((level-mark (make-string level ?#))) 246 (concat "\n" anchor-lines level-mark " " title tags "\n\n"))))) 247 248 (defun org-md--build-toc (info &optional n _keyword scope) 249 "Return a table of contents. 250 251 INFO is a plist used as a communication channel. 252 253 Optional argument N, when non-nil, is an integer specifying the 254 depth of the table. 255 256 When optional argument SCOPE is non-nil, build a table of 257 contents according to the specified element." 258 (concat 259 (unless scope 260 (let ((level (plist-get info :md-toplevel-hlevel)) 261 (style (plist-get info :md-headline-style)) 262 (title (org-html--translate "Table of Contents" info))) 263 (org-md--headline-title style level title nil))) 264 (mapconcat 265 (lambda (headline) 266 (let* ((indentation 267 (make-string 268 (* 4 (1- (org-export-get-relative-level headline info))) 269 ?\s)) 270 (bullet 271 (if (not (org-export-numbered-headline-p headline info)) "- " 272 (let ((prefix 273 (format "%d." (org-last (org-export-get-headline-number 274 headline info))))) 275 (concat prefix (make-string (max 1 (- 4 (length prefix))) 276 ?\s))))) 277 (title 278 (format "[%s](#%s)" 279 (org-export-data-with-backend 280 (org-export-get-alt-title headline info) 281 (org-export-toc-entry-backend 'md) 282 info) 283 (or (org-element-property :CUSTOM_ID headline) 284 (org-export-get-reference headline info)))) 285 (tags (and (plist-get info :with-tags) 286 (not (eq 'not-in-toc (plist-get info :with-tags))) 287 (org-make-tag-string 288 (org-export-get-tags headline info))))) 289 (concat indentation bullet title tags))) 290 (org-export-collect-headlines info n scope) "\n") 291 "\n")) 292 293 (defun org-md--footnote-formatted (footnote info) 294 "Formats a single footnote entry FOOTNOTE. 295 FOOTNOTE is a cons cell of the form (number . definition). 296 INFO is a plist with contextual information." 297 (let* ((fn-num (car footnote)) 298 (fn-text (cdr footnote)) 299 (fn-format (plist-get info :md-footnote-format)) 300 (fn-anchor (format "fn.%d" fn-num)) 301 (fn-href (format " href=\"#fnr.%d\"" fn-num)) 302 (fn-link-to-ref (org-html--anchor fn-anchor fn-num fn-href info))) 303 (concat (format fn-format fn-link-to-ref) " " fn-text "\n"))) 304 305 (defun org-md--footnote-section (info) 306 "Format the footnote section. 307 INFO is a plist used as a communication channel." 308 (let* ((fn-alist (org-export-collect-footnote-definitions info)) 309 (fn-alist (cl-loop for (n _type raw) in fn-alist collect 310 (cons n (org-trim (org-export-data raw info))))) 311 (headline-style (plist-get info :md-headline-style)) 312 (section-title (org-html--translate "Footnotes" info))) 313 (when fn-alist 314 (format (plist-get info :md-footnotes-section) 315 (org-md--headline-title headline-style (plist-get info :md-toplevel-hlevel) section-title) 316 (mapconcat (lambda (fn) (org-md--footnote-formatted fn info)) 317 fn-alist 318 "\n"))))) 319 320 (defun org-md--convert-to-html (datum _contents info) 321 "Convert DATUM into raw HTML. 322 CONTENTS is ignored. INFO is the info plist." 323 (org-export-data-with-backend datum 'html info)) 324 325 (defun org-md--identity (_datum contents _info) 326 "Return CONTENTS only." 327 contents) 328 329 330 ;;; Transcode Functions 331 332 ;;;; Bold 333 334 (defun org-md-bold (_bold contents _info) 335 "Transcode BOLD object into Markdown format. 336 CONTENTS is the text within bold markup. INFO is a plist used as 337 a communication channel." 338 (format "**%s**" contents)) 339 340 341 ;;;; Code and Verbatim 342 343 (defun org-md-verbatim (verbatim _contents _info) 344 "Transcode VERBATIM object into Markdown format. 345 CONTENTS is nil. INFO is a plist used as a communication 346 channel." 347 (let ((value (org-element-property :value verbatim))) 348 (format (cond ((not (string-match "`" value)) "`%s`") 349 ((or (string-prefix-p "`" value) 350 (string-suffix-p "`" value)) 351 "`` %s ``") 352 (t "``%s``")) 353 value))) 354 355 356 ;;;; Example Block, Src Block and Export Block 357 358 (defun org-md-example-block (example-block _contents info) 359 "Transcode EXAMPLE-BLOCK element into Markdown format. 360 CONTENTS is nil. INFO is a plist used as a communication 361 channel." 362 (replace-regexp-in-string 363 "^" " " 364 (org-remove-indentation 365 (org-export-format-code-default example-block info)))) 366 367 (defun org-md-export-block (export-block contents info) 368 "Transcode a EXPORT-BLOCK element from Org to Markdown. 369 CONTENTS is nil. INFO is a plist holding contextual information." 370 (if (member (org-element-property :type export-block) '("MARKDOWN" "MD")) 371 (org-remove-indentation (org-element-property :value export-block)) 372 ;; Also include HTML export blocks. 373 (org-export-with-backend 'html export-block contents info))) 374 375 376 ;;;; Headline 377 378 (defun org-md-headline (headline contents info) 379 "Transcode HEADLINE element into Markdown format. 380 CONTENTS is the headline contents. INFO is a plist used as 381 a communication channel." 382 (unless (org-element-property :footnote-section-p headline) 383 (let* ((level (+ (org-export-get-relative-level headline info) 384 (1- (plist-get info :md-toplevel-hlevel)))) 385 (title (org-export-data (org-element-property :title headline) info)) 386 (todo (and (plist-get info :with-todo-keywords) 387 (let ((todo (org-element-property :todo-keyword 388 headline))) 389 (and todo (concat (org-export-data todo info) " "))))) 390 (tags (and (plist-get info :with-tags) 391 (let ((tag-list (org-export-get-tags headline info))) 392 (and tag-list 393 (concat " " (org-make-tag-string tag-list)))))) 394 (priority 395 (and (plist-get info :with-priority) 396 (let ((char (org-element-property :priority headline))) 397 (and char (format "[#%c] " char))))) 398 ;; Headline text without tags. 399 (heading (concat todo priority title)) 400 (style (plist-get info :md-headline-style))) 401 (cond 402 ;; Cannot create a headline. Fall-back to a list. 403 ((or (org-export-low-level-p headline info) 404 (not (memq style '(atx mixed setext))) 405 (and (eq style 'atx) (> level 6)) 406 (and (eq style 'setext) (> level 2)) 407 (and (eq style 'mixed) (> level 6))) 408 (let ((bullet 409 (if (not (org-export-numbered-headline-p headline info)) "-" 410 (concat (number-to-string 411 (car (last (org-export-get-headline-number 412 headline info)))) 413 ".")))) 414 (concat bullet (make-string (- 4 (length bullet)) ?\s) heading tags "\n\n" 415 (and contents (replace-regexp-in-string "^" " " contents))))) 416 (t 417 (let ((anchor 418 (and (org-md--headline-referred-p headline info) 419 (format "<a id=\"%s\"></a>" 420 (or (org-element-property :CUSTOM_ID headline) 421 (org-export-get-reference headline info)))))) 422 (concat (org-md--headline-title style level heading anchor tags) 423 contents))))))) 424 425 ;;;; Horizontal Rule 426 427 (defun org-md-horizontal-rule (_horizontal-rule _contents _info) 428 "Transcode HORIZONTAL-RULE element into Markdown format. 429 CONTENTS is the horizontal rule contents. INFO is a plist used 430 as a communication channel." 431 "---") 432 433 434 ;;;; Italic 435 436 (defun org-md-italic (_italic contents _info) 437 "Transcode ITALIC object into Markdown format. 438 CONTENTS is the text within italic markup. INFO is a plist used 439 as a communication channel." 440 (format "*%s*" contents)) 441 442 443 ;;;; Item 444 445 (defun org-md-item (item contents info) 446 "Transcode ITEM element into Markdown format. 447 CONTENTS is the item contents. INFO is a plist used as 448 a communication channel." 449 (let* ((type (org-element-property :type (org-element-parent item))) 450 (struct (org-element-property :structure item)) 451 (bullet (if (not (eq type 'ordered)) "-" 452 (concat (number-to-string 453 (car (last (org-list-get-item-number 454 (org-element-property :begin item) 455 struct 456 (org-list-prevs-alist struct) 457 (org-list-parents-alist struct))))) 458 ".")))) 459 (concat bullet 460 (make-string (max 1 (- 4 (length bullet))) ? ) 461 (pcase (org-element-property :checkbox item) 462 (`on "[X] ") 463 (`trans "[-] ") 464 (`off "[ ] ")) 465 (let ((tag (org-element-property :tag item))) 466 (and tag (format "**%s:** "(org-export-data tag info)))) 467 (and contents 468 (org-trim (replace-regexp-in-string "^" " " contents)))))) 469 470 471 472 ;;;; Keyword 473 474 (defun org-md-keyword (keyword contents info) 475 "Transcode a KEYWORD element into Markdown format. 476 CONTENTS is nil. INFO is a plist used as a communication 477 channel." 478 (pcase (org-element-property :key keyword) 479 ((or "MARKDOWN" "MD") (org-element-property :value keyword)) 480 ("TOC" 481 (let ((case-fold-search t) 482 (value (org-element-property :value keyword))) 483 (cond 484 ((string-match-p "\\<headlines\\>" value) 485 (let ((depth (and (string-match "\\<[0-9]+\\>" value) 486 (string-to-number (match-string 0 value)))) 487 (scope 488 (cond 489 ((string-match ":target +\\(\".+?\"\\|\\S-+\\)" value) ;link 490 (org-export-resolve-link 491 (org-strip-quotes (match-string 1 value)) info)) 492 ((string-match-p "\\<local\\>" value) keyword)))) ;local 493 (org-remove-indentation 494 (org-md--build-toc info depth keyword scope))))))) 495 (_ (org-export-with-backend 'html keyword contents info)))) 496 497 498 ;;;; LaTeX Environment 499 500 (defun org-md-latex-environment (latex-environment _contents info) 501 "Transcode a LATEX-ENVIRONMENT object from Org to Markdown. 502 CONTENTS is nil. INFO is a plist holding contextual information." 503 (when (plist-get info :with-latex) 504 (let ((latex-frag (org-remove-indentation 505 (org-element-property :value latex-environment))) 506 (label (org-html--reference latex-environment info t))) 507 (if (org-string-nw-p label) 508 (replace-regexp-in-string "\\`.*" 509 (format "\\&\n\\\\label{%s}" label) 510 latex-frag) 511 latex-frag)))) 512 513 ;;;; LaTeX Fragment 514 515 (defun org-md-latex-fragment (latex-fragment _contents info) 516 "Transcode a LATEX-FRAGMENT object from Org to Markdown. 517 CONTENTS is nil. INFO is a plist holding contextual information." 518 (when (plist-get info :with-latex) 519 (let ((frag (org-element-property :value latex-fragment))) 520 (cond 521 ((string-match-p "^\\\\(" frag) 522 (concat "$" (substring frag 2 -2) "$")) 523 ((string-match-p "^\\\\\\[" frag) 524 (concat "$$" (substring frag 2 -2) "$$")) 525 (t frag))))) ; either already $-deliminated or a macro 526 527 ;;;; Line Break 528 529 (defun org-md-line-break (_line-break _contents _info) 530 "Transcode LINE-BREAK object into Markdown format. 531 CONTENTS is nil. INFO is a plist used as a communication 532 channel." 533 " \n") 534 535 536 ;;;; Link 537 538 (defun org-md-link (link desc info) 539 "Transcode LINK object into Markdown format. 540 DESC is the description part of the link, or the empty string. 541 INFO is a plist holding contextual information. See 542 `org-export-data'." 543 (let* ((link-org-files-as-md 544 (lambda (raw-path) 545 ;; Treat links to `file.org' as links to `file.md'. 546 (if (string= ".org" (downcase (file-name-extension raw-path "."))) 547 (concat (file-name-sans-extension raw-path) ".md") 548 raw-path))) 549 (type (org-element-property :type link)) 550 (raw-path (org-element-property :path link)) 551 (path (cond 552 ((string-equal type "file") 553 (org-export-file-uri (funcall link-org-files-as-md raw-path))) 554 (t (concat type ":" raw-path))))) 555 (cond 556 ;; Link type is handled by a special function. 557 ((org-export-custom-protocol-maybe link desc 'md info)) 558 ((member type '("custom-id" "id" "fuzzy")) 559 (let ((destination (if (string= type "fuzzy") 560 (org-export-resolve-fuzzy-link link info) 561 (org-export-resolve-id-link link info)))) 562 (pcase (org-element-type destination) 563 (`plain-text ; External file. 564 (let ((path (funcall link-org-files-as-md destination))) 565 (if (not desc) (format "<%s>" path) 566 (format "[%s](%s)" desc path)))) 567 (`headline 568 (format 569 "[%s](#%s)" 570 ;; Description. 571 (cond ((org-string-nw-p desc)) 572 ((org-export-numbered-headline-p destination info) 573 (mapconcat #'number-to-string 574 (org-export-get-headline-number destination info) 575 ".")) 576 (t (org-export-data (org-element-property :title destination) 577 info))) 578 ;; Reference. 579 (or (org-element-property :CUSTOM_ID destination) 580 (org-export-get-reference destination info)))) 581 (_ 582 (let ((description 583 (or (org-string-nw-p desc) 584 (let ((number (org-export-get-ordinal destination info))) 585 (cond 586 ((not number) nil) 587 ((atom number) (number-to-string number)) 588 (t (mapconcat #'number-to-string number "."))))))) 589 (when description 590 (format "[%s](#%s)" 591 description 592 (org-export-get-reference destination info)))))))) 593 ((org-export-inline-image-p link org-html-inline-image-rules) 594 (let ((path (cond ((not (string-equal type "file")) 595 (concat type ":" raw-path)) 596 ((not (file-name-absolute-p raw-path)) raw-path) 597 (t (expand-file-name raw-path)))) 598 (caption (org-export-data 599 (org-export-get-caption 600 (org-element-parent-element link)) 601 info))) 602 (format "![img](%s)" 603 (if (not (org-string-nw-p caption)) path 604 (format "%s \"%s\"" path caption))))) 605 ((string= type "coderef") 606 (format (org-export-get-coderef-format path desc) 607 (org-export-resolve-coderef path info))) 608 ((string= type "radio") 609 (let ((destination (org-export-resolve-radio-link link info))) 610 (if (not destination) desc 611 (format "<a href=\"#%s\">%s</a>" 612 (org-export-get-reference destination info) 613 desc)))) 614 (t (if (not desc) (format "<%s>" path) 615 (format "[%s](%s)" desc path)))))) 616 617 618 ;;;; Node Property 619 620 (defun org-md-node-property (node-property _contents _info) 621 "Transcode a NODE-PROPERTY element into Markdown syntax. 622 CONTENTS is nil. INFO is a plist holding contextual 623 information." 624 (format "%s:%s" 625 (org-element-property :key node-property) 626 (let ((value (org-element-property :value node-property))) 627 (if value (concat " " value) "")))) 628 629 630 ;;;; Paragraph 631 632 (defun org-md-paragraph (paragraph contents _info) 633 "Transcode PARAGRAPH element into Markdown format. 634 CONTENTS is the paragraph contents. INFO is a plist used as 635 a communication channel." 636 ;; Ensure that we do not create multiple paragraphs, when a single 637 ;; paragraph is expected. 638 ;; Multiple newlines may appear in CONTENTS, for example, when 639 ;; certain objects are stripped from export, leaving single newlines 640 ;; before and after. 641 (setq contents (org-remove-blank-lines contents)) 642 (let ((first-object (car (org-element-contents paragraph)))) 643 ;; If paragraph starts with a #, protect it. 644 (if (and (stringp first-object) (string-prefix-p "#" first-object)) 645 (concat "\\" contents) 646 contents))) 647 648 649 ;;;; Plain List 650 651 (defun org-md-plain-list (_plain-list contents _info) 652 "Transcode PLAIN-LIST element into Markdown format. 653 CONTENTS is the plain-list contents. INFO is a plist used as 654 a communication channel." 655 contents) 656 657 658 ;;;; Plain Text 659 660 (defun org-md-plain-text (text info) 661 "Transcode a TEXT string into Markdown format. 662 TEXT is the string to transcode. INFO is a plist holding 663 contextual information." 664 (when (plist-get info :with-smart-quotes) 665 (setq text (org-export-activate-smart-quotes text :html info))) 666 ;; The below series of replacements in `text' is order sensitive. 667 ;; Protect `, *, _, and \ 668 (setq text (replace-regexp-in-string "[`*_\\]" "\\\\\\&" text)) 669 ;; Protect ambiguous #. This will protect # at the beginning of 670 ;; a line, but not at the beginning of a paragraph. See 671 ;; `org-md-paragraph'. 672 (setq text (replace-regexp-in-string "\n#" "\n\\\\#" text)) 673 ;; Protect ambiguous ! 674 (setq text (replace-regexp-in-string "\\(!\\)\\[" "\\\\!" text nil nil 1)) 675 ;; Handle special strings, if required. 676 (when (plist-get info :with-special-strings) 677 (setq text (org-html-convert-special-strings text))) 678 ;; Handle break preservation, if required. 679 (when (plist-get info :preserve-breaks) 680 (setq text (replace-regexp-in-string "[ \t]*\n" " \n" text))) 681 ;; Return value. 682 text) 683 684 685 ;;;; Property Drawer 686 687 (defun org-md-property-drawer (_property-drawer contents _info) 688 "Transcode a PROPERTY-DRAWER element into Markdown format. 689 CONTENTS holds the contents of the drawer. INFO is a plist 690 holding contextual information." 691 (and (org-string-nw-p contents) 692 (replace-regexp-in-string "^" " " contents))) 693 694 695 ;;;; Quote Block 696 697 (defun org-md-quote-block (_quote-block contents _info) 698 "Transcode QUOTE-BLOCK element into Markdown format. 699 CONTENTS is the quote-block contents. INFO is a plist used as 700 a communication channel." 701 (replace-regexp-in-string 702 "^" "> " 703 (replace-regexp-in-string "\n\\'" "" contents))) 704 705 706 ;;;; Section 707 708 (defun org-md-section (_section contents _info) 709 "Transcode SECTION element into Markdown format. 710 CONTENTS is the section contents. INFO is a plist used as 711 a communication channel." 712 contents) 713 714 715 ;;;; Template 716 717 (defun org-md-inner-template (contents info) 718 "Return body of document after converting it to Markdown syntax. 719 CONTENTS is the transcoded contents string. INFO is a plist 720 holding export options." 721 ;; Make sure CONTENTS is separated from table of contents and 722 ;; footnotes with at least a blank line. 723 (concat 724 ;; Table of contents. 725 (let ((depth (plist-get info :with-toc))) 726 (when depth 727 (concat (org-md--build-toc info (and (wholenump depth) depth)) "\n"))) 728 ;; Document contents. 729 contents 730 "\n" 731 ;; Footnotes section. 732 (org-md--footnote-section info))) 733 734 (defun org-md-template (contents _info) 735 "Return complete document string after Markdown conversion. 736 CONTENTS is the transcoded contents string. INFO is a plist used 737 as a communication channel." 738 contents) 739 740 741 742 ;;; Interactive function 743 744 ;;;###autoload 745 (defun org-md-export-as-markdown (&optional async subtreep visible-only) 746 "Export current buffer to a Markdown buffer. 747 748 If narrowing is active in the current buffer, only export its 749 narrowed part. 750 751 If a region is active, export that region. 752 753 A non-nil optional argument ASYNC means the process should happen 754 asynchronously. The resulting buffer should be accessible 755 through the `org-export-stack' interface. 756 757 When optional argument SUBTREEP is non-nil, export the sub-tree 758 at point, extracting information from the headline properties 759 first. 760 761 When optional argument VISIBLE-ONLY is non-nil, don't export 762 contents of hidden elements. 763 764 Export is done in a buffer named \"*Org MD Export*\", which will 765 be displayed when `org-export-show-temporary-export-buffer' is 766 non-nil." 767 (interactive) 768 (org-export-to-buffer 'md "*Org MD Export*" 769 async subtreep visible-only nil nil (lambda () (text-mode)))) 770 771 ;;;###autoload 772 (defun org-md-convert-region-to-md () 773 "Assume the current region has Org syntax, and convert it to Markdown. 774 This can be used in any buffer. For example, you can write an 775 itemized list in Org syntax in a Markdown buffer and use 776 this command to convert it." 777 (interactive) 778 (org-export-replace-region-by 'md)) 779 780 (defalias 'org-export-region-to-md #'org-md-convert-region-to-md) 781 782 ;;;###autoload 783 (defun org-md-export-to-markdown (&optional async subtreep visible-only) 784 "Export current buffer to a Markdown file. 785 786 If narrowing is active in the current buffer, only export its 787 narrowed part. 788 789 If a region is active, export that region. 790 791 A non-nil optional argument ASYNC means the process should happen 792 asynchronously. The resulting file should be accessible through 793 the `org-export-stack' interface. 794 795 When optional argument SUBTREEP is non-nil, export the sub-tree 796 at point, extracting information from the headline properties 797 first. 798 799 When optional argument VISIBLE-ONLY is non-nil, don't export 800 contents of hidden elements. 801 802 Return output file's name." 803 (interactive) 804 (let ((outfile (org-export-output-file-name ".md" subtreep))) 805 (org-export-to-file 'md outfile async subtreep visible-only))) 806 807 ;;;###autoload 808 (defun org-md-publish-to-md (plist filename pub-dir) 809 "Publish an org file to Markdown. 810 811 FILENAME is the filename of the Org file to be published. PLIST 812 is the property list for the given project. PUB-DIR is the 813 publishing directory. 814 815 Return output file name." 816 (org-publish-org-to 'md filename ".md" plist pub-dir)) 817 818 (provide 'ox-md) 819 820 ;; Local variables: 821 ;; generated-autoload-file: "org-loaddefs.el" 822 ;; End: 823 824 ;;; ox-md.el ends here