config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

ox-texinfo.el (78346B)


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