dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

ox-texinfo.el (76741B)


      1 ;;; ox-texinfo.el --- Texinfo Back-End for Org Export Engine -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2012-2023 Free Software Foundation, Inc.
      4 ;; Author: Jonathan Leech-Pepin <jonathan.leechpepin at gmail dot com>
      5 ;; Maintainer: Nicolas Goaziou <mail@nicolasgoaziou.fr>
      6 ;; Keywords: outlines, hypermedia, calendar, wp
      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 ;; See Org manual for details.
     26 
     27 ;;; Code:
     28 
     29 (require 'org-macs)
     30 (org-assert-version)
     31 
     32 (require 'cl-lib)
     33 (require 'ox)
     34 
     35 (defvar orgtbl-exp-regexp)
     36 (defvar org-texinfo-supports-math--cache)
     37 
     38 
     39 ;;; Define Back-End
     40 
     41 (org-export-define-backend 'texinfo
     42   '((bold . org-texinfo-bold)
     43     (center-block . org-texinfo-center-block)
     44     (clock . org-texinfo-clock)
     45     (code . org-texinfo-code)
     46     (drawer . org-texinfo-drawer)
     47     (dynamic-block . org-texinfo-dynamic-block)
     48     (entity . org-texinfo-entity)
     49     (example-block . org-texinfo-example-block)
     50     (export-block . org-texinfo-export-block)
     51     (export-snippet . org-texinfo-export-snippet)
     52     (fixed-width . org-texinfo-fixed-width)
     53     (footnote-definition . org-texinfo-footnote-definition)
     54     (footnote-reference . org-texinfo-footnote-reference)
     55     (headline . org-texinfo-headline)
     56     (inline-src-block . org-texinfo-inline-src-block)
     57     (inlinetask . org-texinfo-inlinetask)
     58     (italic . org-texinfo-italic)
     59     (item . org-texinfo-item)
     60     (keyword . org-texinfo-keyword)
     61     (latex-environment . org-texinfo-latex-environment)
     62     (latex-fragment . org-texinfo-latex-fragment)
     63     (line-break . org-texinfo-line-break)
     64     (link . org-texinfo-link)
     65     (node-property . org-texinfo-node-property)
     66     (paragraph . org-texinfo-paragraph)
     67     (plain-list . org-texinfo-plain-list)
     68     (plain-text . org-texinfo-plain-text)
     69     (planning . org-texinfo-planning)
     70     (property-drawer . org-texinfo-property-drawer)
     71     (quote-block . org-texinfo-quote-block)
     72     (radio-target . org-texinfo-radio-target)
     73     (section . org-texinfo-section)
     74     (special-block . org-texinfo-special-block)
     75     (src-block . org-texinfo-src-block)
     76     (statistics-cookie . org-texinfo-statistics-cookie)
     77     (strike-through . org-texinfo-strike-through)
     78     (subscript . org-texinfo-subscript)
     79     (superscript . org-texinfo-superscript)
     80     (table . org-texinfo-table)
     81     (table-cell . org-texinfo-table-cell)
     82     (table-row . org-texinfo-table-row)
     83     (target . org-texinfo-target)
     84     (template . org-texinfo-template)
     85     (timestamp . org-texinfo-timestamp)
     86     (underline . org-texinfo-underline)
     87     (verbatim . org-texinfo-verbatim)
     88     (verse-block . org-texinfo-verse-block))
     89   :filters-alist
     90   '((:filter-headline . org-texinfo--filter-section-blank-lines)
     91     (:filter-parse-tree . (org-texinfo--normalize-headlines
     92 			   org-texinfo--separate-definitions))
     93     (:filter-section . org-texinfo--filter-section-blank-lines)
     94     (:filter-final-output . org-texinfo--untabify))
     95   :menu-entry
     96   '(?i "Export to Texinfo"
     97        ((?t "As TEXI file" org-texinfo-export-to-texinfo)
     98 	(?i "As INFO file" org-texinfo-export-to-info)
     99 	(?o "As INFO file and open"
    100 	    (lambda (a s v b)
    101 	      (if a (org-texinfo-export-to-info t s v b)
    102 		(org-open-file (org-texinfo-export-to-info nil s v b)))))))
    103   :options-alist
    104   '((:texinfo-filename "TEXINFO_FILENAME" nil nil t)
    105     (:texinfo-class "TEXINFO_CLASS" nil org-texinfo-default-class t)
    106     (:texinfo-header "TEXINFO_HEADER" nil nil newline)
    107     (:texinfo-post-header "TEXINFO_POST_HEADER" nil nil newline)
    108     (:subtitle "SUBTITLE" nil nil parse)
    109     (:subauthor "SUBAUTHOR" nil nil newline)
    110     (:texinfo-dircat "TEXINFO_DIR_CATEGORY" nil nil t)
    111     (:texinfo-dirtitle "TEXINFO_DIR_TITLE" nil nil t)
    112     (:texinfo-dirdesc "TEXINFO_DIR_DESC" nil nil t)
    113     (:texinfo-printed-title "TEXINFO_PRINTED_TITLE" nil nil t)
    114     ;; Other variables.
    115     (:texinfo-classes nil nil org-texinfo-classes)
    116     (:texinfo-format-headline-function nil nil org-texinfo-format-headline-function)
    117     (:texinfo-node-description-column nil nil org-texinfo-node-description-column)
    118     (:texinfo-active-timestamp-format nil nil org-texinfo-active-timestamp-format)
    119     (:texinfo-inactive-timestamp-format nil nil org-texinfo-inactive-timestamp-format)
    120     (:texinfo-diary-timestamp-format nil nil org-texinfo-diary-timestamp-format)
    121     (:texinfo-link-with-unknown-path-format nil nil org-texinfo-link-with-unknown-path-format)
    122     (:texinfo-tables-verbatim nil nil org-texinfo-tables-verbatim)
    123     (:texinfo-table-scientific-notation nil nil org-texinfo-table-scientific-notation)
    124     (:texinfo-table-default-markup nil nil org-texinfo-table-default-markup)
    125     (:texinfo-text-markup-alist nil nil org-texinfo-text-markup-alist)
    126     (:texinfo-format-drawer-function nil nil org-texinfo-format-drawer-function)
    127     (:texinfo-format-inlinetask-function nil nil org-texinfo-format-inlinetask-function)
    128     (:texinfo-compact-itemx nil "compact-itemx" org-texinfo-compact-itemx)
    129     ;; Redefine regular options.
    130     (:with-latex nil "tex" org-texinfo-with-latex)))
    131 
    132 
    133 ;;; User Configurable Variables
    134 
    135 (defgroup org-export-texinfo nil
    136   "Options for exporting Org mode files to Texinfo."
    137   :tag "Org Export Texinfo"
    138   :version "24.4"
    139   :package-version '(Org . "8.0")
    140   :group 'org-export)
    141 
    142 ;;;; Preamble
    143 
    144 (defcustom org-texinfo-coding-system nil
    145   "Default document encoding for Texinfo output.
    146 
    147 If nil it will default to `buffer-file-coding-system'."
    148   :group 'org-export-texinfo
    149   :type 'coding-system)
    150 
    151 (defcustom org-texinfo-default-class "info"
    152   "The default Texinfo class."
    153   :group 'org-export-texinfo
    154   :type '(string :tag "Texinfo class"))
    155 
    156 (defcustom org-texinfo-classes
    157   '(("info"
    158      "@documentencoding AUTO\n@documentlanguage AUTO"
    159      ("@chapter %s" "@unnumbered %s" "@chapheading %s" "@appendix %s")
    160      ("@section %s" "@unnumberedsec %s" "@heading %s" "@appendixsec %s")
    161      ("@subsection %s" "@unnumberedsubsec %s" "@subheading %s"
    162       "@appendixsubsec %s")
    163      ("@subsubsection %s" "@unnumberedsubsubsec %s" "@subsubheading %s"
    164       "@appendixsubsubsec %s")))
    165   "Alist of Texinfo classes and associated header and structure.
    166 If #+TEXINFO_CLASS is set in the buffer, use its value and the
    167 associated information.  Here is the structure of a class
    168 definition:
    169 
    170   (class-name
    171     header-string
    172     (numbered-1 unnumbered-1 unnumbered-no-toc-1 appendix-1)
    173     (numbered-2 unnumbered-2 unnumbered-no-toc-2 appendix-2)
    174     ...)
    175 
    176 
    177 The header string
    178 -----------------
    179 
    180 The header string is inserted in the header of the generated
    181 document, right after \"@setfilename\" and \"@settitle\"
    182 commands.
    183 
    184 If it contains the special string
    185 
    186   \"@documentencoding AUTO\"
    187 
    188 \"AUTO\" will be replaced with an appropriate coding system.  See
    189 `org-texinfo-coding-system' for more information.  Likewise, if
    190 the string contains the special string
    191 
    192   \"@documentlanguage AUTO\"
    193 
    194 \"AUTO\" will be replaced with the language defined in the
    195 buffer, through #+LANGUAGE keyword, or globally, with
    196 `org-export-default-language', which see.
    197 
    198 
    199 The sectioning structure
    200 ------------------------
    201 
    202 The sectioning structure of the class is given by the elements
    203 following the header string.  For each sectioning level, a number
    204 of strings is specified.  A %s formatter is mandatory in each
    205 section string and will be replaced by the title of the section."
    206   :group 'org-export-texinfo
    207   :version "27.1"
    208   :package-version '(Org . "9.2")
    209   :type '(repeat
    210 	  (list (string :tag "Texinfo class")
    211 		(string :tag "Texinfo header")
    212 		(repeat :tag "Levels" :inline t
    213 			(choice
    214 			 (list :tag "Heading"
    215 			       (string :tag "         numbered")
    216 			       (string :tag "       unnumbered")
    217 			       (string :tag "unnumbered-no-toc")
    218 			       (string :tag "         appendix")))))))
    219 
    220 ;;;; Headline
    221 
    222 (defcustom org-texinfo-format-headline-function
    223   'org-texinfo-format-headline-default-function
    224   "Function to format headline text.
    225 
    226 This function will be called with 5 arguments:
    227 TODO      the todo keyword (string or nil).
    228 TODO-TYPE the type of todo (symbol: `todo', `done', nil)
    229 PRIORITY  the priority of the headline (integer or nil)
    230 TEXT      the main headline text (string).
    231 TAGS      the tags as a list of strings (list of strings or nil).
    232 
    233 The function result will be used in the section format string."
    234   :group 'org-export-texinfo
    235   :type 'function
    236   :version "26.1"
    237   :package-version '(Org . "8.3"))
    238 
    239 ;;;; Node listing (menu)
    240 
    241 (defcustom org-texinfo-node-description-column 32
    242   "Column at which to start the description in the node listings.
    243 If a node title is greater than this length, the description will
    244 be placed after the end of the title."
    245   :group 'org-export-texinfo
    246   :type 'integer)
    247 
    248 ;;;; Timestamps
    249 
    250 (defcustom org-texinfo-active-timestamp-format "@emph{%s}"
    251   "A printf format string to be applied to active timestamps."
    252   :group 'org-export-texinfo
    253   :type 'string)
    254 
    255 (defcustom org-texinfo-inactive-timestamp-format "@emph{%s}"
    256   "A printf format string to be applied to inactive timestamps."
    257   :group 'org-export-texinfo
    258   :type 'string)
    259 
    260 (defcustom org-texinfo-diary-timestamp-format "@emph{%s}"
    261   "A printf format string to be applied to diary timestamps."
    262   :group 'org-export-texinfo
    263   :type 'string)
    264 
    265 ;;;; Links
    266 
    267 (defcustom org-texinfo-link-with-unknown-path-format "@indicateurl{%s}"
    268   "Format string for links with unknown path type."
    269   :group 'org-export-texinfo
    270   :type 'string)
    271 
    272 ;;;; Tables
    273 
    274 (defcustom org-texinfo-tables-verbatim nil
    275   "When non-nil, tables are exported verbatim."
    276   :group 'org-export-texinfo
    277   :type 'boolean)
    278 
    279 (defcustom org-texinfo-table-scientific-notation nil
    280   "Format string to display numbers in scientific notation.
    281 
    282 The format should have \"%s\" twice, for mantissa and exponent
    283 \(i.e. \"%s\\\\times10^{%s}\").
    284 
    285 When nil, no transformation is made."
    286   :group 'org-export-texinfo
    287   :type '(choice
    288 	  (string :tag "Format string")
    289 	  (const :tag "No formatting" nil)))
    290 
    291 (defcustom org-texinfo-table-default-markup "@asis"
    292   "Default markup for first column in two-column tables.
    293 
    294 This should an indicating command, e.g., \"@code\", \"@kbd\" or
    295 \"@samp\".
    296 
    297 It can be overridden locally using the \":indic\" attribute."
    298   :group 'org-export-texinfo
    299   :type 'string
    300   :version "26.1"
    301   :package-version '(Org . "9.1")
    302   :safe #'stringp)
    303 
    304 ;;;; Text markup
    305 
    306 (defcustom org-texinfo-text-markup-alist '((bold . "@strong{%s}")
    307 					   (code . code)
    308 					   (italic . "@emph{%s}")
    309 					   (verbatim . samp))
    310   "Alist of Texinfo expressions to convert text markup.
    311 
    312 The key must be a symbol among `bold', `code', `italic',
    313 `strike-through', `underscore' and `verbatim'.  The value is
    314 a formatting string to wrap fontified text with.
    315 
    316 Value can also be set to the following symbols: `verb', `samp'
    317 and `code'.  With the first one, Org uses \"@verb\" to create
    318 a format string and selects a delimiter character that isn't in
    319 the string.  For the other two, Org uses \"@samp\" or \"@code\"
    320 to typeset and protects special characters.
    321 
    322 When no association is found for a given markup, text is returned
    323 as-is."
    324   :group 'org-export-texinfo
    325   :version "26.1"
    326   :package-version '(Org . "9.1")
    327   :type 'alist
    328   :options '(bold code italic strike-through underscore verbatim))
    329 
    330 ;;;; Drawers
    331 
    332 (defcustom org-texinfo-format-drawer-function (lambda (_name contents) contents)
    333   "Function called to format a drawer in Texinfo code.
    334 
    335 The function must accept two parameters:
    336   NAME      the drawer name, like \"LOGBOOK\"
    337   CONTENTS  the contents of the drawer.
    338 
    339 The function should return the string to be exported.
    340 
    341 The default function simply returns the value of CONTENTS."
    342   :group 'org-export-texinfo
    343   :version "24.4"
    344   :package-version '(Org . "8.2")
    345   :type 'function)
    346 
    347 ;;;; Inlinetasks
    348 
    349 (defcustom org-texinfo-format-inlinetask-function
    350   'org-texinfo-format-inlinetask-default-function
    351   "Function called to format an inlinetask in Texinfo code.
    352 
    353 The function must accept six parameters:
    354   TODO      the todo keyword, as a string
    355   TODO-TYPE the todo type, a symbol among `todo', `done' and nil.
    356   PRIORITY  the inlinetask priority, as a string
    357   NAME      the inlinetask name, as a string.
    358   TAGS      the inlinetask tags, as a list of strings.
    359   CONTENTS  the contents of the inlinetask, as a string.
    360 
    361 The function should return the string to be exported."
    362   :group 'org-export-texinfo
    363   :type 'function)
    364 
    365 ;;;; LaTeX
    366 
    367 (defcustom org-texinfo-with-latex (and org-export-with-latex 'detect)
    368   "When non-nil, the Texinfo exporter attempts to process LaTeX math.
    369 
    370 When set to t, the exporter will process LaTeX environments and
    371 fragments as Texinfo \"@displaymath\" and \"@math\" commands
    372 respectively.  Alternatively, when set to `detect', the exporter
    373 does so only if the installed version of Texinfo supports the
    374 necessary commands."
    375   :group 'org-export-texinfo
    376   :package-version '(Org . "9.6")
    377   :type '(choice
    378           (const :tag "Detect" detect)
    379           (const :tag "Yes" t)
    380           (const :tag "No" nil)))
    381 
    382 ;;;; Itemx
    383 
    384 (defcustom org-texinfo-compact-itemx nil
    385   "Non-nil means certain items in description list become `@itemx'.
    386 
    387 If this is non-nil and an item in a description list has no
    388 body but is followed by another item, then the second item is
    389 transcoded to `@itemx'.  See info node `(org)Plain lists in
    390 Texinfo export' for how to enable this for individual lists."
    391   :package-version '(Org . "9.6")
    392   :group 'org-export-texinfo
    393   :type 'boolean
    394   :safe t)
    395 
    396 ;;;; Compilation
    397 
    398 (defcustom org-texinfo-info-process '("makeinfo --no-split %f")
    399   "Commands to process a Texinfo file to an INFO file.
    400 
    401 This is a list of strings, each of them will be given to the
    402 shell as a command.  %f in the command will be replaced by the
    403 relative file name, %F by the absolute file name, %b by the file
    404 base name (i.e. without directory and extension parts), %o by the
    405 base directory of the file and %O by the absolute file name of
    406 the output file."
    407   :group 'org-export-texinfo
    408   :version "26.1"
    409   :package-version '(Org . "9.1")
    410   :type '(repeat :tag "Shell command sequence"
    411 		 (string :tag "Shell command")))
    412 
    413 (defcustom org-texinfo-logfiles-extensions
    414   '("aux" "toc" "cp" "fn" "ky" "pg" "tp" "vr")
    415   "The list of file extensions to consider as Texinfo logfiles.
    416 The logfiles will be remove if `org-texinfo-remove-logfiles' is
    417 non-nil."
    418   :group 'org-export-texinfo
    419   :type '(repeat (string :tag "Extension")))
    420 
    421 (defcustom org-texinfo-remove-logfiles t
    422   "Non-nil means remove the logfiles produced by compiling a Texinfo file.
    423 By default, logfiles are files with these extensions: .aux, .toc,
    424 .cp, .fn, .ky, .pg and .tp.  To define the set of logfiles to remove,
    425 set `org-texinfo-logfiles-extensions'."
    426   :group 'org-export-latex
    427   :type 'boolean)
    428 
    429 ;;; Constants
    430 
    431 (defconst org-texinfo-max-toc-depth 4
    432   "Maximum depth for creation of detailed menu listings.
    433 Beyond this depth, Texinfo will not recognize the nodes and will
    434 cause errors.  Left as a constant in case this value ever
    435 changes.")
    436 
    437 (defconst org-texinfo-supported-coding-systems
    438   '("US-ASCII" "UTF-8" "ISO-8859-15" "ISO-8859-1" "ISO-8859-2" "koi8-r" "koi8-u")
    439   "List of coding systems supported by Texinfo, as strings.
    440 Specified coding system will be matched against these strings.
    441 If two strings share the same prefix (e.g. \"ISO-8859-1\" and
    442 \"ISO-8859-15\"), the most specific one has to be listed first.")
    443 
    444 (defconst org-texinfo-inline-image-rules
    445   (list (cons "file"
    446 	      (regexp-opt '("eps" "pdf" "png" "jpg" "jpeg" "gif" "svg"))))
    447   "Rules characterizing image files that can be inlined.")
    448 
    449 (defvar org-texinfo--quoted-keys-regexp
    450   (regexp-opt '("BS" "TAB" "RET" "ESC" "SPC" "DEL"
    451 		"LFD" "DELETE" "SHIFT" "Ctrl" "Meta" "Alt"
    452 		"Cmd" "Super" "UP" "LEFT" "RIGHT" "DOWN")
    453 	      'words)
    454   "Regexp matching keys that have to be quoted using @key{KEY}.")
    455 
    456 (defconst org-texinfo--definition-command-alist
    457   '(("deffn Command" . "Command")
    458     ("defun" . "Function")
    459     ("defmac" . "Macro")
    460     ("defspec" . "Special Form")
    461     ("defvar" . "Variable")
    462     ("defopt" . "User Option")
    463     (nil . "Key"))
    464   "Alist mapping Texinfo definition commands to output in Info files.")
    465 
    466 (defconst org-texinfo--definition-command-regexp
    467   (format "\\`%s: \\(.+\\)"
    468 	  (regexp-opt
    469 	   (delq nil (mapcar #'cdr org-texinfo--definition-command-alist))
    470 	   t))
    471   "Regexp used to match definition commands in descriptive lists.")
    472 
    473 
    474 ;;; Internal Functions
    475 
    476 (defun org-texinfo--untabify (s _backend _info)
    477   "Remove TAB characters in string S."
    478   (replace-regexp-in-string "\t" (make-string tab-width ?\s) s))
    479 
    480 (defun org-texinfo--filter-section-blank-lines (headline _backend _info)
    481   "Filter controlling number of blank lines after a section."
    482   (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" "\n\n" headline))
    483 
    484 (defun org-texinfo--normalize-headlines (tree _backend info)
    485   "Normalize headlines in TREE.
    486 
    487 BACK-END is the symbol specifying back-end used for export.
    488 INFO is a plist used as a communication channel.
    489 
    490 Make sure every headline in TREE contains a section, since those
    491 are required to install a menu.  Also put exactly one blank line
    492 at the end of each section.
    493 
    494 Return new tree."
    495   (org-element-map tree 'headline
    496     (lambda (hl)
    497       (org-element-put-property hl :post-blank 1)
    498       (let ((contents (org-element-contents hl)))
    499 	(when contents
    500 	  (let ((first (org-element-map contents '(headline section)
    501 			 #'identity info t)))
    502 	    (unless (eq (org-element-type first) 'section)
    503 	      (apply #'org-element-set-contents
    504 		     hl
    505 		     (cons `(section (:parent ,hl)) contents)))))))
    506     info)
    507   tree)
    508 
    509 (defun org-texinfo--find-verb-separator (s)
    510   "Return a character not used in string S.
    511 This is used to choose a separator for constructs like \\verb."
    512   (let ((ll "~,./?;':\"|!@#%^&-_=+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>()[]{}"))
    513     (cl-loop for c across ll
    514 	     when (not (string-match (regexp-quote (char-to-string c)) s))
    515 	     return (char-to-string c))))
    516 
    517 (defun org-texinfo--text-markup (text markup _info)
    518   "Format TEXT depending on MARKUP text markup.
    519 INFO is a plist used as a communication channel.  See
    520 `org-texinfo-text-markup-alist' for details."
    521   (pcase (cdr (assq markup org-texinfo-text-markup-alist))
    522     (`nil text)				;no markup: return raw text
    523     (`code (format "@code{%s}" (org-texinfo--sanitize-content text)))
    524     (`samp (format "@samp{%s}" (org-texinfo--sanitize-content text)))
    525     (`verb
    526      (let ((separator (org-texinfo--find-verb-separator text)))
    527        (format "@verb{%s%s%s}" separator text separator)))
    528     ;; Else use format string.
    529     (fmt (format fmt text))))
    530 
    531 (defun org-texinfo--get-node (datum info)
    532   "Return node or anchor associated to DATUM.
    533 DATUM is a headline, a radio-target or a target.  INFO is a plist
    534 used as a communication channel.  The function guarantees the
    535 node or anchor name is unique."
    536   (let ((cache (plist-get info :texinfo-node-cache)))
    537     (or (cdr (assq datum cache))
    538 	(let* ((salt 0)
    539 	       (basename
    540 		(org-texinfo--sanitize-node
    541 		 (pcase (org-element-type datum)
    542 		   (`headline
    543 		    (org-texinfo--sanitize-title
    544 		     (org-export-get-alt-title datum info) info))
    545 		   (`radio-target
    546 		    (org-export-data (org-element-contents datum) info))
    547 		   (`target
    548 		    (org-element-property :value datum))
    549 		   (_
    550 		    (or (org-element-property :name datum)
    551 			(org-export-get-reference datum info))))))
    552 	       (name basename))
    553 	  ;; Org exports deeper elements before their parents.  If two
    554 	  ;; node names collide -- e.g., they have the same title --
    555 	  ;; within the same hierarchy, the second one would get the
    556 	  ;; smaller node name.  This is counter-intuitive.
    557 	  ;; Consequently, we ensure that every parent headline gets
    558 	  ;; its node beforehand.  As a recursive operation, this
    559 	  ;; achieves the desired effect.
    560 	  (let ((parent (org-element-lineage datum '(headline))))
    561 	    (when (and parent (not (assq parent cache)))
    562 	      (org-texinfo--get-node parent info)
    563 	      (setq cache (plist-get info :texinfo-node-cache))))
    564 	  ;; Ensure NAME is unique and not reserved node name "Top",
    565           ;; no matter what case is used.
    566 	  (while (or (string-equal "Top" (capitalize name))
    567                      (rassoc name cache))
    568 	    (setq name (concat basename (format " (%d)" (cl-incf salt)))))
    569 	  (plist-put info :texinfo-node-cache (cons (cons datum name) cache))
    570 	  name))))
    571 
    572 (defun org-texinfo--sanitize-node (title)
    573   "Bend string TITLE to node line requirements.
    574 Trim string and collapse multiple whitespace characters as they
    575 are not significant.  Replace leading left parenthesis, when
    576 followed by a right parenthesis, with a square bracket.  Remove
    577 periods, commas and colons."
    578   (org-trim
    579    (replace-regexp-in-string
    580     "[ \t]+" " "
    581     (replace-regexp-in-string
    582      "[:,.]" ""
    583      (replace-regexp-in-string "\\`(\\(.*?)\\)" "[\\1" title)))))
    584 
    585 (defun org-texinfo--sanitize-title (title info)
    586   "Make TITLE suitable as a section name.
    587 TITLE is a string or a secondary string.  INFO is the current
    588 export state, as a plist."
    589   (org-export-data-with-backend
    590    title (org-export-toc-entry-backend 'texinfo) info))
    591 
    592 (defun org-texinfo--sanitize-content (text)
    593   "Escape special characters in string TEXT.
    594 Special characters are: @ { }"
    595   (replace-regexp-in-string "[@{}]" "@\\&" text))
    596 
    597 (defun org-texinfo--wrap-float (value info &optional type label caption short)
    598   "Wrap string VALUE within a @float command.
    599 INFO is the current export state, as a plist.  TYPE is float
    600 type, as a string.  LABEL is the cross reference label for the
    601 float, as a string.  CAPTION and SHORT are, respectively, the
    602 caption and shortcaption used for the float, as secondary
    603 strings (e.g., returned by `org-export-get-caption')."
    604   (let* ((backend
    605 	  (org-export-toc-entry-backend 'texinfo
    606 	    (cons 'footnote-reference
    607 		  (lambda (f c i) (org-export-with-backend 'texinfo f c i)))))
    608 	 (short-backend
    609 	  (org-export-toc-entry-backend 'texinfo
    610 	    '(inline-src-block . ignore)
    611 	    '(verbatim . ignore)))
    612 	 (short-str
    613 	  (if (and short caption)
    614 	      (format "@shortcaption{%s}\n"
    615 		      (org-export-data-with-backend short short-backend info))
    616 	    ""))
    617 	 (caption-str
    618 	  (if (or short caption)
    619 	      (format "@caption{%s}\n"
    620 		      (org-export-data-with-backend
    621 		       (or caption short)
    622 		       (if (equal short-str "") short-backend backend)
    623 		       info))
    624 	    "")))
    625     (format "@float %s%s\n%s\n%s%s@end float"
    626 	    type (if label (concat "," label) "") value caption-str short-str)))
    627 
    628 (defun org-texinfo--sectioning-structure (info)
    629   "Return sectioning structure used in the document.
    630 INFO is a plist holding export options."
    631   (let ((class (plist-get info :texinfo-class)))
    632     (pcase (assoc class (plist-get info :texinfo-classes))
    633       (`(,_ ,_ . ,sections) sections)
    634       (_ (user-error "Unknown Texinfo class: %S" class)))))
    635 
    636 (defun org-texinfo--separate-definitions (tree _backend info)
    637   "Split up descriptive lists in TREE that contain Texinfo definition commands.
    638 INFO is a plist used as a communication channel.
    639 Return new tree."
    640   (org-element-map tree 'plain-list
    641     (lambda (plain-list)
    642       (when (eq (org-element-property :type plain-list) 'descriptive)
    643 	(let ((contents (org-element-contents plain-list))
    644 	      (items nil))
    645 	  (dolist (item contents)
    646 	    (pcase-let ((`(,cmd . ,args) (org-texinfo--match-definition item)))
    647 	      (cond
    648 	       (cmd
    649 		(when items
    650 		  (org-texinfo--split-plain-list plain-list (nreverse items))
    651 		  (setq items nil))
    652 		(org-texinfo--split-definition plain-list item cmd args))
    653 	       (t
    654 		(when args
    655 		  (org-texinfo--massage-key-item plain-list item args info))
    656 		(push item items)))))
    657 	  (unless (org-element-contents plain-list)
    658 	    (org-element-extract-element plain-list)))))
    659     info)
    660   tree)
    661 
    662 (defun org-texinfo--match-definition (item)
    663   "Return a cons-cell if ITEM specifies a Texinfo definition command.
    664 The car is the command and the cdr is its arguments."
    665   (let ((tag (car-safe (org-element-property :tag item))))
    666     (and tag
    667 	 (stringp tag)
    668 	 (string-match org-texinfo--definition-command-regexp tag)
    669 	 (pcase-let*
    670 	     ((cmd (car (rassoc (match-string-no-properties 1 tag)
    671 				 org-texinfo--definition-command-alist)))
    672 	      (`(,cmd ,category)
    673 	       (and cmd (save-match-data (split-string cmd " "))))
    674 	      (args (match-string-no-properties 2 tag)))
    675 	   (cons cmd (if category (concat category " " args) args))))))
    676 
    677 (defun org-texinfo--split-definition (plain-list item cmd args)
    678   "Insert a definition command before list PLAIN-LIST.
    679 Replace list item ITEM with a special-block that inherits the
    680 contents of ITEM and whose type and Texinfo attributes are
    681 specified by CMD and ARGS."
    682   (let ((contents (org-element-contents item)))
    683     (org-element-insert-before
    684      (apply #'org-element-create 'special-block
    685 	    (list :type cmd
    686 		  :attr_texinfo (list (format ":options %s" args))
    687 		  :post-blank (if contents 1 0))
    688 	    (mapc #'org-element-extract-element contents))
    689      plain-list))
    690   (org-element-extract-element item))
    691 
    692 (defun org-texinfo--split-plain-list (plain-list items)
    693   "Insert a new plain list before the plain list PLAIN-LIST.
    694 Remove ITEMS from PLAIN-LIST and use them as the contents of the
    695 new plain list."
    696   (org-element-insert-before
    697    (apply #'org-element-create 'plain-list
    698 	  (list :type 'descriptive
    699                 :attr_texinfo (org-element-property :attr_texinfo plain-list)
    700                 :post-blank 1)
    701 	  (mapc #'org-element-extract-element items))
    702    plain-list))
    703 
    704 (defun org-texinfo--massage-key-item (plain-list item args info)
    705   "In PLAIN-LIST modify ITEM based on ARGS.
    706 
    707 Reformat ITEM's tag property and determine the arguments for the
    708 `@findex' and `@kindex' commands for ITEM and store them in ITEM
    709 using the `:findex' and `:kindex' properties.
    710 
    711 If PLAIN-LIST is a description list whose `:compact' attribute is
    712 non-nil and ITEM has no content but is followed by another item,
    713 then store the `@findex' and `@kindex' values in the next item.
    714 If the previous item stored its respective values in this item,
    715 then move them to the next item.
    716 
    717 INFO is a plist used as a communication channel."
    718   (let ((key nil)
    719 	(cmd nil))
    720     (if (string-match (rx (+ " ")
    721 			  "(" (group (+ (not (any "()")))) ")"
    722 			  (* " ")
    723 			  eos)
    724 		      args)
    725 	(setq key (substring args 0 (match-beginning 0))
    726 	      cmd (match-string 1 args))
    727       (setq key args))
    728     (org-element-put-property
    729      item :tag
    730      (cons (org-export-raw-string (org-texinfo-kbd-macro key t))
    731 	   (and cmd `(" (" (code (:value ,cmd :post-blank 0)) ")"))))
    732     (let ((findex (org-element-property :findex item))
    733 	  (kindex (org-element-property :kindex item))
    734 	  (next-item (org-export-get-next-element item nil))
    735 	  (mx (string-prefix-p "M-x " key)))
    736       (when (and (not cmd) mx)
    737 	(setq cmd (substring key 4)))
    738       (when (and cmd (not (member cmd findex)))
    739 	(setq findex (nconc findex (list cmd))))
    740       (unless mx
    741 	(setq kindex (nconc kindex (list key))))
    742       (cond
    743        ((and next-item
    744              (or (plist-get info :texinfo-compact-itemx)
    745 	         (org-not-nil
    746 	          (org-export-read-attribute :attr_texinfo plain-list :compact)))
    747 	     (not (org-element-contents item))
    748 	     (eq 1 (org-element-property :post-blank item)))
    749 	(org-element-put-property next-item :findex findex)
    750 	(org-element-put-property next-item :kindex kindex)
    751 	(org-element-put-property item :findex nil)
    752 	(org-element-put-property item :kindex nil))
    753        (t
    754 	(org-element-set-contents
    755 	 item
    756 	 (nconc (mapcar (lambda (key) `(keyword (:key "KINDEX" :value ,key))) kindex)
    757 		(mapcar (lambda (cmd) `(keyword (:key "FINDEX" :value ,cmd))) findex)
    758 		(org-element-contents item))))))))
    759 
    760 ;;; Template
    761 
    762 (defun org-texinfo-template (contents info)
    763   "Return complete document string after Texinfo conversion.
    764 CONTENTS is the transcoded contents string.  INFO is a plist
    765 holding export options."
    766   (let ((title (org-export-data (plist-get info :title) info))
    767 	;; Copying data is the contents of the first headline in
    768 	;; parse tree with a non-nil copying property.
    769 	(copying (org-element-map (plist-get info :parse-tree) 'headline
    770 		   (lambda (hl)
    771 		     (and (org-not-nil (org-element-property :COPYING hl))
    772 			  (org-element-contents hl)))
    773 		   info t)))
    774     (concat
    775      "\\input texinfo    @c -*- texinfo -*-\n"
    776      "@c %**start of header\n"
    777      (let ((file (or (org-strip-quotes (plist-get info :texinfo-filename))
    778 		     (let ((f (plist-get info :output-file)))
    779 		       (and f (concat (file-name-sans-extension f) ".info"))))))
    780        (and file (format "@setfilename %s\n" file)))
    781      (format "@settitle %s\n" title)
    782      ;; Insert class-defined header.
    783      (org-element-normalize-string
    784       (let ((header (nth 1 (assoc (plist-get info :texinfo-class)
    785 				  org-texinfo-classes)))
    786 	    (coding
    787 	     (catch 'coding-system
    788 	       (let ((case-fold-search t)
    789 		     (name (symbol-name (or org-texinfo-coding-system
    790 					    buffer-file-coding-system))))
    791 		 (dolist (system org-texinfo-supported-coding-systems "UTF-8")
    792 		   (when (string-match-p (regexp-quote system) name)
    793 		     (throw 'coding-system system))))))
    794 	    (language (plist-get info :language))
    795 	    (case-fold-search nil))
    796 	;; Auto coding system.
    797 	(replace-regexp-in-string
    798 	 "^@documentencoding \\(AUTO\\)$"
    799 	 coding
    800 	 (replace-regexp-in-string
    801 	  "^@documentlanguage \\(AUTO\\)$" language header t nil 1)
    802 	 t nil 1)))
    803      ;; Additional header options set by #+TEXINFO_HEADER.
    804      (let ((texinfo-header (plist-get info :texinfo-header)))
    805        (and texinfo-header (org-element-normalize-string texinfo-header)))
    806      "@c %**end of header\n\n"
    807      ;; Additional options set by #+TEXINFO_POST_HEADER.
    808      (let ((texinfo-post-header (plist-get info :texinfo-post-header)))
    809        (and texinfo-post-header
    810 	    (org-element-normalize-string texinfo-post-header)))
    811      ;; Copying.
    812      (and copying
    813 	  (format "@copying\n%s@end copying\n\n"
    814 		  (org-element-normalize-string
    815 		   (org-export-data copying info))))
    816      ;; Info directory information.  Only supply if both title and
    817      ;; category are provided.
    818      (let ((dircat (plist-get info :texinfo-dircat))
    819 	   (dirtitle
    820 	    (let ((title (plist-get info :texinfo-dirtitle)))
    821 	      (and title
    822 		   (string-match "^\\(?:\\* \\)?\\(.*?\\)\\(\\.\\)?$" title)
    823 		   (format "* %s." (match-string 1 title))))))
    824        (when (and dircat dirtitle)
    825 	 (concat "@dircategory " dircat "\n"
    826 		 "@direntry\n"
    827 		 (let ((dirdesc
    828 			(let ((desc (plist-get info :texinfo-dirdesc)))
    829 			  (cond ((not desc) nil)
    830 				((string-suffix-p "." desc) desc)
    831 				(t (concat desc "."))))))
    832 		   (if dirdesc (format "%-23s %s" dirtitle dirdesc) dirtitle))
    833 		 "\n"
    834 		 "@end direntry\n\n")))
    835      ;; Title
    836      "@finalout\n"
    837      "@titlepage\n"
    838      (when (plist-get info :with-title)
    839        (concat
    840 	(format "@title %s\n"
    841 		(or (plist-get info :texinfo-printed-title) title ""))
    842 	(let ((subtitle (plist-get info :subtitle)))
    843 	  (when subtitle
    844 	    (format "@subtitle %s\n"
    845 		    (org-export-data subtitle info))))))
    846      (when (plist-get info :with-author)
    847        (concat
    848 	;; Primary author.
    849 	(let ((author (org-string-nw-p
    850 		       (org-export-data (plist-get info :author) info)))
    851 	      (email (and (plist-get info :with-email)
    852 			  (org-string-nw-p
    853 			   (org-export-data (plist-get info :email) info)))))
    854 	  (cond ((and author email)
    855 		 (format "@author %s (@email{%s})\n" author email))
    856 		(author (format "@author %s\n" author))
    857 		(email (format "@author @email{%s}\n" email))))
    858 	;; Other authors.
    859 	(let ((subauthor (plist-get info :subauthor)))
    860 	  (and subauthor
    861 	       (org-element-normalize-string
    862 		(replace-regexp-in-string "^" "@author " subauthor))))))
    863      (and copying "@page\n@vskip 0pt plus 1filll\n@insertcopying\n")
    864      "@end titlepage\n\n"
    865      ;; Table of contents.
    866      (and (plist-get info :with-toc) "@contents\n\n")
    867      ;; Configure Top Node when not for TeX.  Also include contents
    868      ;; from the first section of the document.
    869      "@ifnottex\n"
    870      "@node Top\n"
    871      (format "@top %s\n" title)
    872      (let* ((first-section
    873 	     (org-element-map (plist-get info :parse-tree) 'section
    874 	       #'identity info t '(headline)))
    875 	    (top-contents
    876 	     (org-export-data (org-element-contents first-section) info)))
    877        (and (org-string-nw-p top-contents) (concat "\n" top-contents)))
    878      "@end ifnottex\n\n"
    879      ;; Menu.
    880      (org-texinfo-make-menu (plist-get info :parse-tree) info 'master)
    881      "\n"
    882      ;; Document's body.
    883      contents "\n"
    884      ;; Creator.
    885      (and (plist-get info :with-creator)
    886 	  (concat (plist-get info :creator) "\n"))
    887      ;; Document end.
    888      "@bye")))
    889 
    890 
    891 
    892 ;;; Transcode Functions
    893 
    894 ;;;; Bold
    895 
    896 (defun org-texinfo-bold (_bold contents info)
    897   "Transcode BOLD from Org to Texinfo.
    898 CONTENTS is the text with bold markup.  INFO is a plist holding
    899 contextual information."
    900   (org-texinfo--text-markup contents 'bold info))
    901 
    902 ;;;; Center Block
    903 
    904 (defun org-texinfo-center-block (_center-block contents _info)
    905   "Transcode a CENTER-BLOCK element from Org to Texinfo.
    906 CONTENTS holds the contents of the block.  INFO is a plist used
    907 as a communication channel."
    908   (replace-regexp-in-string "\\(^\\).*?\\S-" "@center " contents nil nil 1))
    909 
    910 ;;;; Clock
    911 
    912 (defun org-texinfo-clock (clock _contents info)
    913   "Transcode a CLOCK element from Org to Texinfo.
    914 CONTENTS is nil.  INFO is a plist holding contextual
    915 information."
    916   (concat
    917    "@noindent"
    918    (format "@strong{%s} " org-clock-string)
    919    (format (plist-get info :texinfo-inactive-timestamp-format)
    920 	   (concat (org-timestamp-translate (org-element-property :value clock))
    921 		   (let ((time (org-element-property :duration clock)))
    922 		     (and time (format " (%s)" time)))))
    923    "@*"))
    924 
    925 ;;;; Code
    926 
    927 (defun org-texinfo-code (code _contents info)
    928   "Transcode a CODE object from Org to Texinfo.
    929 CONTENTS is nil.  INFO is a plist used as a communication
    930 channel."
    931   (org-texinfo--text-markup (org-element-property :value code) 'code info))
    932 
    933 ;;;; Drawer
    934 
    935 (defun org-texinfo-drawer (drawer contents info)
    936   "Transcode a DRAWER element from Org to Texinfo.
    937 CONTENTS holds the contents of the block.  INFO is a plist
    938 holding contextual information."
    939   (let* ((name (org-element-property :drawer-name drawer))
    940 	 (output (funcall (plist-get info :texinfo-format-drawer-function)
    941 			  name contents)))
    942     output))
    943 
    944 ;;;; Dynamic Block
    945 
    946 (defun org-texinfo-dynamic-block (_dynamic-block contents _info)
    947   "Transcode a DYNAMIC-BLOCK element from Org to Texinfo.
    948 CONTENTS holds the contents of the block.  INFO is a plist
    949 holding contextual information."
    950   contents)
    951 
    952 ;;;; Entity
    953 
    954 (defun org-texinfo-entity (entity _contents _info)
    955   "Transcode an ENTITY object from Org to Texinfo."
    956   ;; Since there is not specific Texinfo entry in entities, use
    957   ;; Texinfo-specific commands whenever possible, and fallback to
    958   ;; UTF-8 otherwise.
    959   (pcase (org-element-property :name entity)
    960     ("AElig"                       "@AE{}")
    961     ("aelig"                       "@ae{}")
    962     ((or "bull" "bullet")          "@bullet{}")
    963     ("copy"                        "@copyright{}")
    964     ("deg"                         "@textdegree{}")
    965     ((or "dots" "hellip")          "@dots{}")
    966     ("equiv"                       "@equiv{}")
    967     ((or "euro" "EUR")             "@euro{}")
    968     ((or "ge" "geq")               "@geq{}")
    969     ("laquo"                       "@guillemetleft{}")
    970     ("iexcl"                       "@exclamdown{}")
    971     ("imath"                       "@dotless{i}")
    972     ("iquest"                      "@questiondown{}")
    973     ("jmath"                       "@dotless{j}")
    974     ((or "le" "leq")               "@leq{}")
    975     ("lsaquo"                      "@guilsinglleft{}")
    976     ("mdash"                       "---")
    977     ("minus"                       "@minus{}")
    978     ("nbsp"                        "@tie{}")
    979     ("ndash"                       "--")
    980     ("OElig"                       "@OE{}")
    981     ("oelig"                       "@oe{}")
    982     ("ordf"                        "@ordf{}")
    983     ("ordm"                        "@ordm{}")
    984     ("pound"                       "@pound{}")
    985     ("raquo"                       "@guillemetright{}")
    986     ((or "rArr" "Rightarrow")      "@result{}")
    987     ("reg"                         "@registeredsymbol{}")
    988     ((or "rightarrow" "to" "rarr") "@arrow{}")
    989     ("rsaquo"                      "@guilsinglright{}")
    990     ("thorn"                       "@th{}")
    991     ("THORN"                       "@TH{}")
    992     ((and (pred (string-prefix-p "_")) name) ;spacing entities
    993      (format "@w{%s}" (substring name 1)))
    994     (_ (org-element-property :utf-8 entity))))
    995 
    996 ;;;; Example Block
    997 
    998 (defun org-texinfo-example-block (example-block _contents info)
    999   "Transcode an EXAMPLE-BLOCK element from Org to Texinfo.
   1000 CONTENTS is nil.  INFO is a plist holding contextual
   1001 information."
   1002   (format "@example\n%s@end example"
   1003 	  (org-texinfo--sanitize-content
   1004 	   (org-export-format-code-default example-block info))))
   1005 
   1006 ;;; Export Block
   1007 
   1008 (defun org-texinfo-export-block (export-block _contents _info)
   1009   "Transcode a EXPORT-BLOCK element from Org to Texinfo.
   1010 CONTENTS is nil.  INFO is a plist holding contextual information."
   1011   (when (string= (org-element-property :type export-block) "TEXINFO")
   1012     (org-remove-indentation (org-element-property :value export-block))))
   1013 
   1014 ;;; Export Snippet
   1015 
   1016 (defun org-texinfo-export-snippet (export-snippet _contents _info)
   1017   "Transcode a EXPORT-SNIPPET object from Org to Texinfo.
   1018 CONTENTS is nil.  INFO is a plist holding contextual information."
   1019   (when (eq (org-export-snippet-backend export-snippet) 'texinfo)
   1020     (org-element-property :value export-snippet)))
   1021 
   1022 ;;;; Fixed Width
   1023 
   1024 (defun org-texinfo-fixed-width (fixed-width _contents _info)
   1025   "Transcode a FIXED-WIDTH element from Org to Texinfo.
   1026 CONTENTS is nil.  INFO is a plist holding contextual information."
   1027   (format "@example\n%s\n@end example"
   1028 	  (org-remove-indentation
   1029 	   (org-texinfo--sanitize-content
   1030 	    (org-element-property :value fixed-width)))))
   1031 
   1032 ;;;; Footnote Reference
   1033 
   1034 (defun org-texinfo-footnote-reference (footnote _contents info)
   1035   "Create a footnote reference for FOOTNOTE.
   1036 
   1037 FOOTNOTE is the footnote to define.  CONTENTS is nil.  INFO is a
   1038 plist holding contextual information."
   1039   (let* ((contents (org-export-get-footnote-definition footnote info))
   1040          (data (org-export-data contents info)))
   1041     (format "@footnote{%s}"
   1042             ;; It is invalid to close a footnote on a line starting
   1043             ;; with "@end".  As a safety net, we leave a newline
   1044             ;; character before the closing brace.  However, when the
   1045             ;; footnote ends with a paragraph, it is visually pleasing
   1046             ;; to move the brace right after its end.
   1047             (if (eq 'paragraph (org-element-type (org-last contents)))
   1048                 (org-trim data)
   1049               data))))
   1050 
   1051 ;;;; Headline
   1052 
   1053 (defun org-texinfo-headline (headline contents info)
   1054   "Transcode a HEADLINE element from Org to Texinfo.
   1055 CONTENTS holds the contents of the headline.  INFO is a plist
   1056 holding contextual information."
   1057   (cond
   1058    ((org-element-property :footnote-section-p headline) nil)
   1059    ((org-not-nil (org-export-get-node-property :COPYING headline t)) nil)
   1060    (t
   1061     (let* ((index (let ((i (org-export-get-node-property :INDEX headline t)))
   1062 		    (and (member i '("cp" "fn" "ky" "pg" "tp" "vr")) i)))
   1063 	   (numbered? (org-export-numbered-headline-p headline info))
   1064 	   (notoc? (org-export-excluded-from-toc-p headline info))
   1065 	   (command
   1066 	    (and
   1067              (not (org-export-low-level-p headline info))
   1068 	     (let ((sections (org-texinfo--sectioning-structure info)))
   1069                (pcase (nth (1- (org-export-get-relative-level headline info))
   1070 			   sections)
   1071 		 (`(,numbered ,unnumbered ,unnumbered-no-toc ,appendix)
   1072 		  (cond
   1073 		   ((org-not-nil
   1074 		     (org-export-get-node-property :APPENDIX headline t))
   1075 		    appendix)
   1076 		   (numbered? numbered)
   1077 		   (index unnumbered)
   1078 		   (notoc? unnumbered-no-toc)
   1079 		   (t unnumbered)))
   1080 		 (`nil nil)
   1081 		 (_ (user-error "Invalid Texinfo class specification: %S"
   1082 				(plist-get info :texinfo-class)))))))
   1083 	   (todo
   1084 	    (and (plist-get info :with-todo-keywords)
   1085 		 (let ((todo (org-element-property :todo-keyword headline)))
   1086 		   (and todo (org-export-data todo info)))))
   1087 	   (todo-type (and todo (org-element-property :todo-type headline)))
   1088 	   (tags (and (plist-get info :with-tags)
   1089 		      (org-export-get-tags headline info)))
   1090 	   (priority (and (plist-get info :with-priority)
   1091 			  (org-element-property :priority headline)))
   1092 	   (text (org-texinfo--sanitize-title
   1093 		  (org-element-property :title headline) info))
   1094 	   (full-text
   1095 	    (funcall (plist-get info :texinfo-format-headline-function)
   1096 		     todo todo-type priority text tags))
   1097 	   (contents
   1098 	    (concat "\n"
   1099 		    (if (org-string-nw-p contents) (concat "\n" contents) "")
   1100 		    (and index (format "\n@printindex %s\n" index))))
   1101            (node (org-texinfo--get-node headline info)))
   1102       (if (not command)
   1103 	  (concat (and (org-export-first-sibling-p headline info)
   1104 		       (format "@%s\n" (if numbered? 'enumerate 'itemize)))
   1105 		  (format "@item\n@anchor{%s}%s\n" node full-text)
   1106 		  contents
   1107 		  (if (org-export-last-sibling-p headline info)
   1108 		      (format "@end %s" (if numbered? 'enumerate 'itemize))
   1109 		    "\n"))
   1110 	(concat
   1111 	 ;; Even if HEADLINE is using @subheading and al., leave an
   1112 	 ;; anchor so cross-references in the Org document still work.
   1113 	 (format (if notoc? "@anchor{%s}\n" "@node %s\n") node)
   1114 	 (format command full-text)
   1115 	 contents))))))
   1116 
   1117 (defun org-texinfo-format-headline-default-function
   1118     (todo _todo-type priority text tags)
   1119   "Default format function for a headline.
   1120 See `org-texinfo-format-headline-function' for details."
   1121   (concat (and todo (format "@strong{%s} " todo))
   1122 	  (and priority (format "@emph{#%s} " priority))
   1123 	  text
   1124 	  (and tags (concat " " (org-make-tag-string tags)))))
   1125 
   1126 ;;;; Inline Src Block
   1127 
   1128 (defun org-texinfo-inline-src-block (inline-src-block _contents _info)
   1129   "Transcode an INLINE-SRC-BLOCK element from Org to Texinfo.
   1130 CONTENTS holds the contents of the item.  INFO is a plist holding
   1131 contextual information."
   1132   (format "@code{%s}"
   1133 	  (org-texinfo--sanitize-content
   1134 	   (org-element-property :value inline-src-block))))
   1135 
   1136 ;;;; Inlinetask
   1137 
   1138 (defun org-texinfo-inlinetask (inlinetask contents info)
   1139   "Transcode an INLINETASK element from Org to Texinfo.
   1140 CONTENTS holds the contents of the block.  INFO is a plist
   1141 holding contextual information."
   1142   (let ((title (org-export-data (org-element-property :title inlinetask) info))
   1143 	(todo (and (plist-get info :with-todo-keywords)
   1144 		   (let ((todo (org-element-property :todo-keyword inlinetask)))
   1145 		     (and todo (org-export-data todo info)))))
   1146 	(todo-type (org-element-property :todo-type inlinetask))
   1147 	(tags (and (plist-get info :with-tags)
   1148 		   (org-export-get-tags inlinetask info)))
   1149 	(priority (and (plist-get info :with-priority)
   1150 		       (org-element-property :priority inlinetask))))
   1151     (funcall (plist-get info :texinfo-format-inlinetask-function)
   1152 	     todo todo-type priority title tags contents)))
   1153 
   1154 (defun org-texinfo-format-inlinetask-default-function
   1155     (todo _todo-type priority title tags contents)
   1156   "Default format function for inlinetasks.
   1157 See `org-texinfo-format-inlinetask-function' for details."
   1158   (let ((full-title
   1159 	 (concat (when todo (format "@strong{%s} " todo))
   1160 		 (when priority (format "#%c " priority))
   1161 		 title
   1162 		 (when tags (org-make-tag-string tags)))))
   1163     (format "@center %s\n\n%s\n" full-title contents)))
   1164 
   1165 ;;;; Italic
   1166 
   1167 (defun org-texinfo-italic (_italic contents info)
   1168   "Transcode ITALIC from Org to Texinfo.
   1169 CONTENTS is the text with italic markup.  INFO is a plist holding
   1170 contextual information."
   1171   (org-texinfo--text-markup contents 'italic info))
   1172 
   1173 ;;;; Item
   1174 
   1175 (defun org-texinfo-item (item contents info)
   1176   "Transcode an ITEM element from Org to Texinfo.
   1177 CONTENTS holds the contents of the item.  INFO is a plist holding
   1178 contextual information."
   1179   (let* ((tag (org-element-property :tag item))
   1180          (plain-list (org-element-property :parent item))
   1181          (compact (and (eq (org-element-property :type plain-list) 'descriptive)
   1182                        (or (plist-get info :texinfo-compact-itemx)
   1183                            (org-not-nil (org-export-read-attribute
   1184                                          :attr_texinfo plain-list :compact)))))
   1185          (previous-item nil))
   1186     (when (and compact
   1187                (org-export-get-next-element item info)
   1188                (not (org-element-contents item))
   1189                (eq 1 (org-element-property :post-blank item)))
   1190       (org-element-put-property item :post-blank 0))
   1191     (if (and compact
   1192              (setq previous-item (org-export-get-previous-element item info))
   1193              (not (org-element-contents previous-item))
   1194 	     (eq 0 (org-element-property :post-blank previous-item)))
   1195         (format "@itemx%s\n%s"
   1196                 (if tag (concat " " (org-export-data tag info)) "")
   1197                 (or contents ""))
   1198       (let* ((split (org-string-nw-p (org-export-read-attribute
   1199                                       :attr_texinfo plain-list :sep)))
   1200 	     (items (and tag
   1201 		         (let ((tag (org-export-data tag info)))
   1202 		           (if split
   1203 			       (split-string tag (regexp-quote split)
   1204                                              t "[ \t\n]+")
   1205 			     (list tag))))))
   1206         (format "%s\n%s"
   1207 	        (pcase items
   1208 	          (`nil "@item")
   1209 	          (`(,item) (concat "@item " item))
   1210 	          (`(,item . ,items)
   1211 	           (concat "@item " item "\n"
   1212 		           (mapconcat (lambda (i) (concat "@itemx " i))
   1213 				      items
   1214 				      "\n"))))
   1215 	        (or contents ""))))))
   1216 
   1217 ;;;; Keyword
   1218 
   1219 (defun org-texinfo-keyword (keyword _contents info)
   1220   "Transcode a KEYWORD element from Org to Texinfo.
   1221 CONTENTS is nil.  INFO is a plist holding contextual information."
   1222   (let ((value (org-element-property :value keyword)))
   1223     (pcase (org-element-property :key keyword)
   1224       ("TEXINFO" value)
   1225       ("CINDEX" (format "@cindex %s" value))
   1226       ("FINDEX" (format "@findex %s" value))
   1227       ("KINDEX" (format "@kindex %s" value))
   1228       ("PINDEX" (format "@pindex %s" value))
   1229       ("TINDEX" (format "@tindex %s" value))
   1230       ("VINDEX" (format "@vindex %s" value))
   1231       ("TOC"
   1232        (cond ((string-match-p "\\<tables\\>" value)
   1233 	      (concat "@listoffloats "
   1234 		      (org-export-translate "Table" :utf-8 info)))
   1235 	     ((string-match-p "\\<listings\\>" value)
   1236 	      (concat "@listoffloats "
   1237 		      (org-export-translate "Listing" :utf-8 info))))))))
   1238 
   1239 ;;;; LaTeX Environment
   1240 
   1241 (defun org-texinfo-latex-environment (environment _contents info)
   1242   "Transcode a LaTeX ENVIRONMENT from Org to Texinfo.
   1243 CONTENTS is ignored.  INFO is a plist holding contextual information."
   1244   (let ((with-latex (plist-get info :with-latex)))
   1245     (when (or (eq with-latex t)
   1246               (and (eq with-latex 'detect)
   1247                    (org-texinfo-supports-math-p)))
   1248       (let ((value (org-element-property :value environment)))
   1249         (string-join (list "@displaymath"
   1250                            (string-trim (org-remove-indentation value))
   1251                            "@end displaymath")
   1252                      "\n")))))
   1253 
   1254 ;;;; LaTeX Fragment
   1255 
   1256 (defun org-texinfo-latex-fragment (fragment _contents info)
   1257   "Transcode a LaTeX FRAGMENT from Org to Texinfo.
   1258 INFO is a plist holding contextual information."
   1259   (let ((with-latex (plist-get info :with-latex)))
   1260     (when (or (eq with-latex t)
   1261               (and (eq with-latex 'detect)
   1262                    (org-texinfo-supports-math-p)))
   1263       (let ((value (org-remove-indentation
   1264                     (org-element-property :value fragment))))
   1265         (cond
   1266          ((or (string-match-p "^\\\\\\[" value)
   1267               (string-match-p "^\\$\\$" value))
   1268           (concat "\n"
   1269                   "@displaymath"
   1270                   "\n"
   1271                   (string-trim (substring value 2 -2))
   1272                   "\n"
   1273                   "@end displaymath"
   1274                   "\n"))
   1275          ((string-match-p "^\\$" value)
   1276           (concat "@math{"
   1277                   (string-trim (substring value 1 -1))
   1278                   "}"))
   1279          ((string-match-p "^\\\\(" value)
   1280           (concat "@math{"
   1281                   (string-trim (substring value 2 -2))
   1282                   "}"))
   1283          (t value))))))
   1284 
   1285 ;;;; Line Break
   1286 
   1287 (defun org-texinfo-line-break (_line-break _contents _info)
   1288   "Transcode a LINE-BREAK object from Org to Texinfo.
   1289 CONTENTS is nil.  INFO is a plist holding contextual information."
   1290   "@*\n")
   1291 
   1292 ;;;; Link
   1293 
   1294 (defun org-texinfo--@ref (datum description info)
   1295   "Return @ref command for element or object DATUM.
   1296 DESCRIPTION is the printed name of the section, as a string, or
   1297 nil."
   1298   (let ((node-name (org-texinfo--get-node datum info))
   1299 	;; Sanitize DESCRIPTION for cross-reference use.  In
   1300 	;; particular, remove colons as they seem to cause pain (even
   1301 	;; within @asis{...}) to the Texinfo reader.
   1302 	(title (and description
   1303 		    (replace-regexp-in-string
   1304 		     "[ \t]*:+" ""
   1305 		     (replace-regexp-in-string "," "@comma{}" description)))))
   1306     (if (or (not title) (equal title node-name))
   1307 	(format "@ref{%s}" node-name)
   1308       (format "@ref{%s, , %s}" node-name title))))
   1309 
   1310 (defun org-texinfo-link (link desc info)
   1311   "Transcode a LINK object from Org to Texinfo.
   1312 DESC is the description part of the link, or the empty string.
   1313 INFO is a plist holding contextual information.  See
   1314 `org-export-data'."
   1315   (let* ((type (org-element-property :type link))
   1316 	 (raw-path (org-element-property :path link))
   1317 	 ;; Ensure DESC really exists, or set it to nil.
   1318 	 (desc (and (not (string= desc "")) desc))
   1319 	 (path (org-texinfo--sanitize-content
   1320 		(cond
   1321 		 ((member type '("http" "https" "ftp"))
   1322 		  (concat type ":" raw-path))
   1323 		 ((string-equal type "file")
   1324 		  (org-export-file-uri raw-path))
   1325 		 (t raw-path)))))
   1326     (cond
   1327      ((org-export-custom-protocol-maybe link desc 'texinfo info))
   1328      ((org-export-inline-image-p link org-texinfo-inline-image-rules)
   1329       (org-texinfo--inline-image link info))
   1330      ((equal type "radio")
   1331       (let ((destination (org-export-resolve-radio-link link info)))
   1332 	(if (not destination) desc
   1333 	  (org-texinfo--@ref destination desc info))))
   1334      ((member type '("custom-id" "id" "fuzzy"))
   1335       (let ((destination
   1336 	     (if (equal type "fuzzy")
   1337 		 (org-export-resolve-fuzzy-link link info)
   1338 	       (org-export-resolve-id-link link info))))
   1339 	(pcase (org-element-type destination)
   1340 	  (`nil
   1341 	   (format org-texinfo-link-with-unknown-path-format path))
   1342 	  ;; Id link points to an external file.
   1343 	  (`plain-text
   1344 	   (if desc (format "@uref{file://%s,%s}" destination desc)
   1345 	     (format "@uref{file://%s}" destination)))
   1346 	  ((or `headline
   1347 	       ;; Targets within headlines cannot be turned into
   1348 	       ;; @anchor{}, so we refer to the headline parent
   1349 	       ;; directly.
   1350 	       (and `target
   1351 		    (guard (eq 'headline
   1352 			       (org-element-type
   1353 				(org-element-property :parent destination))))))
   1354 	   (let ((headline (org-element-lineage destination '(headline) t)))
   1355 	     (org-texinfo--@ref headline desc info)))
   1356 	  (_ (org-texinfo--@ref destination desc info)))))
   1357      ((string= type "mailto")
   1358       (format "@email{%s}"
   1359 	      (concat path (and desc (concat ", " desc)))))
   1360      ;; External link with a description part.
   1361      ((and path desc) (format "@uref{%s, %s}" path desc))
   1362      ;; External link without a description part.
   1363      (path (format "@uref{%s}" path))
   1364      ;; No path, only description.  Try to do something useful.
   1365      (t
   1366       (format (plist-get info :texinfo-link-with-unknown-path-format) desc)))))
   1367 
   1368 (defun org-texinfo--inline-image (link info)
   1369   "Return Texinfo code for an inline image.
   1370 LINK is the link pointing to the inline image.  INFO is the
   1371 current state of the export, as a plist."
   1372   (let* ((parent (org-export-get-parent-element link))
   1373 	 (label (and (org-element-property :name parent)
   1374 		     (org-texinfo--get-node parent info)))
   1375 	 (caption (org-export-get-caption parent))
   1376 	 (shortcaption (org-export-get-caption parent t))
   1377 	 (path  (org-element-property :path link))
   1378 	 (filename
   1379 	  (file-name-sans-extension
   1380 	   (if (file-name-absolute-p path)
   1381                (expand-file-name path)
   1382              (file-relative-name path))))
   1383 	 (extension (file-name-extension path))
   1384 	 (attributes (org-export-read-attribute :attr_texinfo parent))
   1385 	 (height (or (plist-get attributes :height) ""))
   1386 	 (width (or (plist-get attributes :width) ""))
   1387 	 (alt (or (plist-get attributes :alt) ""))
   1388 	 (image (format "@image{%s,%s,%s,%s,%s}"
   1389 			filename width height alt extension)))
   1390     (cond ((or caption shortcaption)
   1391 	   (org-texinfo--wrap-float image
   1392 				    info
   1393 				    (org-export-translate "Figure" :utf-8 info)
   1394 				    label
   1395 				    caption
   1396 				    shortcaption))
   1397 	  (label (concat "@anchor{" label "}\n" image))
   1398 	  (t image))))
   1399 
   1400 
   1401 ;;;; Menu
   1402 
   1403 (defun org-texinfo-make-menu (scope info &optional master)
   1404   "Create the menu for inclusion in the Texinfo document.
   1405 
   1406 SCOPE is a headline or a full parse tree.  INFO is the
   1407 communication channel, as a plist.
   1408 
   1409 When optional argument MASTER is non-nil, generate a master menu,
   1410 including detailed node listing."
   1411   (let ((menu (org-texinfo--build-menu scope info)))
   1412     (when (org-string-nw-p menu)
   1413       (org-element-normalize-string
   1414        (format
   1415 	"@menu\n%s@end menu"
   1416 	(concat menu
   1417 		(when master
   1418 		  (let ((detailmenu
   1419 			 (org-texinfo--build-menu
   1420 			  scope info
   1421 			  (let ((toc-depth (plist-get info :with-toc)))
   1422 			    (if (wholenump toc-depth) toc-depth
   1423 			      org-texinfo-max-toc-depth)))))
   1424 		    (when (org-string-nw-p detailmenu)
   1425 		      (concat "\n@detailmenu\n"
   1426 			      "--- The Detailed Node Listing ---\n\n"
   1427 			      detailmenu
   1428 			      "@end detailmenu\n"))))))))))
   1429 
   1430 (defun org-texinfo--build-menu (scope info &optional level)
   1431   "Build menu for entries within SCOPE.
   1432 SCOPE is a headline or a full parse tree.  INFO is a plist
   1433 containing contextual information.  When optional argument LEVEL
   1434 is an integer, build the menu recursively, down to this depth."
   1435   (cond
   1436    ((not level)
   1437     (org-texinfo--format-entries (org-texinfo--menu-entries scope info) info))
   1438    ((zerop level) "\n")
   1439    (t
   1440     (mapconcat
   1441      (lambda (h)
   1442        (let ((entries (org-texinfo--menu-entries h info)))
   1443 	 (when entries
   1444 	   (concat
   1445 	    (format "%s\n\n%s\n"
   1446 		    (org-export-data (org-export-get-alt-title h info) info)
   1447 		    (org-texinfo--format-entries entries info))
   1448 	    (org-texinfo--build-menu h info (1- level))))))
   1449      (org-texinfo--menu-entries scope info)
   1450      ""))))
   1451 
   1452 (defun org-texinfo--format-entries (entries info)
   1453   "Format all direct menu entries in SCOPE, as a string.
   1454 SCOPE is either a headline or a full Org document.  INFO is
   1455 a plist containing contextual information."
   1456   (org-element-normalize-string
   1457    (mapconcat
   1458     (lambda (h)
   1459       (let* ((title
   1460 	      ;; Colons are used as a separator between title and node
   1461 	      ;; name.  Remove them.
   1462 	      (replace-regexp-in-string
   1463 	       "[ \t]*:+" ""
   1464 	       (org-texinfo--sanitize-title
   1465 		(org-export-get-alt-title h info) info)))
   1466 	     (node (org-texinfo--get-node h info))
   1467 	     (entry (concat "* " title ":"
   1468 			    (if (string= title node) ":"
   1469 			      (concat " " node ". "))))
   1470 	     (desc (org-element-property :DESCRIPTION h)))
   1471 	(if (not desc) entry
   1472 	  (format (format "%%-%ds %%s" org-texinfo-node-description-column)
   1473 		  entry desc))))
   1474     entries "\n")))
   1475 
   1476 (defun org-texinfo--menu-entries (scope info)
   1477   "List direct children in SCOPE needing a menu entry.
   1478 SCOPE is a headline or a full parse tree.  INFO is a plist
   1479 holding contextual information."
   1480   (let* ((cache (or (plist-get info :texinfo-entries-cache)
   1481 		    (plist-get (plist-put info :texinfo-entries-cache
   1482 					  (make-hash-table :test #'eq))
   1483 			       :texinfo-entries-cache)))
   1484 	 (cached-entries (gethash scope cache 'no-cache)))
   1485     (if (not (eq cached-entries 'no-cache)) cached-entries
   1486       (let* ((sections (org-texinfo--sectioning-structure info))
   1487              (max-depth (length sections)))
   1488         (puthash scope
   1489 	         (cl-remove-if
   1490 		  (lambda (h)
   1491 		    (or (org-not-nil (org-export-get-node-property :COPYING h t))
   1492                         (< max-depth (org-export-get-relative-level h info))))
   1493 		  (org-export-collect-headlines info 1 scope))
   1494 	         cache)))))
   1495 
   1496 ;;;; Node Property
   1497 
   1498 (defun org-texinfo-node-property (node-property _contents _info)
   1499   "Transcode a NODE-PROPERTY element from Org to Texinfo.
   1500 CONTENTS is nil.  INFO is a plist holding contextual
   1501 information."
   1502   (format "%s:%s"
   1503           (org-element-property :key node-property)
   1504           (let ((value (org-element-property :value node-property)))
   1505             (if value (concat " " value) ""))))
   1506 
   1507 ;;;; Paragraph
   1508 
   1509 (defun org-texinfo-paragraph (_paragraph contents _info)
   1510   "Transcode a PARAGRAPH element from Org to Texinfo.
   1511 CONTENTS is the contents of the paragraph, as a string.  INFO is
   1512 the plist used as a communication channel."
   1513   contents)
   1514 
   1515 ;;;; Plain List
   1516 
   1517 (defun org-texinfo-plain-list (plain-list contents info)
   1518   "Transcode a PLAIN-LIST element from Org to Texinfo.
   1519 CONTENTS is the contents of the list.  INFO is a plist holding
   1520 contextual information."
   1521   (let* ((attr (org-export-read-attribute :attr_texinfo plain-list))
   1522 	 (indic (let ((i (or (plist-get attr :indic)
   1523 			     (plist-get info :texinfo-table-default-markup))))
   1524 		  ;; Allow indicating commands with missing @ sign.
   1525 		  (if (string-prefix-p "@" i) i (concat "@" i))))
   1526 	 (table-type (plist-get attr :table-type))
   1527 	 (type (org-element-property :type plain-list))
   1528 	 (enum
   1529 	  (cond ((not (eq type 'ordered)) nil)
   1530 		((plist-member attr :enum) (plist-get attr :enum))
   1531 		(t
   1532 		 ;; Texinfo only supports initial counters, i.e., it
   1533 		 ;; cannot change the numbering mid-list.
   1534 		 (let ((first-item (car (org-element-contents plain-list))))
   1535 		   (org-element-property :counter first-item)))))
   1536 	 (list-type (cond
   1537 		     ((eq type 'ordered) "enumerate")
   1538 		     ((eq type 'unordered) "itemize")
   1539 		     ((member table-type '("ftable" "vtable")) table-type)
   1540 		     (t "table"))))
   1541     (format "@%s\n%s@end %s"
   1542 	    (cond ((eq type 'descriptive) (concat list-type " " indic))
   1543 		  (enum (format "%s %s" list-type enum))
   1544 		  (t list-type))
   1545 	    contents
   1546 	    list-type)))
   1547 
   1548 ;;;; Plain Text
   1549 
   1550 (defun org-texinfo-plain-text (text info)
   1551   "Transcode a TEXT string from Org to Texinfo.
   1552 TEXT is the string to transcode.  INFO is a plist holding
   1553 contextual information."
   1554   ;; First protect @, { and }.
   1555   (let ((output (org-texinfo--sanitize-content text)))
   1556     ;; Activate smart quotes.  Be sure to provide original TEXT string
   1557     ;; since OUTPUT may have been modified.
   1558     (when (plist-get info :with-smart-quotes)
   1559       (setq output
   1560 	    (org-export-activate-smart-quotes output :texinfo info text)))
   1561     ;; LaTeX into @LaTeX{} and TeX into @TeX{}
   1562     (let ((case-fold-search nil))
   1563       (setq output (replace-regexp-in-string "\\(?:La\\)?TeX" "@\\&{}" output)))
   1564     ;; Convert special strings.
   1565     (when (plist-get info :with-special-strings)
   1566       (setq output
   1567 	    (replace-regexp-in-string
   1568 	     "\\.\\.\\." "@dots{}"
   1569 	     (replace-regexp-in-string "\\\\-" "@-" output))))
   1570     ;; Handle break preservation if required.
   1571     (when (plist-get info :preserve-breaks)
   1572       (setq output (replace-regexp-in-string
   1573 		    "\\(\\\\\\\\\\)?[ \t]*\n" " @*\n" output)))
   1574     ;; Reverse sentence ending.  A sentence can end with a capital
   1575     ;; letter.  Use non-breaking space if it shouldn't.
   1576     (let ((case-fold-search nil))
   1577       (replace-regexp-in-string
   1578        "[A-Z]\\([.?!]\\)\\(?:[])]\\|'\\{1,2\\}\\)?\\(?: \\|$\\)"
   1579        "@\\1"
   1580        output nil nil 1))))
   1581 
   1582 ;;;; Planning
   1583 
   1584 (defun org-texinfo-planning (planning _contents info)
   1585   "Transcode a PLANNING element from Org to Texinfo.
   1586 CONTENTS is nil.  INFO is a plist holding contextual
   1587 information."
   1588   (concat
   1589    "@noindent"
   1590    (mapconcat
   1591     'identity
   1592     (delq nil
   1593 	  (list
   1594 	   (let ((closed (org-element-property :closed planning)))
   1595 	     (when closed
   1596 	       (concat
   1597 		(format "@strong{%s} " org-closed-string)
   1598 		(format (plist-get info :texinfo-inactive-timestamp-format)
   1599 			(org-timestamp-translate closed)))))
   1600 	   (let ((deadline (org-element-property :deadline planning)))
   1601 	     (when deadline
   1602 	       (concat
   1603 		(format "@strong{%s} " org-deadline-string)
   1604 		(format (plist-get info :texinfo-active-timestamp-format)
   1605 			(org-timestamp-translate deadline)))))
   1606 	   (let ((scheduled (org-element-property :scheduled planning)))
   1607 	     (when scheduled
   1608 	       (concat
   1609 		(format "@strong{%s} " org-scheduled-string)
   1610 		(format (plist-get info :texinfo-active-timestamp-format)
   1611 			(org-timestamp-translate scheduled)))))))
   1612     " ")
   1613    "@*"))
   1614 
   1615 ;;;; Property Drawer
   1616 
   1617 (defun org-texinfo-property-drawer (_property-drawer contents _info)
   1618   "Transcode a PROPERTY-DRAWER element from Org to Texinfo.
   1619 CONTENTS holds the contents of the drawer.  INFO is a plist
   1620 holding contextual information."
   1621   (and (org-string-nw-p contents)
   1622        (format "@verbatim\n%s@end verbatim" contents)))
   1623 
   1624 ;;;; Quote Block
   1625 
   1626 (defun org-texinfo-quote-block (quote-block contents _info)
   1627   "Transcode a QUOTE-BLOCK element from Org to Texinfo.
   1628 CONTENTS holds the contents of the block.  INFO is a plist
   1629 holding contextual information."
   1630   (let ((tag (org-export-read-attribute :attr_texinfo quote-block :tag))
   1631 	(author (org-export-read-attribute :attr_texinfo quote-block :author)))
   1632     (format "@quotation%s\n%s%s\n@end quotation"
   1633 	    (if tag (concat " " tag) "")
   1634 	    contents
   1635 	    (if author (concat "\n@author " author) ""))))
   1636 
   1637 ;;;; Radio Target
   1638 
   1639 (defun org-texinfo-radio-target (radio-target text info)
   1640   "Transcode a RADIO-TARGET object from Org to Texinfo.
   1641 TEXT is the text of the target.  INFO is a plist holding
   1642 contextual information."
   1643   (format "@anchor{%s}%s"
   1644 	  (org-texinfo--get-node radio-target info)
   1645 	  text))
   1646 
   1647 ;;;; Section
   1648 
   1649 (defun org-texinfo-section (section contents info)
   1650   "Transcode a SECTION element from Org to Texinfo.
   1651 CONTENTS holds the contents of the section.  INFO is a plist
   1652 holding contextual information."
   1653   (let ((parent (org-export-get-parent-headline section)))
   1654     (when parent   ;first section is handled in `org-texinfo-template'
   1655       (org-trim
   1656        (concat contents
   1657 	       "\n"
   1658 	       (and (not (org-export-excluded-from-toc-p parent info))
   1659 		    (org-texinfo-make-menu parent info)))))))
   1660 
   1661 ;;;; Special Block
   1662 
   1663 (defun org-texinfo-special-block (special-block contents _info)
   1664   "Transcode a SPECIAL-BLOCK element from Org to Texinfo.
   1665 CONTENTS holds the contents of the block.  INFO is a plist used
   1666 as a communication channel."
   1667   (let ((opt (org-export-read-attribute :attr_texinfo special-block :options))
   1668 	(type (org-element-property :type special-block)))
   1669     (format "@%s%s\n%s@end %s"
   1670 	    type
   1671 	    (if opt (concat " " opt) "")
   1672 	    (or contents "")
   1673 	    type)))
   1674 
   1675 ;;;; Src Block
   1676 
   1677 (defun org-texinfo-src-block (src-block _contents info)
   1678   "Transcode a SRC-BLOCK element from Org to Texinfo.
   1679 CONTENTS holds the contents of the item.  INFO is a plist holding
   1680 contextual information."
   1681   (let* ((lisp (string-match-p "lisp"
   1682 			       (org-element-property :language src-block)))
   1683 	 (code (org-texinfo--sanitize-content
   1684 		(org-export-format-code-default src-block info)))
   1685 	 (value (format
   1686 		 (if lisp "@lisp\n%s@end lisp" "@example\n%s@end example")
   1687 		 code))
   1688 	 (caption (org-export-get-caption src-block))
   1689 	 (shortcaption (org-export-get-caption src-block t)))
   1690     (if (not (or caption shortcaption)) value
   1691       (org-texinfo--wrap-float value
   1692 			       info
   1693 			       (org-export-translate "Listing" :utf-8 info)
   1694 			       (org-texinfo--get-node src-block info)
   1695 			       caption
   1696 			       shortcaption))))
   1697 
   1698 ;;;; Statistics Cookie
   1699 
   1700 (defun org-texinfo-statistics-cookie (statistics-cookie _contents _info)
   1701   "Transcode a STATISTICS-COOKIE object from Org to Texinfo.
   1702 CONTENTS is nil.  INFO is a plist holding contextual information."
   1703   (org-element-property :value statistics-cookie))
   1704 
   1705 
   1706 ;;;; Strike-through
   1707 
   1708 (defun org-texinfo-strike-through (_strike-through contents info)
   1709   "Transcode STRIKE-THROUGH from Org to Texinfo.
   1710 CONTENTS is the text with strike-through markup.  INFO is a plist
   1711 holding contextual information."
   1712   (org-texinfo--text-markup contents 'strike-through info))
   1713 
   1714 ;;;; Subscript
   1715 
   1716 (defun org-texinfo-subscript (_subscript contents _info)
   1717   "Transcode a SUBSCRIPT object from Org to Texinfo.
   1718 CONTENTS is the contents of the object.  INFO is a plist holding
   1719 contextual information."
   1720   (format "@math{_%s}" contents))
   1721 
   1722 ;;;; Superscript
   1723 
   1724 (defun org-texinfo-superscript (_superscript contents _info)
   1725   "Transcode a SUPERSCRIPT object from Org to Texinfo.
   1726 CONTENTS is the contents of the object.  INFO is a plist holding
   1727 contextual information."
   1728   (format "@math{^%s}" contents))
   1729 
   1730 ;;;; Table
   1731 
   1732 (defun org-texinfo-table (table contents info)
   1733   "Transcode a TABLE element from Org to Texinfo.
   1734 CONTENTS is the contents of the table.  INFO is a plist holding
   1735 contextual information."
   1736   (if (eq (org-element-property :type table) 'table.el)
   1737       (format "@verbatim\n%s@end verbatim"
   1738 	      (org-element-normalize-string
   1739 	       (org-element-property :value table)))
   1740     (let* ((col-width (org-export-read-attribute :attr_texinfo table :columns))
   1741 	   (columns
   1742 	    (if col-width (format "@columnfractions %s" col-width)
   1743 	      (org-texinfo-table-column-widths table info)))
   1744 	   (caption (org-export-get-caption table))
   1745 	   (shortcaption (org-export-get-caption table t))
   1746 	   (table-str (format "@multitable %s\n%s@end multitable"
   1747 			      columns
   1748 			      contents)))
   1749       (if (not (or caption shortcaption)) table-str
   1750 	(org-texinfo--wrap-float table-str
   1751 				 info
   1752 				 (org-export-translate "Table" :utf-8 info)
   1753 				 (org-texinfo--get-node table info)
   1754 				 caption
   1755 				 shortcaption)))))
   1756 
   1757 (defun org-texinfo-table-column-widths (table info)
   1758   "Determine the largest table cell in each column to process alignment.
   1759 TABLE is the table element to transcode.  INFO is a plist used as
   1760 a communication channel."
   1761   (let ((widths (make-vector (cdr (org-export-table-dimensions table info)) 0)))
   1762     (org-element-map table 'table-row
   1763       (lambda (row)
   1764 	(let ((idx 0))
   1765 	  (org-element-map row 'table-cell
   1766 	    (lambda (cell)
   1767 	      ;; Length of the cell in the original buffer is only an
   1768 	      ;; approximation of the length of the cell in the
   1769 	      ;; output.  It can sometimes fail (e.g. it considers
   1770 	      ;; "/a/" being larger than "ab").
   1771 	      (let ((w (- (org-element-property :contents-end cell)
   1772 			  (org-element-property :contents-begin cell))))
   1773 		(aset widths idx (max w (aref widths idx))))
   1774 	      (cl-incf idx))
   1775 	    info)))
   1776       info)
   1777     (format "{%s}" (mapconcat (lambda (w) (make-string w ?a)) widths "} {"))))
   1778 
   1779 ;;;; Table Cell
   1780 
   1781 (defun org-texinfo-table-cell (table-cell contents info)
   1782   "Transcode a TABLE-CELL element from Org to Texinfo.
   1783 CONTENTS is the cell contents.  INFO is a plist used as
   1784 a communication channel."
   1785   (concat
   1786    (let ((scientific-notation
   1787 	  (plist-get info :texinfo-table-scientific-notation)))
   1788      (if (and contents
   1789 	      scientific-notation
   1790 	      (string-match orgtbl-exp-regexp contents))
   1791 	 ;; Use appropriate format string for scientific notation.
   1792 	 (format scientific-notation
   1793 		 (match-string 1 contents)
   1794 		 (match-string 2 contents))
   1795        contents))
   1796    (when (org-export-get-next-element table-cell info) "\n@tab ")))
   1797 
   1798 ;;;; Table Row
   1799 
   1800 (defun org-texinfo-table-row (table-row contents info)
   1801   "Transcode a TABLE-ROW element from Org to Texinfo.
   1802 CONTENTS is the contents of the row.  INFO is a plist used as
   1803 a communication channel."
   1804   ;; Rules are ignored since table separators are deduced from
   1805   ;; borders of the current row.
   1806   (when (eq (org-element-property :type table-row) 'standard)
   1807     (let ((rowgroup-tag
   1808 	   (if (and (= 1 (org-export-table-row-group table-row info))
   1809 		    (org-export-table-has-header-p
   1810 		     (org-export-get-parent-table table-row) info))
   1811 	       "@headitem "
   1812 	     "@item ")))
   1813       (concat rowgroup-tag contents "\n"))))
   1814 
   1815 ;;;; Target
   1816 
   1817 (defun org-texinfo-target (target _contents info)
   1818   "Transcode a TARGET object from Org to Texinfo.
   1819 CONTENTS is nil.  INFO is a plist holding contextual
   1820 information."
   1821   (format "@anchor{%s}" (org-texinfo--get-node target info)))
   1822 
   1823 ;;;; Timestamp
   1824 
   1825 (defun org-texinfo-timestamp (timestamp _contents info)
   1826   "Transcode a TIMESTAMP object from Org to Texinfo.
   1827 CONTENTS is nil.  INFO is a plist holding contextual
   1828 information."
   1829   (let ((value (org-texinfo-plain-text
   1830 		(org-timestamp-translate timestamp) info)))
   1831     (pcase (org-element-property :type timestamp)
   1832       ((or `active `active-range)
   1833        (format (plist-get info :texinfo-active-timestamp-format) value))
   1834       ((or `inactive `inactive-range)
   1835        (format (plist-get info :texinfo-inactive-timestamp-format) value))
   1836       (_ (format (plist-get info :texinfo-diary-timestamp-format) value)))))
   1837 
   1838 ;;;; Underline
   1839 
   1840 (defun org-texinfo-underline (_underline contents info)
   1841   "Transcode UNDERLINE from Org to Texinfo.
   1842 CONTENTS is the text with underline markup.  INFO is a plist
   1843 holding contextual information."
   1844   (org-texinfo--text-markup contents 'underline info))
   1845 
   1846 ;;;; Verbatim
   1847 
   1848 (defun org-texinfo-verbatim (verbatim _contents info)
   1849   "Transcode a VERBATIM object from Org to Texinfo.
   1850 CONTENTS is nil.  INFO is a plist used as a communication
   1851 channel."
   1852   (org-texinfo--text-markup
   1853    (org-element-property :value verbatim) 'verbatim info))
   1854 
   1855 ;;;; Verse Block
   1856 
   1857 (defun org-texinfo-verse-block (_verse-block contents _info)
   1858   "Transcode a VERSE-BLOCK element from Org to Texinfo.
   1859 CONTENTS is verse block contents.  INFO is a plist holding
   1860 contextual information."
   1861   (format "@display\n%s@end display" contents))
   1862 
   1863 
   1864 ;;; Public Functions
   1865 
   1866 (defun org-texinfo-kbd-macro (key &optional noquote)
   1867   "Quote KEY using @kbd{...} and if necessary @key{...}.
   1868 
   1869 This is intended to be used as an Org macro like so:
   1870 
   1871   #+macro: kbd (eval (org-texinfo-kbd-macro $1))
   1872   Type {{{kbd(C-c SPC)}}}.
   1873 
   1874 Also see info node `(org)Key bindings in Texinfo export'.
   1875 
   1876 If optional NOQOUTE is non-nil, then do not add the quoting
   1877 that is necessary when using this in an Org macro."
   1878   (format (if noquote "@kbd{%s}" "@@texinfo:@kbd{@@%s@@texinfo:}@@")
   1879 	  (let ((case-fold-search nil))
   1880 	    (replace-regexp-in-string
   1881 	     org-texinfo--quoted-keys-regexp
   1882 	     (if noquote "@key{\\&}" "@@texinfo:@key{@@\\&@@texinfo:}@@")
   1883 	     key t))))
   1884 
   1885 ;;; Interactive Functions
   1886 
   1887 ;;;###autoload
   1888 (defun org-texinfo-export-to-texinfo
   1889     (&optional async subtreep visible-only body-only ext-plist)
   1890   "Export current buffer to a Texinfo file.
   1891 
   1892 If narrowing is active in the current buffer, only export its
   1893 narrowed part.
   1894 
   1895 If a region is active, export that region.
   1896 
   1897 A non-nil optional argument ASYNC means the process should happen
   1898 asynchronously.  The resulting file should be accessible through
   1899 the `org-export-stack' interface.
   1900 
   1901 When optional argument SUBTREEP is non-nil, export the sub-tree
   1902 at point, extracting information from the headline properties
   1903 first.
   1904 
   1905 When optional argument VISIBLE-ONLY is non-nil, don't export
   1906 contents of hidden elements.
   1907 
   1908 When optional argument BODY-ONLY is non-nil, only write code
   1909 between \"\\begin{document}\" and \"\\end{document}\".
   1910 
   1911 EXT-PLIST, when provided, is a property list with external
   1912 parameters overriding Org default settings, but still inferior to
   1913 file-local settings.
   1914 
   1915 Return output file's name."
   1916   (interactive)
   1917   (let ((outfile (org-export-output-file-name ".texi" subtreep))
   1918 	(org-export-coding-system org-texinfo-coding-system))
   1919     (org-export-to-file 'texinfo outfile
   1920       async subtreep visible-only body-only ext-plist)))
   1921 
   1922 (defun org-texinfo-export-to-texinfo-batch ()
   1923   "Export Org file INFILE to Texinfo file OUTFILE, in batch mode.
   1924 Overwrites existing output file.
   1925 Usage: emacs -batch -f org-texinfo-export-to-texinfo-batch INFILE OUTFILE"
   1926   (or noninteractive (user-error "Batch mode use only"))
   1927   (let ((infile (pop command-line-args-left))
   1928 	(outfile (pop command-line-args-left))
   1929 	(org-export-coding-system org-texinfo-coding-system)
   1930         (make-backup-files nil))
   1931     (unless (file-readable-p infile)
   1932       (message "File `%s' not readable" infile)
   1933       (kill-emacs 1))
   1934     (with-temp-buffer
   1935       (insert-file-contents infile)
   1936       (org-export-to-file 'texinfo outfile))))
   1937 
   1938 ;;;###autoload
   1939 (defun org-texinfo-export-to-info
   1940     (&optional async subtreep visible-only body-only ext-plist)
   1941   "Export current buffer to Texinfo then process through to INFO.
   1942 
   1943 If narrowing is active in the current buffer, only export its
   1944 narrowed part.
   1945 
   1946 If a region is active, export that region.
   1947 
   1948 A non-nil optional argument ASYNC means the process should happen
   1949 asynchronously.  The resulting file should be accessible through
   1950 the `org-export-stack' interface.
   1951 
   1952 When optional argument SUBTREEP is non-nil, export the sub-tree
   1953 at point, extracting information from the headline properties
   1954 first.
   1955 
   1956 When optional argument VISIBLE-ONLY is non-nil, don't export
   1957 contents of hidden elements.
   1958 
   1959 When optional argument BODY-ONLY is non-nil, only write code
   1960 between \"\\begin{document}\" and \"\\end{document}\".
   1961 
   1962 EXT-PLIST, when provided, is a property list with external
   1963 parameters overriding Org default settings, but still inferior to
   1964 file-local settings.
   1965 
   1966 When optional argument PUB-DIR is set, use it as the publishing
   1967 directory.
   1968 
   1969 Return INFO file's name."
   1970   (interactive)
   1971   (let ((outfile (org-export-output-file-name ".texi" subtreep))
   1972 	(org-export-coding-system org-texinfo-coding-system))
   1973     (org-export-to-file 'texinfo outfile
   1974       async subtreep visible-only body-only ext-plist
   1975       #'org-texinfo-compile)))
   1976 
   1977 ;;;###autoload
   1978 (defun org-texinfo-publish-to-texinfo (plist filename pub-dir)
   1979   "Publish an org file to Texinfo.
   1980 
   1981 FILENAME is the filename of the Org file to be published.  PLIST
   1982 is the property list for the given project.  PUB-DIR is the
   1983 publishing directory.
   1984 
   1985 Return output file name."
   1986   (org-publish-org-to 'texinfo filename ".texi" plist pub-dir))
   1987 
   1988 ;;;###autoload
   1989 (defun org-texinfo-convert-region-to-texinfo ()
   1990   "Assume the current region has Org syntax, and convert it to Texinfo.
   1991 This can be used in any buffer.  For example, you can write an
   1992 itemized list in Org syntax in an Texinfo buffer and use this
   1993 command to convert it."
   1994   (interactive)
   1995   (org-export-replace-region-by 'texinfo))
   1996 
   1997 (defun org-texinfo-compile (file)
   1998   "Compile a texinfo file.
   1999 
   2000 FILE is the name of the file being compiled.  Processing is done
   2001 through the command specified in `org-texinfo-info-process',
   2002 which see.  Output is redirected to \"*Org INFO Texinfo Output*\"
   2003 buffer.
   2004 
   2005 Return INFO file name or an error if it couldn't be produced."
   2006   (message "Processing Texinfo file %s..." file)
   2007   (let* ((log-name "*Org INFO Texinfo Output*")
   2008 	 (log (get-buffer-create log-name))
   2009 	 (output
   2010 	  (org-compile-file file org-texinfo-info-process "info"
   2011 			    (format "See %S for details" log-name)
   2012 			    log)))
   2013     (when org-texinfo-remove-logfiles
   2014       (let ((base (file-name-sans-extension output)))
   2015 	(dolist (ext org-texinfo-logfiles-extensions)
   2016 	  (let ((file (concat base "." ext)))
   2017 	    (when (file-exists-p file) (delete-file file))))))
   2018     (message "Process completed.")
   2019     output))
   2020 
   2021 (defun org-texinfo-supports-math-p ()
   2022   "Return t if the installed version of Texinfo supports \"@math\".
   2023 
   2024 Once computed, the results remain cached."
   2025   (unless (boundp 'org-texinfo-supports-math--cache)
   2026     (setq org-texinfo-supports-math--cache
   2027           (let ((math-example "1 + 1 = 2"))
   2028             (let* ((input-file
   2029                     (make-temp-file "test" nil ".info"))
   2030                    (input-content
   2031                     (concat (format "@setfilename %s" input-file) "\n"
   2032                             "@node Top" "\n"
   2033                             (format "@displaymath{%s}" math-example) "\n")))
   2034               (with-temp-file input-file
   2035                 (insert input-content))
   2036               (let* ((output-file (org-texinfo-compile input-file))
   2037                      (output-content (with-temp-buffer
   2038                                        (insert-file-contents output-file)
   2039                                        (buffer-string))))
   2040                 (let ((result (string-match-p (regexp-quote math-example)
   2041                                               output-content)))
   2042                   (delete-file input-file)
   2043                   (delete-file output-file)
   2044                   (if result t nil)))))))
   2045   org-texinfo-supports-math--cache)
   2046 
   2047 (provide 'ox-texinfo)
   2048 
   2049 ;; Local variables:
   2050 ;; generated-autoload-file: "org-loaddefs.el"
   2051 ;; End:
   2052 
   2053 ;;; ox-texinfo.el ends here