]> code.delx.au - gnu-emacs/blobdiff - lisp/org/ox-texinfo.el
Update copyright year to 2015
[gnu-emacs] / lisp / org / ox-texinfo.el
index a55f298ddc3e2ccd47c76da6ceef5cf21a1e547b..0c81b62061812e82f594e4076b77cbb6e6c86a2b 100644 (file)
@@ -1,6 +1,6 @@
 ;;; ox-texinfo.el --- Texinfo Back-End for Org Export Engine
 
-;; Copyright (C) 2012-2014 Free Software Foundation, Inc.
+;; Copyright (C) 2012-2015 Free Software Foundation, Inc.
 ;; Author: Jonathan Leech-Pepin <jonathan.leechpepin at gmail dot com>
 ;; Keywords: outlines, hypermedia, calendar, wp
 
 
 ;;; Commentary:
 ;;
-;; This library implements a Texinfo back-end for Org generic
-;; exporter.
-;;
-;; To test it, run
-;;
-;;   M-: (org-export-to-buffer 'texinfo "*Test Texinfo*") RET
-;;
-;; in an Org mode buffer then switch to the buffer to see the Texinfo
-;; export.  See ox.el for more details on how this exporter works.
-;;
-
-;; It introduces nine new buffer keywords: "TEXINFO_CLASS",
-;; "TEXINFO_FILENAME", "TEXINFO_HEADER", "TEXINFO_POST_HEADER",
-;; "TEXINFO_DIR_CATEGORY", "TEXINFO_DIR_TITLE", "TEXINFO_DIR_DESC"
-;; "SUBTITLE" and "SUBAUTHOR".
-
-;;
-;; It introduces 1 new headline property keywords:
-;; "TEXINFO_MENU_TITLE" for optional menu titles.
-;;
-;; To include inline code snippets (for example for generating @kbd{}
-;; and @key{} commands), the following export-snippet keys are
-;; accepted:
-;;
-;;     texinfo
-;;     info
-;;
-;; You can add them for export snippets via any of the below:
-;;
-;;    (add-to-list 'org-export-snippet-translation-alist
-;;                 '("info" . "texinfo"))
-;;
+;; See Org manual for details.
 
 ;;; Code:
 
@@ -70,8 +39,8 @@
     (center-block . org-texinfo-center-block)
     (clock . org-texinfo-clock)
     (code . org-texinfo-code)
-    (comment . org-texinfo-comment)
-    (comment-block . org-texinfo-comment-block)
+    (comment . (lambda (&rest args) ""))
+    (comment-block . (lambda (&rest args) ""))
     (drawer . org-texinfo-drawer)
     (dynamic-block . org-texinfo-dynamic-block)
     (entity . org-texinfo-entity)
   :export-block "TEXINFO"
   :filters-alist
   '((:filter-headline . org-texinfo-filter-section-blank-lines)
+    (:filter-parse-tree . org-texinfo--normalize-headlines)
     (:filter-section . org-texinfo-filter-section-blank-lines))
   :menu-entry
   '(?i "Export to Texinfo"
        ((?t "As TEXI file" org-texinfo-export-to-texinfo)
        (?i "As INFO file" org-texinfo-export-to-info)))
   :options-alist
-  '((:texinfo-filename "TEXINFO_FILENAME" nil org-texinfo-filename t)
+  '((:texinfo-filename "TEXINFO_FILENAME" nil nil t)
     (:texinfo-class "TEXINFO_CLASS" nil org-texinfo-default-class t)
     (:texinfo-header "TEXINFO_HEADER" nil nil newline)
     (:texinfo-post-header "TEXINFO_POST_HEADER" nil nil newline)
     (:subauthor "SUBAUTHOR" nil nil newline)
     (:texinfo-dircat "TEXINFO_DIR_CATEGORY" nil nil t)
     (:texinfo-dirtitle "TEXINFO_DIR_TITLE" nil nil t)
-    (:texinfo-dirdesc "TEXINFO_DIR_DESC" nil nil t)))
+    (:texinfo-dirdesc "TEXINFO_DIR_DESC" nil nil t)
+    (:texinfo-printed-title "TEXINFO_PRINTED_TITLE" nil nil t)))
 
 
 \f
   :package-version '(Org . "8.0")
   :group 'org-export)
 
-;;; Preamble
-
-(defcustom org-texinfo-filename ""
-  "Default filename for Texinfo output."
-  :group 'org-export-texinfo
-  :type '(string :tag "Export Filename"))
+;;;; Preamble
 
 (defcustom org-texinfo-coding-system nil
   "Default document encoding for Texinfo output.
@@ -162,19 +128,42 @@ If `nil' it will default to `buffer-file-coding-system'."
 
 (defcustom org-texinfo-classes
   '(("info"
-     "\\input texinfo    @c -*- texinfo -*-"
+     "@documentencoding AUTO\n@documentlanguage AUTO"
      ("@chapter %s" . "@unnumbered %s")
      ("@section %s" . "@unnumberedsec %s")
      ("@subsection %s" . "@unnumberedsubsec %s")
      ("@subsubsection %s" . "@unnumberedsubsubsec %s")))
   "Alist of Texinfo classes and associated header and structure.
-If #+Texinfo_CLASS is set in the buffer, use its value and the
+If #+TEXINFO_CLASS is set in the buffer, use its value and the
 associated information.  Here is the structure of each cell:
 
   \(class-name
     header-string
-    \(numbered-section . unnumbered-section\)
-    ...\)
+    \(numbered-section . unnumbered-section)
+    ...)
+
+
+The header string
+-----------------
+
+The header string is inserted in the header of the generated
+document, right after \"@setfilename\" and \"@settitle\"
+commands.
+
+If it contains the special string
+
+  \"@documentencoding AUTO\"
+
+\"AUTO\" will be replaced with an appropriate coding system.  See
+`org-texinfo-coding-system' for more information.  Likewise, if
+the string contains the special string
+
+  \"@documentlanguage AUTO\"
+
+\"AUTO\" will be replaced with the language defined in the
+buffer, through #+LANGUAGE keyword, or globally, with
+`org-export-default-language', which see.
+
 
 The sectioning structure
 ------------------------
@@ -186,10 +175,12 @@ section string and will be replaced by the title of the section.
 
 Instead of a list of sectioning commands, you can also specify
 a function name.  That function will be called with two
-parameters, the \(reduced) level of the headline, and a predicate
+parameters, the reduced) level of the headline, and a predicate
 non-nil when the headline should be numbered.  It must return
 a format string in which the section title will be added."
   :group 'org-export-texinfo
+  :version "24.4"
+  :package-version '(Org . "8.2")
   :type '(repeat
          (list (string :tag "Texinfo class")
                (string :tag "Texinfo header")
@@ -200,7 +191,7 @@ a format string in which the section title will be added."
                               (string :tag "unnumbered"))
                         (function :tag "Hook computing sectioning"))))))
 
-;;; Headline
+;;;; Headline
 
 (defcustom org-texinfo-format-headline-function 'ignore
   "Function to format headline text.
@@ -230,22 +221,16 @@ order to reproduce the default set-up:
   :group 'org-export-texinfo
   :type 'function)
 
-;;; Node listing (menu)
+;;;; Node listing (menu)
 
 (defcustom org-texinfo-node-description-column 32
-  "Column at which to start the description in the node
-  listings.
-
+  "Column at which to start the description in the node listings.
 If a node title is greater than this length, the description will
 be placed after the end of the title."
   :group 'org-export-texinfo
   :type 'integer)
 
-;;; Footnotes
-;;
-;; Footnotes are inserted directly
-
-;;; Timestamps
+;;;; Timestamps
 
 (defcustom org-texinfo-active-timestamp-format "@emph{%s}"
   "A printf format string to be applied to active timestamps."
@@ -262,14 +247,14 @@ be placed after the end of the title."
   :group 'org-export-texinfo
   :type 'string)
 
-;;; Links
+;;;; Links
 
 (defcustom org-texinfo-link-with-unknown-path-format "@indicateurl{%s}"
   "Format string for links with unknown path type."
   :group 'org-export-texinfo
   :type 'string)
 
-;;; Tables
+;;;; Tables
 
 (defcustom org-texinfo-tables-verbatim nil
   "When non-nil, tables are exported verbatim."
@@ -285,12 +270,14 @@ When nil, no transformation is made."
   :group 'org-export-texinfo
   :type '(choice
          (string :tag "Format string")
-         (const :tag "No formatting")))
+         (const :tag "No formatting" nil)))
 
 (defcustom org-texinfo-def-table-markup "@samp"
-  "Default setting for @table environments.")
+  "Default setting for @table environments."
+  :group 'org-export-texinfo
+  :type 'string)
 
-;;; Text markup
+;;;; Text markup
 
 (defcustom org-texinfo-text-markup-alist '((bold . "@strong{%s}")
                                           (code . code)
@@ -314,7 +301,7 @@ returned as-is."
   :type 'alist
   :options '(bold code italic verbatim comment))
 
-;;; Drawers
+;;;; Drawers
 
 (defcustom org-texinfo-format-drawer-function
   (lambda (name contents) contents)
@@ -329,10 +316,10 @@ The function should return the string to be exported.
 The default function simply returns the value of CONTENTS."
   :group 'org-export-texinfo
   :version "24.4"
-  :package-version '(Org . "8.3")
+  :package-version '(Org . "8.2")
   :type 'function)
 
-;;; Inlinetasks
+;;;; Inlinetasks
 
 (defcustom org-texinfo-format-inlinetask-function 'ignore
   "Function called to format an inlinetask in Texinfo code.
@@ -348,7 +335,7 @@ The function must accept six parameters:
 The function should return the string to be exported.
 
 For example, the variable could be set to the following function
-in order to mimic default behaviour:
+in order to mimic default behavior:
 
 \(defun org-texinfo-format-inlinetask \(todo type priority name tags contents\)
 \"Format an inline task element for Texinfo export.\"
@@ -368,18 +355,13 @@ in order to mimic default behaviour:
   :group 'org-export-texinfo
   :type 'function)
 
-;;; Src blocks
-;;
-;; Src Blocks are example blocks, except for LISP
+;;;; Compilation
 
-;;; Compilation
-
-(defcustom org-texinfo-info-process
-  '("makeinfo %f")
+(defcustom org-texinfo-info-process '("makeinfo %f")
   "Commands to process a Texinfo file to an INFO file.
 This is list of strings, each of them will be given to the shell
 as a command.  %f in the command will be replaced by the full
-file name, %b by the file base name \(i.e without extension) and
+file name, %b by the file base name (i.e without extension) and
 %o by the base directory of the file."
   :group 'org-export-texinfo
   :type '(repeat :tag "Shell command sequence"
@@ -401,12 +383,13 @@ set `org-texinfo-logfiles-extensions'."
   :group 'org-export-latex
   :type 'boolean)
 
-
 ;;; Constants
+
 (defconst org-texinfo-max-toc-depth 4
-  "Maximum depth for creation of detailed menu listings.  Beyond
-  this depth Texinfo will not recognize the nodes and will cause
-  errors.  Left as a constant in case this value ever changes.")
+  "Maximum depth for creation of detailed menu listings.
+Beyond this depth, Texinfo will not recognize the nodes and will
+cause errors.  Left as a constant in case this value ever
+changes.")
 
 (defconst org-texinfo-supported-coding-systems
   '("US-ASCII" "UTF-8" "ISO-8859-15" "ISO-8859-1" "ISO-8859-2" "koi8-r" "koi8-u")
@@ -423,6 +406,31 @@ If two strings share the same prefix (e.g. \"ISO-8859-1\" and
   (let ((blanks (make-string 2 ?\n)))
     (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline)))
 
+(defun org-texinfo--normalize-headlines (tree back-end info)
+  "Normalize headlines in TREE.
+
+BACK-END is the symbol specifying back-end used for export. INFO
+is a plist used as a communication channel.
+
+Make sure every headline in TREE contains a section, since those
+are required to install a menu.  Also put exactly one blank line
+at the end of each section.
+
+Return new tree."
+  (org-element-map tree 'headline
+    (lambda (hl)
+      (org-element-put-property hl :post-blank 1)
+      (let ((contents (org-element-contents hl)))
+       (when contents
+         (let ((first (org-element-map contents '(headline section)
+                        #'identity info t)))
+           (unless (eq (org-element-type first) 'section)
+             (apply #'org-element-set-contents
+                    hl
+                    (cons `(section (:parent ,hl)) contents)))))))
+    info)
+  tree)
+
 (defun org-texinfo--find-verb-separator (s)
   "Return a character not used in string S.
 This is used to choose a separator for constructs like \\verb."
@@ -431,18 +439,6 @@ This is used to choose a separator for constructs like \\verb."
          when (not (string-match (regexp-quote (char-to-string c)) s))
          return (char-to-string c))))
 
-(defun org-texinfo--make-option-string (options)
-  "Return a comma separated string of keywords and values.
-OPTIONS is an alist where the key is the options keyword as
-a string, and the value a list containing the keyword value, or
-nil."
-  (mapconcat (lambda (pair)
-              (concat (first pair)
-                      (when (> (length (second pair)) 0)
-                        (concat "=" (second pair)))))
-            options
-            ","))
-
 (defun org-texinfo--text-markup (text markup)
   "Format TEXT depending on MARKUP text markup.
 See `org-texinfo-text-markup-alist' for details."
@@ -470,207 +466,44 @@ See `org-texinfo-text-markup-alist' for details."
      ;; Else use format string.
      (t (format fmt text)))))
 
-(defun org-texinfo--get-node (headline info)
-  "Return node entry associated to HEADLINE.
-INFO is a plist used as a communication channel."
-  (let ((menu-title (org-export-get-alt-title headline info)))
-    (org-texinfo--sanitize-menu
-     (replace-regexp-in-string
-      "%" "%%"
-      (if menu-title (org-export-data menu-title info)
-       (org-texinfo--sanitize-headline
-        (org-element-property :title headline) info))))))
-
-;;; Headline sanitizing
-
-(defun org-texinfo--sanitize-headline (headline info)
-  "Remove all formatting from the text of a headline for use in
-  node and menu listing."
-  (mapconcat 'identity
-            (org-texinfo--sanitize-headline-contents headline info) " "))
-
-(defun org-texinfo--sanitize-headline-contents (headline info)
-  "Retrieve the content of the headline.
-
-Any content that can contain further formatting is checked
-recursively, to ensure that nested content is also properly
-retrieved."
-  (loop for contents in headline append
-       (cond
-        ;; already a string
-        ((stringp contents)
-         (list (replace-regexp-in-string " $" "" contents)))
-        ;; Is exported as-is (value)
-        ((org-element-map contents '(verbatim code)
-           (lambda (value) (org-element-property :value value)) info))
-        ;; Has content and recurse into the content
-        ((org-element-contents contents)
-         (org-texinfo--sanitize-headline-contents
-          (org-element-contents contents) info)))))
-
-;;; Menu sanitizing
-
-(defun org-texinfo--sanitize-menu (title)
-  "Remove invalid characters from TITLE for use in menus and
-nodes.
-
-Based on Texinfo specifications, the following must be removed:
-@ { } ( ) : . ,"
-  (replace-regexp-in-string "[@{}():,.]" "" title))
-
-;;; Content sanitizing
+(defun org-texinfo--get-node (blob info)
+  "Return node or anchor associated to BLOB.
+BLOB is an element or object.  INFO is a plist used as
+a communication channel.  The function guarantees the node or
+anchor name is unique."
+  (let ((cache (plist-get info :texinfo-node-cache)))
+    (or (cdr (assq blob cache))
+       (let ((name
+              (org-texinfo--sanitize-node
+               (case (org-element-type blob)
+                 (headline
+                  (org-export-data (org-export-get-alt-title blob info) info))
+                 ((radio-target target) (org-element-property :value blob))
+                 (otherwise (or (org-element-property :name blob) ""))))))
+         ;; Ensure NAME is unique.
+         (while (rassoc name cache) (setq name (concat name "x")))
+         (plist-put info :texinfo-node-cache (cons (cons blob name) cache))
+         name))))
+
+;;;; Menu sanitizing
+
+(defun org-texinfo--sanitize-node (title)
+  "Bend string TITLE to node line requirements.
+Trim string and collapse multiple whitespace characters as they
+are not significant.  Also remove the following characters: @
+{ } ( ) : . ,"
+  (replace-regexp-in-string
+   "[:,.]" ""
+   (replace-regexp-in-string
+    "\\`(\\(.*)\\)" "[\\1"
+    (org-trim (replace-regexp-in-string "[ \t]\\{2,\\}" " " title)))))
+
+;;;; Content sanitizing
 
 (defun org-texinfo--sanitize-content (text)
-  "Ensure characters are properly escaped when used in headlines or blocks.
-
-Escape characters are: @ { }"
-  (replace-regexp-in-string "\\\([@{}]\\\)" "@\\1" text))
-
-;;; Menu creation
-
-(defun org-texinfo--build-menu (tree level info &optional detailed)
-  "Create the @menu/@end menu information from TREE at headline
-level LEVEL.
-
-TREE contains the parse-tree to work with, either of the entire
-document or of a specific parent headline.  LEVEL indicates what
-level of headlines to look at when generating the menu.  INFO is
-a plist containing contextual information.
-
-Detailed determines whether to build a single level of menu, or
-recurse into all children as well."
-  (let ((menu (org-texinfo--generate-menu-list tree level info))
-       output text-menu)
-    (cond
-     (detailed
-      ;; Looping is done within the menu generation.
-      (setq text-menu (org-texinfo--generate-detailed menu level info)))
-     (t
-      (setq text-menu (org-texinfo--generate-menu-items menu info))))
-    (when text-menu
-      (setq output (org-texinfo--format-menu text-menu))
-      (mapconcat 'identity output "\n"))))
-
-(defun org-texinfo--generate-detailed (menu level info)
-  "Generate a detailed listing of all subheadings within MENU starting at LEVEL.
-
-MENU is the parse-tree to work with.  LEVEL is the starting level
-for the menu headlines and from which recursion occurs.  INFO is
-a plist containing contextual information."
-  (when level
-    (let ((max-depth (min org-texinfo-max-toc-depth
-                     (plist-get info :headline-levels))))
-      (when (> max-depth level)
-       (loop for headline in menu append
-             (let* ((title (org-texinfo--menu-headlines headline info))
-                    ;; Create list of menu entries for the next level
-                    (sublist (org-texinfo--generate-menu-list
-                              headline (1+ level) info))
-                    ;; Generate the menu items for that level.  If
-                    ;; there are none omit that heading completely,
-                    ;; otherwise join the title to it's related entries.
-                    (submenu (if (org-texinfo--generate-menu-items sublist info)
-                                 (append (list title)
-                                         (org-texinfo--generate-menu-items sublist info))
-                               'nil))
-                    ;; Start the process over the next level down.
-                    (recursion (org-texinfo--generate-detailed sublist (1+ level) info)))
-               (setq recursion (append submenu recursion))
-               recursion))))))
-
-(defun org-texinfo--generate-menu-list (tree level info)
-  "Generate the list of headlines that are within a given level
-of the tree for further formatting.
-
-TREE is the parse-tree containing the headlines.  LEVEL is the
-headline level to generate a list of.  INFO is a plist holding
-contextual information."
-  (org-element-map tree 'headline
-    (lambda (head)
-      (and (= (org-export-get-relative-level head info) level)
-          ;; Do not take note of footnotes or copying headlines.
-          (not (org-element-property :COPYING head))
-          (not (org-element-property :footnote-section-p head))
-          ;; Collect headline.
-          head))
-    info))
-
-(defun org-texinfo--generate-menu-items (items info)
-  "Generate a list of headline information from the listing ITEMS.
-
-ITEMS is a list of the headlines to be converted into entries.
-INFO is a plist containing contextual information.
-
-Returns a list containing the following information from each
-headline: length, title, description.  This is used to format the
-menu using `org-texinfo--format-menu'."
-  (loop for headline in items collect
-       (let* ((menu-title (org-texinfo--sanitize-menu
-                           (org-export-data
-                            (org-export-get-alt-title headline info)
-                            info)))
-              (title (org-texinfo--sanitize-menu
-                      (org-texinfo--sanitize-headline
-                       (org-element-property :title headline) info)))
-              (descr (org-export-data
-                      (org-element-property :DESCRIPTION headline)
-                      info))
-              (menu-entry (if (string= "" menu-title) title menu-title))
-              (len (length menu-entry))
-              (output (list len menu-entry descr)))
-         output)))
-
-(defun org-texinfo--menu-headlines (headline info)
-  "Retrieve the title from HEADLINE.
-
-INFO is a plist holding contextual information.
-
-Return the headline as a list of (length title description) with
-length of -1 and nil description.  This is used in
-`org-texinfo--format-menu' to identify headlines as opposed to
-entries."
-  (let ((title (org-export-data
-               (org-element-property :title headline) info)))
-    (list -1 title 'nil)))
-
-(defun org-texinfo--format-menu (text-menu)
-  "Format the TEXT-MENU items to be properly printed in the menu.
-
-Each entry in the menu should be provided as (length title
-description).
-
-Headlines in the detailed menu are given length -1 to ensure they
-are never confused with other entries.  They also have no
-description.
-
-Other menu items are output as:
-    Title::     description
-
-With the spacing between :: and description based on the length
-of the longest menu entry."
-
-  (let (output)
-    (setq output
-          (mapcar (lambda (name)
-                    (let* ((title   (nth 1 name))
-                           (desc    (nth 2 name))
-                           (length  (nth 0 name))
-                          (column  (max
-                                    ;;6 is "* " ":: " for inserted text
-                                    length
-                                    (-
-                                     org-texinfo-node-description-column
-                                     6)))
-                          (spacing (- column length)
-                                   ))
-                      (if (> length -1)
-                          (concat "* " title "::  "
-                                  (make-string spacing ?\s)
-                                  (if desc
-                                      (concat desc)))
-                        (concat "\n" title "\n"))))
-                 text-menu))
-    output))
+  "Escape special characters in string TEXT.
+Special characters are: @ { }"
+  (replace-regexp-in-string "[@{}]" "@\\&" text))
 
 ;;; Template
 
@@ -678,145 +511,127 @@ of the longest menu entry."
   "Return complete document string after Texinfo conversion.
 CONTENTS is the transcoded contents string.  INFO is a plist
 holding export options."
-  (let* ((title (org-export-data (plist-get info :title) info))
-        (info-filename (or (plist-get info :texinfo-filename)
-                           (file-name-nondirectory
-                            (org-export-output-file-name ".info"))))
-        (author (org-export-data (plist-get info :author) info))
-        (lang (org-export-data (plist-get info :language) info))
-        (texinfo-header (plist-get info :texinfo-header))
-        (texinfo-post-header (plist-get info :texinfo-post-header))
-        (subtitle (plist-get info :subtitle))
-        (subauthor (plist-get info :subauthor))
-        (class (plist-get info :texinfo-class))
-        (header (nth 1 (assoc class org-texinfo-classes)))
-        (copying
-         (org-element-map (plist-get info :parse-tree) 'headline
-           (lambda (hl) (and (org-element-property :COPYING hl) hl)) info t))
-        (dircat (plist-get info :texinfo-dircat))
-        (dirtitle (plist-get info :texinfo-dirtitle))
-        (dirdesc (plist-get info :texinfo-dirdesc))
-        ;; Spacing to align description (column 32 - 3 for `* ' and
-        ;; `.' in text.
-        (dirspacing (- 29 (length dirtitle)))
-        (menu (org-texinfo-make-menu info 'main))
-        (detail-menu (org-texinfo-make-menu info 'detailed)))
+  (let ((title (org-export-data (plist-get info :title) info))
+       ;; Copying data is the contents of the first headline in
+       ;; parse tree with a non-nil copying property.
+       (copying (org-element-map (plist-get info :parse-tree) 'headline
+                  (lambda (hl)
+                    (and (org-not-nil (org-element-property :COPYING hl))
+                         (org-element-contents hl)))
+                  info t)))
     (concat
-     ;; Header
-     header "\n"
+     "\\input texinfo    @c -*- texinfo -*-\n"
      "@c %**start of header\n"
-     ;; Filename and Title
-     "@setfilename " info-filename "\n"
-     "@settitle " title "\n"
-     ;; Coding system.
-     (format
-      "@documentencoding %s\n"
-      (catch 'coding-system
-       (let ((case-fold-search t)
-             (name (symbol-name (or org-texinfo-coding-system
-                                    buffer-file-coding-system))))
-         (dolist (system org-texinfo-supported-coding-systems "UTF-8")
-           (when (org-string-match-p (regexp-quote system) name)
-             (throw 'coding-system system))))))
-     "\n"
-     (format "@documentlanguage %s\n" lang)
-     "\n\n"
-     "@c Version and Contact Info\n"
-     "@set AUTHOR " author "\n"
-
-     ;; Additional Header Options set by `#+TEXINFO_HEADER
-     (if texinfo-header
-        (concat "\n"
-                texinfo-header
-                "\n"))
-
-     "@c %**end of header\n"
-     "@finalout\n"
-     "\n\n"
-
-     ;; Additional Header Options set by #+TEXINFO_POST_HEADER
-     (if texinfo-post-header
-        (concat "\n"
-                texinfo-post-header
-                "\n"))
-
-     ;; Copying
-     "@copying\n"
-     ;; Only export the content of the headline, do not need the
-     ;; initial headline.
-     (org-export-data (nth 2 copying) info)
-     "@end copying\n"
-     "\n\n"
-
-     ;; Info directory information
-     ;; Only supply if both title and category are provided
-     (if (and dircat dirtitle)
+     (let ((file (or (plist-get info :texinfo-filename)
+                    (let ((f (plist-get info :output-file)))
+                      (and f (concat (file-name-sans-extension f) ".info"))))))
+       (and file (format "@setfilename %s\n" file)))
+     (format "@settitle %s\n" title)
+     ;; Insert class-defined header.
+     (org-element-normalize-string
+      (let ((header (nth 1 (assoc (plist-get info :texinfo-class)
+                                 org-texinfo-classes)))
+           (coding
+            (catch 'coding-system
+              (let ((case-fold-search t)
+                    (name (symbol-name (or org-texinfo-coding-system
+                                           buffer-file-coding-system))))
+                (dolist (system org-texinfo-supported-coding-systems "UTF-8")
+                  (when (org-string-match-p (regexp-quote system) name)
+                    (throw 'coding-system system))))))
+           (language (plist-get info :language))
+           (case-fold-search nil))
+       ;; Auto coding system.
+       (replace-regexp-in-string
+        "^@documentencoding \\(AUTO\\)$"
+        coding
+        (replace-regexp-in-string
+         "^@documentlanguage \\(AUTO\\)$" language header t nil 1) t nil 1)))
+     ;; Additional header options set by #+TEXINFO_HEADER.
+     (let ((texinfo-header (plist-get info :texinfo-header)))
+       (and texinfo-header (org-element-normalize-string texinfo-header)))
+     "@c %**end of header\n\n"
+     ;; Additional options set by #+TEXINFO_POST_HEADER.
+     (let ((texinfo-post-header (plist-get info :texinfo-post-header)))
+       (and texinfo-post-header
+           (org-element-normalize-string texinfo-post-header)))
+     ;; Copying.
+     (and copying
+         (format "@copying\n%s@end copying\n\n"
+                 (org-element-normalize-string
+                  (org-export-data copying info))))
+     ;; Info directory information.  Only supply if both title and
+     ;; category are provided.
+     (let ((dircat (plist-get info :texinfo-dircat))
+          (dirtitle
+           (let ((title (plist-get info :texinfo-dirtitle)))
+             (and title
+                  (string-match "^\\(?:\\* \\)?\\(.*?\\)\\(\\.\\)?$" title)
+                  (format "* %s." (match-string 1 title))))))
+       (when (and dircat dirtitle)
         (concat "@dircategory " dircat "\n"
                 "@direntry\n"
-                "* " dirtitle "."
-                (make-string dirspacing ?\s)
-                dirdesc "\n"
-                "@end direntry\n"))
-     "\n\n"
-
+                (let ((dirdesc
+                       (let ((desc (plist-get info :texinfo-dirdesc)))
+                         (cond ((not desc) nil)
+                               ((org-string-match-p "\\.$" desc) desc)
+                               (t (concat desc "."))))))
+                  (if dirdesc (format "%-23s %s" dirtitle dirdesc) dirtitle))
+                "\n"
+                "@end direntry\n\n")))
      ;; Title
+     "@finalout\n"
      "@titlepage\n"
-     "@title " title "\n\n"
-     (if subtitle
-        (concat "@subtitle " subtitle "\n"))
-     "@author " author "\n"
-     (if subauthor
-        (concat subauthor "\n"))
-     "\n"
-     "@c The following two commands start the copyright page.\n"
-     "@page\n"
-     "@vskip 0pt plus 1filll\n"
-     "@insertcopying\n"
+     (format "@title %s\n" (or (plist-get info :texinfo-printed-title) title))
+     (let ((subtitle (plist-get info :subtitle)))
+       (and subtitle
+           (org-element-normalize-string
+            (replace-regexp-in-string "^" "@subtitle " subtitle))))
+     (when (plist-get info :with-author)
+       (concat
+       ;; Primary author.
+       (let ((author (org-string-nw-p
+                      (org-export-data (plist-get info :author) info)))
+             (email (and (plist-get info :with-email)
+                         (org-string-nw-p
+                          (org-export-data (plist-get info :email) info)))))
+         (cond ((and author email)
+                (format "@author %s (@email{%s})\n" author email))
+               (author (format "@author %s\n" author))
+               (email (format "@author @email{%s}\n" email))))
+       ;; Other authors.
+       (let ((subauthor (plist-get info :subauthor)))
+         (and subauthor
+              (org-element-normalize-string
+               (replace-regexp-in-string "^" "@author " subauthor))))))
+     (and copying "@page\n@vskip 0pt plus 1filll\n@insertcopying\n")
      "@end titlepage\n\n"
-     "@c Output the table of contents at the beginning.\n"
-     "@contents\n\n"
-
+     ;; Table of contents.
+     (and (plist-get info :with-toc) "@contents\n\n")
      ;; Configure Top Node when not for Tex
      "@ifnottex\n"
      "@node Top\n"
-     "@top " title " Manual\n"
-     "@insertcopying\n"
+     (format "@top %s\n" title)
+     (and copying "@insertcopying\n")
      "@end ifnottex\n\n"
-
-     ;; Do not output menus if they are empty
-     (if menu
-        ;; Menu
-        (concat "@menu\n"
-                menu
-                "\n\n"
-                ;; Detailed Menu
-                (if detail-menu
-                    (concat "@detailmenu\n"
-                            " --- The Detailed Node Listing ---\n"
-                            detail-menu
-                            "\n\n"
-                            "@end detailmenu\n"))
-                "@end menu\n"))
-     "\n\n"
-
-     ;; Document's body.
-     contents
+     ;; Menu.
+     (org-texinfo-make-menu (plist-get info :parse-tree) info 'master)
      "\n"
+     ;; Document's body.
+     contents "\n"
      ;; Creator.
-     (let ((creator-info (plist-get info :with-creator)))
-       (cond
-       ((not creator-info) "")
-       ((eq creator-info 'comment)
-        (format "@c %s\n" (plist-get info :creator)))
-       (t (concat (plist-get info :creator) "\n"))))
+     (case (plist-get info :with-creator)
+       ((nil) nil)
+       (comment (format "@c %s\n" (plist-get info :creator)))
+       (otherwise (concat (plist-get info :creator) "\n")))
      ;; Document end.
-     "\n@bye")))
+     "@bye")))
 
 
 \f
 ;;; Transcode Functions
 
-;;; Bold
+;;;; Bold
 
 (defun org-texinfo-bold (bold contents info)
   "Transcode BOLD from Org to Texinfo.
@@ -824,7 +639,7 @@ CONTENTS is the text with bold markup.  INFO is a plist holding
 contextual information."
   (org-texinfo--text-markup contents 'bold))
 
-;;; Center Block
+;;;; Center Block
 
 (defun org-texinfo-center-block (center-block contents info)
   "Transcode a CENTER-BLOCK element from Org to Texinfo.
@@ -832,7 +647,7 @@ CONTENTS holds the contents of the block.  INFO is a plist used
 as a communication channel."
   contents)
 
-;;; Clock
+;;;; Clock
 
 (defun org-texinfo-clock (clock contents info)
   "Transcode a CLOCK element from Org to Texinfo.
@@ -849,7 +664,7 @@ information."
                     (and time (format " (%s)" time)))))
    "@*"))
 
-;;; Code
+;;;; Code
 
 (defun org-texinfo-code (code contents info)
   "Transcode a CODE object from Org to Texinfo.
@@ -857,23 +672,7 @@ CONTENTS is nil.  INFO is a plist used as a communication
 channel."
   (org-texinfo--text-markup (org-element-property :value code) 'code))
 
-;;; Comment
-
-(defun org-texinfo-comment (comment contents info)
-  "Transcode a COMMENT object from Org to Texinfo.
-CONTENTS is the text in the comment.  INFO is a plist holding
-contextual information."
-  (org-texinfo--text-markup (org-element-property :value comment) 'comment))
-
-;;; Comment Block
-
-(defun org-texinfo-comment-block (comment-block contents info)
-  "Transcode a COMMENT-BLOCK object from Org to Texinfo.
-CONTENTS is the text within the block.  INFO is a plist holding
-contextual information."
-  (format "@ignore\n%s@end ignore" (org-element-property :value comment-block)))
-
-;;; Drawer
+;;;; Drawer
 
 (defun org-texinfo-drawer (drawer contents info)
   "Transcode a DRAWER element from Org to Texinfo.
@@ -884,15 +683,15 @@ holding contextual information."
                          name contents)))
     output))
 
-;;; Dynamic Block
+;;;; Dynamic Block
 
 (defun org-texinfo-dynamic-block (dynamic-block contents info)
   "Transcode a DYNAMIC-BLOCK element from Org to Texinfo.
 CONTENTS holds the contents of the block.  INFO is a plist
-holding contextual information.  See `org-export-data'."
+holding contextual information."
   contents)
 
-;;; Entity
+;;;; Entity
 
 (defun org-texinfo-entity (entity contents info)
   "Transcode an ENTITY object from Org to Texinfo.
@@ -901,7 +700,7 @@ contextual information."
   (let ((ent (org-element-property :latex entity)))
     (if (org-element-property :latex-math-p entity) (format "@math{%s}" ent) ent)))
 
-;;; Example Block
+;;;; Example Block
 
 (defun org-texinfo-example-block (example-block contents info)
   "Transcode an EXAMPLE-BLOCK element from Org to Texinfo.
@@ -910,7 +709,7 @@ information."
   (format "@verbatim\n%s@end verbatim"
          (org-export-format-code-default example-block info)))
 
-;;; Export Block
+;;;; Export Block
 
 (defun org-texinfo-export-block (export-block contents info)
   "Transcode a EXPORT-BLOCK element from Org to Texinfo.
@@ -918,7 +717,7 @@ CONTENTS is nil.  INFO is a plist holding contextual information."
   (when (string= (org-element-property :type export-block) "TEXINFO")
     (org-remove-indentation (org-element-property :value export-block))))
 
-;;; Export Snippet
+;;;; Export Snippet
 
 (defun org-texinfo-export-snippet (export-snippet contents info)
   "Transcode a EXPORT-SNIPPET object from Org to Texinfo.
@@ -926,7 +725,7 @@ CONTENTS is nil.  INFO is a plist holding contextual information."
   (when (eq (org-export-snippet-backend export-snippet) 'texinfo)
     (org-element-property :value export-snippet)))
 
-;;; Fixed Width
+;;;; Fixed Width
 
 (defun org-texinfo-fixed-width (fixed-width contents info)
   "Transcode a FIXED-WIDTH element from Org to Texinfo.
@@ -936,8 +735,7 @@ CONTENTS is nil.  INFO is a plist holding contextual information."
           (org-texinfo--sanitize-content
            (org-element-property :value fixed-width)))))
 
-;;; Footnote Reference
-;;
+;;;; Footnote Reference
 
 (defun org-texinfo-footnote-reference (footnote contents info)
   "Create a footnote reference for FOOTNOTE.
@@ -948,7 +746,7 @@ plist holding contextual information."
     (format "@footnote{%s}"
            (org-trim (org-export-data def info)))))
 
-;;; Headline
+;;;; Headline
 
 (defun org-texinfo-headline (headline contents info)
   "Transcode a HEADLINE element from Org to Texinfo.
@@ -958,66 +756,29 @@ holding contextual information."
         (level (org-export-get-relative-level headline info))
         (numberedp (org-export-numbered-headline-p headline info))
         (class-sectioning (assoc class org-texinfo-classes))
-        ;; Find the index type, if any
+        ;; Find the index type, if any.
         (index (org-element-property :INDEX headline))
-        ;; Check if it is an appendix
-        (appendix (org-element-property :APPENDIX headline))
-        ;; Retrieve headline text
-        (text (org-texinfo--sanitize-headline
-               (org-element-property :title headline) info))
         ;; Create node info, to insert it before section formatting.
-        ;; Use custom menu title if present
+        ;; Use custom menu title if present.
         (node (format "@node %s\n" (org-texinfo--get-node headline info)))
-        ;; Menus must be generated with first child, otherwise they
-        ;; will not nest properly
-        (menu (let* ((first (org-export-first-sibling-p headline info))
-                     (parent (org-export-get-parent-headline headline))
-                     (title (org-texinfo--sanitize-headline
-                             (org-element-property :title parent) info))
-                     heading listing
-                     (tree (plist-get info :parse-tree)))
-                (if first
-                    (org-element-map (plist-get info :parse-tree) 'headline
-                      (lambda (ref)
-                        (if (member title (org-element-property :title ref))
-                            (push ref heading)))
-                      info t))
-                (setq listing (org-texinfo--build-menu
-                               (car heading) level info))
-                (if listing
-                    (setq listing (replace-regexp-in-string
-                                   "%" "%%" listing)
-                          listing (format
-                                   "\n@menu\n%s\n@end menu\n\n" listing))
-                  'nil)))
         ;; Section formatting will set two placeholders: one for the
         ;; title and the other for the contents.
         (section-fmt
-         (let ((sec (if (and (symbolp (nth 2 class-sectioning))
-                             (fboundp (nth 2 class-sectioning)))
-                        (funcall (nth 2 class-sectioning) level numberedp)
-                      (nth (1+ level) class-sectioning))))
-           (cond
-            ;; No section available for that LEVEL.
-            ((not sec) nil)
-            ;; Section format directly returned by a function.
-            ((stringp sec) sec)
-            ;; (numbered-section . unnumbered-section)
-            ((not (consp (cdr sec)))
+         (if (org-not-nil (org-element-property :APPENDIX headline))
+             "@appendix %s\n%s"
+           (let ((sec (if (and (symbolp (nth 2 class-sectioning))
+                               (fboundp (nth 2 class-sectioning)))
+                          (funcall (nth 2 class-sectioning) level numberedp)
+                        (nth (1+ level) class-sectioning))))
              (cond
-              ;;If an index, always unnumbered
-              (index
-               (concat menu node (cdr sec) "\n%s"))
-              (appendix
-               (concat menu node (replace-regexp-in-string
-                                  "unnumbered"
-                                  "appendix"
-                                  (cdr sec)) "\n%s"))
-               ;; Otherwise number as needed.
-              (t
-               (concat menu node
-                       (funcall
-                        (if numberedp #'car #'cdr) sec) "\n%s")))))))
+              ;; No section available for that LEVEL.
+              ((not sec) nil)
+              ;; Section format directly returned by a function.
+              ((stringp sec) sec)
+              ;; (numbered-section . unnumbered-section)
+              ((not (consp (cdr sec)))
+               (concat (if (or index (not numberedp)) (cdr sec) (car sec))
+                       "\n%s"))))))
         (todo
          (and (plist-get info :with-todo-keywords)
               (let ((todo (org-element-property :todo-keyword headline)))
@@ -1027,100 +788,54 @@ holding contextual information."
                    (org-export-get-tags headline info)))
         (priority (and (plist-get info :with-priority)
                        (org-element-property :priority headline)))
-        ;; Create the headline text along with a no-tag version.  The
-        ;; latter is required to remove tags from table of contents.
-        (full-text (org-texinfo--sanitize-content
-                    (if (not (eq org-texinfo-format-headline-function 'ignore))
-                        ;; User-defined formatting function.
-                        (funcall org-texinfo-format-headline-function
-                                 todo todo-type priority text tags)
-                      ;; Default formatting.
-                      (concat
-                       (when todo
-                         (format "@strong{%s} " todo))
-                       (when priority (format "@emph{#%s} " priority))
-                       text
-                       (when tags
-                         (format " :%s:"
-                                 (mapconcat 'identity tags ":")))))))
-        (full-text-no-tag
-         (org-texinfo--sanitize-content
-          (if (not (eq org-texinfo-format-headline-function 'ignore))
-              ;; User-defined formatting function.
-              (funcall org-texinfo-format-headline-function
-                       todo todo-type priority text nil)
-            ;; Default formatting.
-            (concat
-             (when todo (format "@strong{%s} " todo))
-             (when priority (format "@emph{#%c} " priority))
-             text))))
-        (pre-blanks
-         (make-string (org-element-property :pre-blank headline) 10)))
+        (text (org-export-data (org-element-property :title headline) info))
+        (full-text (if (not (eq org-texinfo-format-headline-function 'ignore))
+                       ;; User-defined formatting function.
+                       (funcall org-texinfo-format-headline-function
+                                todo todo-type priority text tags)
+                     ;; Default formatting.
+                     (concat
+                      (when todo
+                        (format "@strong{%s} " todo))
+                      (when priority (format "@emph{#%s} " priority))
+                      text
+                      (when tags
+                        (format " :%s:"
+                                (mapconcat 'identity tags ":"))))))
+        (contents (if (org-string-nw-p contents) (concat "\n" contents) "")))
     (cond
      ;; Case 1: This is a footnote section: ignore it.
      ((org-element-property :footnote-section-p headline) nil)
      ;; Case 2: This is the `copying' section: ignore it
      ;;         This is used elsewhere.
-     ((org-element-property :COPYING headline) nil)
+     ((org-not-nil (org-element-property :COPYING headline)) nil)
      ;; Case 3: An index.  If it matches one of the known indexes,
      ;;         print it as such following the contents, otherwise
      ;;         print the contents and leave the index up to the user.
      (index
-      (format
-       section-fmt full-text
-       (concat pre-blanks contents "\n"
-              (if (member index '("cp" "fn" "ky" "pg" "tp" "vr"))
-                  (concat "@printindex " index)))))
+      (concat node
+             (format
+              section-fmt
+              full-text
+              (concat contents
+                      (and (member index '("cp" "fn" "ky" "pg" "tp" "vr"))
+                           (concat "\n@printindex " index))))))
      ;; Case 4: This is a deep sub-tree: export it as a list item.
      ;;         Also export as items headlines for which no section
      ;;         format has been found.
      ((or (not section-fmt) (org-export-low-level-p headline info))
       ;; Build the real contents of the sub-tree.
-      (let ((low-level-body
-            (concat
-             ;; If the headline is the first sibling, start a list.
-             (when (org-export-first-sibling-p headline info)
-               (format "@%s\n" (if numberedp 'enumerate 'itemize)))
-             ;; Itemize headline
-             "@item\n" full-text "\n" pre-blanks contents)))
-       ;; If headline is not the last sibling simply return
-       ;; LOW-LEVEL-BODY.  Otherwise, also close the list, before any
-       ;; blank line.
-       (if (not (org-export-last-sibling-p headline info)) low-level-body
-         (replace-regexp-in-string
-          "[ \t\n]*\\'"
-          (format "\n@end %s" (if numberedp 'enumerate 'itemize))
-          low-level-body))))
+      (concat (and (org-export-first-sibling-p headline info)
+                  (format "@%s\n" (if numberedp 'enumerate 'itemize)))
+             "@item\n" full-text "\n"
+             contents
+             (if (org-export-last-sibling-p headline info)
+                 (format "@end %s" (if numberedp 'enumerate 'itemize))
+               "\n")))
      ;; Case 5: Standard headline.  Export it as a section.
-     (t
-      (cond
-       ((not (and tags (eq (plist-get info :with-tags) 'not-in-toc)))
-       ;; Regular section.  Use specified format string.
-       (format (replace-regexp-in-string "%]" "%%]" section-fmt) full-text
-               (concat pre-blanks contents)))
-       ((string-match "\\`@\\(.*?\\){" section-fmt)
-       ;; If tags should be removed from table of contents, insert
-       ;; title without tags as an alternative heading in sectioning
-       ;; command.
-       (format (replace-match (concat (match-string 1 section-fmt) "[%s]")
-                              nil nil section-fmt 1)
-               ;; Replace square brackets with parenthesis since
-               ;; square brackets are not supported in optional
-               ;; arguments.
-               (replace-regexp-in-string
-                "\\[" "("
-                (replace-regexp-in-string
-                 "\\]" ")"
-                 full-text-no-tag))
-               full-text
-               (concat pre-blanks contents)))
-       (t
-       ;; Impossible to add an alternative heading.  Fallback to
-       ;; regular sectioning format string.
-       (format (replace-regexp-in-string "%]" "%%]" section-fmt) full-text
-               (concat pre-blanks contents))))))))
-
-;;; Inline Src Block
+     (t (concat node (format section-fmt full-text contents))))))
+
+;;;; Inline Src Block
 
 (defun org-texinfo-inline-src-block (inline-src-block contents info)
   "Transcode an INLINE-SRC-BLOCK element from Org to Texinfo.
@@ -1130,7 +845,7 @@ contextual information."
         (separator (org-texinfo--find-verb-separator code)))
     (concat "@verb{" separator code separator "}")))
 
-;;; Inlinetask
+;;;; Inlinetask
 
 (defun org-texinfo-inlinetask (inlinetask contents info)
   "Transcode an INLINETASK element from Org to Texinfo.
@@ -1163,7 +878,7 @@ holding contextual information."
                        "\n")
                full-title contents)))))
 
-;;; Italic
+;;;; Italic
 
 (defun org-texinfo-italic (italic contents info)
   "Transcode ITALIC from Org to Texinfo.
@@ -1171,18 +886,18 @@ CONTENTS is the text with italic markup.  INFO is a plist holding
 contextual information."
   (org-texinfo--text-markup contents 'italic))
 
-;;; Item
+;;;; Item
 
 (defun org-texinfo-item (item contents info)
   "Transcode an ITEM element from Org to Texinfo.
 CONTENTS holds the contents of the item.  INFO is a plist holding
 contextual information."
-  (let* ((tag (org-element-property :tag item))
-        (desc (org-export-data tag info)))
-    (concat "\n@item " (if tag desc) "\n"
-           (and contents (org-trim contents)) "\n")))
+  (format "@item%s\n%s"
+         (let ((tag (org-element-property :tag item)))
+           (if tag (concat " " (org-export-data tag info)) ""))
+         (or contents "")))
 
-;;; Keyword
+;;;; Keyword
 
 (defun org-texinfo-keyword (keyword contents info)
   "Transcode a KEYWORD element from Org to Texinfo.
@@ -1198,14 +913,14 @@ CONTENTS is nil.  INFO is a plist holding contextual information."
      ((string= key "TINDEX") (format "@tindex %s" value))
      ((string= key "VINDEX") (format "@vindex %s" value)))))
 
-;;; Line Break
+;;;; Line Break
 
 (defun org-texinfo-line-break (line-break contents info)
   "Transcode a LINE-BREAK object from Org to Texinfo.
 CONTENTS is nil.  INFO is a plist holding contextual information."
   "@*\n")
 
-;;; Link
+;;;; Link
 
 (defun org-texinfo-link (link desc info)
   "Transcode a LINK object from Org to Texinfo.
@@ -1220,60 +935,72 @@ INFO is a plist holding contextual information.  See
         (path (cond
                ((member type '("http" "https" "ftp"))
                 (concat type ":" raw-path))
-               ((string= type "file")
-                (if (file-name-absolute-p raw-path)
-                    (concat "file://" (expand-file-name raw-path))
-                  (concat "file://" raw-path)))
+               ((and (string= type "file") (file-name-absolute-p raw-path))
+                (concat "file:" raw-path))
                (t raw-path)))
-        (email (if (string= type "mailto")
-                   (let ((text (replace-regexp-in-string
-                                "@" "@@" raw-path)))
-                     (concat text (if desc (concat "," desc))))))
         protocol)
     (cond
-     ;; Links pointing to a headline: Find destination and build
-     ;; appropriate referencing command.
-     ((member type '("custom-id" "id"))
-      (let ((destination (org-export-resolve-id-link link info)))
+     ((equal type "radio")
+      (let ((destination (org-export-resolve-radio-link link info)))
+       (if (not destination) desc
+         (format "@ref{%s,,%s}"
+                 (org-texinfo--get-node destination info)
+                 desc))))
+     ((member type '("custom-id" "id" "fuzzy"))
+      (let ((destination
+            (if (equal type "fuzzy")
+                (org-export-resolve-fuzzy-link link info)
+              (org-export-resolve-id-link link info))))
        (case (org-element-type destination)
+         ((nil)
+          (format org-texinfo-link-with-unknown-path-format
+                  (org-texinfo--sanitize-content path)))
          ;; Id link points to an external file.
          (plain-text
           (if desc (format "@uref{file://%s,%s}" destination desc)
             (format "@uref{file://%s}" destination)))
-         ;; LINK points to a headline.  Use the headline as the NODE target
          (headline
           (format "@ref{%s,%s}"
                   (org-texinfo--get-node destination info)
-                  (or desc "")))
+                  (cond
+                   (desc)
+                   ((org-export-numbered-headline-p destination info)
+                    (org-export-data
+                     (org-element-property :title destination) info))
+                   (t
+                    (mapconcat
+                     #'number-to-string
+                     (org-export-get-headline-number destination info) ".")))))
          (otherwise
-          (let ((path (org-export-solidify-link-text path)))
-            (if (not desc) (format "@ref{%s}" path)
-              (format "@ref{%s,,%s}" path desc)))))))
-     ((member type '("info"))
+          (let ((topic
+                 (or desc
+                     (if (and (eq (org-element-type destination) 'headline)
+                              (not (org-export-numbered-headline-p
+                                    destination info)))
+                         (org-export-data
+                          (org-element-property :title destination) info))
+                     (let ((n (org-export-get-ordinal destination info)))
+                       (cond
+                        ((not n) nil)
+                        ((integerp n) n)
+                        (t (mapconcat #'number-to-string n ".")))))))
+            (when topic
+              (format "@ref{%s,,%s}"
+                      (org-texinfo--get-node destination info)
+                      topic)))))))
+     ((equal type "info")
       (let* ((info-path (split-string path "[:#]"))
             (info-manual (car info-path))
             (info-node (or (cadr info-path) "top"))
             (title (or desc "")))
        (format "@ref{%s,%s,,%s,}" info-node title info-manual)))
-     ((member type '("fuzzy"))
-      (let ((destination (org-export-resolve-fuzzy-link link info)))
-       (case (org-element-type destination)
-         ;; Id link points to an external file.
-         (plain-text
-          (if desc (format "@uref{file://%s,%s}" destination desc)
-            (format "@uref{file://%s}" destination)))
-         ;; LINK points to a headline.  Use the headline as the NODE target
-         (headline
-          (format "@ref{%s,%s}"
-                  (org-texinfo--get-node destination info)
-                  (or desc "")))
-         (otherwise
-          (let ((path (org-export-solidify-link-text path)))
-            (if (not desc) (format "@ref{%s}" path)
-              (format "@ref{%s,,%s}" path desc)))))))
-     ;; Special case for email addresses
-     (email
-      (format "@email{%s}" email))
+     ((string= type "mailto")
+      (format "@email{%s}"
+             (concat (org-texinfo--sanitize-content path)
+                     (and desc (concat "," desc)))))
+     ((let ((protocol (nth 2 (assoc type org-link-protocols))))
+       (and (functionp protocol)
+            (funcall protocol (org-link-unescape path) desc 'texinfo))))
      ;; External link with a description part.
      ((and path desc) (format "@uref{%s,%s}" path desc))
      ;; External link without a description part.
@@ -1282,27 +1009,97 @@ INFO is a plist holding contextual information.  See
      (t (format org-texinfo-link-with-unknown-path-format desc)))))
 
 
-;;; Menu
-
-(defun org-texinfo-make-menu (info level)
-  "Create the menu for inclusion in the texifo document.
-
-INFO is the parsed buffer that contains the headlines.  LEVEL
-determines whether to make the main menu, or the detailed menu.
-
-This is only used for generating the primary menu.  In-Node menus
-are generated directly."
-  (let ((parse (plist-get info :parse-tree)))
-    (cond
-     ;; Generate the main menu
-     ((eq level 'main) (org-texinfo--build-menu parse 1 info))
-     ;; Generate the detailed (recursive) menu
-     ((eq level 'detailed)
-      ;; Requires recursion
-      ;;(org-texinfo--build-detailed-menu parse top info)
-      (org-texinfo--build-menu parse 1 info 'detailed)))))
-
-;;; Paragraph
+;;;; Menu
+
+(defun org-texinfo-make-menu (scope info &optional master)
+  "Create the menu for inclusion in the Texinfo document.
+
+SCOPE is a headline or a full parse tree.  INFO is the
+communication channel, as a plist.
+
+When optional argument MASTER is non-nil, generate a master menu,
+including detailed node listing."
+  (let ((menu (org-texinfo--build-menu scope info)))
+    (when (org-string-nw-p menu)
+      (org-element-normalize-string
+       (format
+       "@menu\n%s@end menu"
+       (concat menu
+               (when master
+                 (let ((detailmenu
+                        (org-texinfo--build-menu
+                         scope info
+                         (let ((toc-depth (plist-get info :with-toc)))
+                           (if (wholenump toc-depth) toc-depth
+                             org-texinfo-max-toc-depth)))))
+                   (when (org-string-nw-p detailmenu)
+                     (concat "\n@detailmenu\n"
+                             "--- The Detailed Node Listing ---\n\n"
+                             detailmenu
+                             "@end detailmenu\n"))))))))))
+
+(defun org-texinfo--build-menu (scope info &optional level)
+  "Build menu for entries within SCOPE.
+SCOPE is a headline or a full parse tree.  INFO is a plist
+containing contextual information.  When optional argument LEVEL
+is an integer, build the menu recursively, down to this depth."
+  (cond
+   ((not level)
+    (org-texinfo--format-entries (org-texinfo--menu-entries scope info) info))
+   ((zerop level) nil)
+   (t
+    (org-element-normalize-string
+     (mapconcat
+      (lambda (h)
+       (let ((entries (org-texinfo--menu-entries h info)))
+         (when entries
+           (concat
+            (format "%s\n\n%s\n"
+                    (org-export-data (org-export-get-alt-title h info) info)
+                    (org-texinfo--format-entries entries info))
+            (org-texinfo--build-menu h info (1- level))))))
+      (org-texinfo--menu-entries scope info) "")))))
+
+(defun org-texinfo--format-entries (entries info)
+  "Format all direct menu entries in SCOPE, as a string.
+SCOPE is either a headline or a full Org document.  INFO is
+a plist containing contextual information."
+  (org-element-normalize-string
+   (mapconcat
+    (lambda (h)
+      (let* ((title (org-export-data
+                    (org-export-get-alt-title h info) info))
+            (node (org-texinfo--get-node h info))
+            (entry (concat "* " title ":"
+                           (if (string= title node) ":"
+                             (concat " " node ". "))))
+            (desc (org-element-property :DESCRIPTION h)))
+       (if (not desc) entry
+         (format (format "%%-%ds %%s" org-texinfo-node-description-column)
+                 entry desc))))
+    entries "\n")))
+
+(defun org-texinfo--menu-entries (scope info)
+  "List direct children in SCOPE needing a menu entry.
+SCOPE is a headline or a full parse tree.  INFO is a plist
+holding contextual information."
+  (let* ((cache (or (plist-get info :texinfo-entries-cache)
+                   (plist-get (plist-put info :texinfo-entries-cache
+                                         (make-hash-table :test #'eq))
+                              :texinfo-entries-cache)))
+        (cached-entries (gethash scope cache 'no-cache)))
+    (if (not (eq cached-entries 'no-cache)) cached-entries
+      (puthash scope
+              (org-element-map (org-element-contents scope) 'headline
+                (lambda (h)
+                  (and (not (org-not-nil (org-element-property :COPYING h)))
+                       (not (org-element-property :footnote-section-p h))
+                       (not (org-export-low-level-p h info))
+                       h))
+                info nil 'headline)
+              cache))))
+
+;;;; Paragraph
 
 (defun org-texinfo-paragraph (paragraph contents info)
   "Transcode a PARAGRAPH element from Org to Texinfo.
@@ -1310,32 +1107,27 @@ CONTENTS is the contents of the paragraph, as a string.  INFO is
 the plist used as a communication channel."
   contents)
 
-;;; Plain List
+;;;; Plain List
 
 (defun org-texinfo-plain-list (plain-list contents info)
   "Transcode a PLAIN-LIST element from Org to Texinfo.
 CONTENTS is the contents of the list.  INFO is a plist holding
 contextual information."
   (let* ((attr (org-export-read-attribute :attr_texinfo plain-list))
-        (indic (or (plist-get attr :indic)
-                   org-texinfo-def-table-markup))
-        (type (org-element-property :type plain-list))
+        (indic (or (plist-get attr :indic) org-texinfo-def-table-markup))
         (table-type (plist-get attr :table-type))
-        ;; Ensure valid texinfo table type.
-        (table-type (if (member table-type '("ftable" "vtable")) table-type
-                      "table"))
+        (type (org-element-property :type plain-list))
         (list-type (cond
                     ((eq type 'ordered) "enumerate")
                     ((eq type 'unordered) "itemize")
-                    ((eq type 'descriptive) table-type))))
-    (format "@%s%s\n@end %s"
-           (if (eq type 'descriptive)
-               (concat list-type " " indic)
-             list-type)
+                    ((member table-type '("ftable" "vtable")) table-type)
+                    (t "table"))))
+    (format "@%s\n%s@end %s"
+           (if (eq type 'descriptive) (concat list-type " " indic) list-type)
            contents
            list-type)))
 
-;;; Plain Text
+;;;; Plain Text
 
 (defun org-texinfo-plain-text (text info)
   "Transcode a TEXT string from Org to Texinfo.
@@ -1366,7 +1158,7 @@ contextual information."
     ;; Return value.
     output))
 
-;;; Planning
+;;;; Planning
 
 (defun org-texinfo-planning (planning contents info)
   "Transcode a PLANNING element from Org to Texinfo.
@@ -1402,7 +1194,7 @@ information."
     " ")
    "@*"))
 
-;;; Property Drawer
+;;;; Property Drawer
 
 (defun org-texinfo-property-drawer (property-drawer contents info)
   "Transcode a PROPERTY-DRAWER element from Org to Texinfo.
@@ -1412,7 +1204,7 @@ information."
   ;; lines nonetheless.
   "")
 
-;;; Quote Block
+;;;; Quote Block
 
 (defun org-texinfo-quote-block (quote-block contents info)
   "Transcode a QUOTE-BLOCK element from Org to Texinfo.
@@ -1424,7 +1216,7 @@ holding contextual information."
                                  (format " %s" title)))))
     (format "%s\n%s@end quotation" start-quote contents)))
 
-;;; Quote Section
+;;;; Quote Section
 
 (defun org-texinfo-quote-section (quote-section contents info)
   "Transcode a QUOTE-SECTION element from Org to Texinfo.
@@ -1433,7 +1225,7 @@ CONTENTS is nil.  INFO is a plist holding contextual information."
                (org-element-property :value quote-section))))
     (when value (format "@verbatim\n%s@end verbatim" value))))
 
-;;; Radio Target
+;;;; Radio Target
 
 (defun org-texinfo-radio-target (radio-target text info)
   "Transcode a RADIO-TARGET object from Org to Texinfo.
@@ -1444,15 +1236,17 @@ contextual information."
           (org-element-property :value radio-target))
          text))
 
-;;; Section
+;;;; Section
 
 (defun org-texinfo-section (section contents info)
   "Transcode a SECTION element from Org to Texinfo.
 CONTENTS holds the contents of the section.  INFO is a plist
 holding contextual information."
-  contents)
+  (concat contents
+         (let ((parent (org-export-get-parent-headline section)))
+           (and parent (org-texinfo-make-menu parent info)))))
 
-;;; Special Block
+;;;; Special Block
 
 (defun org-texinfo-special-block (special-block contents info)
   "Transcode a SPECIAL-BLOCK element from Org to Texinfo.
@@ -1460,34 +1254,26 @@ CONTENTS holds the contents of the block.  INFO is a plist used
 as a communication channel."
   contents)
 
-;;; Src Block
+;;;; Src Block
 
 (defun org-texinfo-src-block (src-block contents info)
   "Transcode a SRC-BLOCK element from Org to Texinfo.
 CONTENTS holds the contents of the item.  INFO is a plist holding
 contextual information."
-  (let* ((lang (org-element-property :language src-block))
-        (lisp-p (string-match-p "lisp" lang))
-        (src-contents (org-texinfo--sanitize-content
-                  (org-export-format-code-default src-block info))))
-    (cond
-     ;; Case 1.  Lisp Block
-     (lisp-p
-      (format "@lisp\n%s@end lisp"
-             src-contents))
-     ;; Case 2.  Other blocks
-     (t
-      (format "@example\n%s@end example"
-             src-contents)))))
+  (let ((lispp (org-string-match-p "lisp"
+                                  (org-element-property :language src-block)))
+       (code (org-texinfo--sanitize-content
+              (org-export-format-code-default src-block info))))
+    (format (if lispp "@lisp\n%s@end lisp" "@example\n%s@end example") code)))
 
-;;; Statistics Cookie
+;;;; Statistics Cookie
 
 (defun org-texinfo-statistics-cookie (statistics-cookie contents info)
   "Transcode a STATISTICS-COOKIE object from Org to Texinfo.
 CONTENTS is nil.  INFO is a plist holding contextual information."
   (org-element-property :value statistics-cookie))
 
-;;; Subscript
+;;;; Subscript
 
 (defun org-texinfo-subscript (subscript contents info)
   "Transcode a SUBSCRIPT object from Org to Texinfo.
@@ -1495,7 +1281,7 @@ CONTENTS is the contents of the object.  INFO is a plist holding
 contextual information."
   (format "@math{_%s}" contents))
 
-;;; Superscript
+;;;; Superscript
 
 (defun org-texinfo-superscript (superscript contents info)
   "Transcode a SUPERSCRIPT object from Org to Texinfo.
@@ -1503,96 +1289,47 @@ CONTENTS is the contents of the object.  INFO is a plist holding
 contextual information."
   (format "@math{^%s}" contents))
 
-;;; Table
-;;
-;; `org-texinfo-table' is the entry point for table transcoding.  It
-;; takes care of tables with a "verbatim" attribute.  Otherwise, it
-;; delegates the job to either `org-texinfo-table--table.el-table' or
-;; `org-texinfo-table--org-table' functions, depending of the type of
-;; the table.
-;;
-;; `org-texinfo-table--align-string' is a subroutine used to build
-;; alignment string for Org tables.
+;;;; Table
 
 (defun org-texinfo-table (table contents info)
   "Transcode a TABLE element from Org to Texinfo.
 CONTENTS is the contents of the table.  INFO is a plist holding
 contextual information."
-  (cond
-   ;; Case 1: verbatim table.
-   ((or org-texinfo-tables-verbatim
-       (let ((attr (mapconcat 'identity
-                              (org-element-property :attr_latex table)
-                              " ")))
-         (and attr (string-match "\\<verbatim\\>" attr))))
-    (format "@verbatim \n%s\n@end verbatim"
-           ;; Re-create table, without affiliated keywords.
-           (org-trim
-            (org-element-interpret-data
-             `(table nil ,@(org-element-contents table))))))
-   ;; Case 2: table.el table.  Convert it using appropriate tools.
-   ((eq (org-element-property :type table) 'table.el)
-    (org-texinfo-table--table.el-table table contents info))
-   ;; Case 3: Standard table.
-   (t (org-texinfo-table--org-table table contents info))))
+  (if (eq (org-element-property :type table) 'table.el)
+      (format "@verbatim\n%s@end verbatim"
+             (org-element-normalize-string
+              (org-element-property :value table)))
+    (let* ((col-width (org-export-read-attribute :attr_texinfo table :columns))
+          (columns
+           (if col-width (format "@columnfractions %s" col-width)
+             (org-texinfo-table-column-widths table info))))
+      (format "@multitable %s\n%s@end multitable"
+             columns
+             contents))))
 
 (defun org-texinfo-table-column-widths (table info)
   "Determine the largest table cell in each column to process alignment.
-
 TABLE is the table element to transcode.  INFO is a plist used as
 a communication channel."
-  (let* ((rows (org-element-map table 'table-row 'identity info))
-        (collected (loop for row in rows collect
-                         (org-element-map row 'table-cell 'identity info)))
-        (number-cells (length (car collected)))
-        cells counts)
-    (loop for row in collected do
-         (push (mapcar (lambda (ref)
-                         (let* ((start (org-element-property :contents-begin ref))
-                                (end (org-element-property :contents-end ref))
-                                (length (- end start)))
-                           length)) row) cells))
-    (setq cells (org-remove-if 'null cells))
-    (push (loop for count from 0 to (- number-cells 1) collect
-               (loop for item in cells collect
-                     (nth count item))) counts)
-    (mapconcat (lambda (size)
-                (make-string size ?a)) (mapcar (lambda (ref)
-                                                 (apply 'max `(,@ref))) (car counts))
-                "} {")))
-
-(defun org-texinfo-table--org-table (table contents info)
-  "Return appropriate Texinfo code for an Org table.
-
-TABLE is the table type element to transcode.  CONTENTS is its
-contents, as a string.  INFO is a plist used as a communication
-channel.
-
-This function assumes TABLE has `org' as its `:type' attribute."
-  (let* ((attr (org-export-read-attribute :attr_texinfo table))
-        (col-width (plist-get attr :columns))
-        (columns (if col-width
-                     (format "@columnfractions %s"
-                             col-width)
-                   (format "{%s}"
-                           (org-texinfo-table-column-widths
-                            table info)))))
-    ;; Prepare the final format string for the table.
-    (cond
-     ;; Longtable.
-     ;; Others.
-     (t (concat
-        (format "@multitable %s\n%s@end multitable"
-                columns
-                contents))))))
-
-(defun org-texinfo-table--table.el-table (table contents info)
-  "Returns nothing.
-
-Rather than return an invalid table, nothing is returned."
-  'nil)
-
-;;; Table Cell
+  (let ((widths (make-vector (cdr (org-export-table-dimensions table info)) 0)))
+    (org-element-map table 'table-row
+      (lambda (row)
+       (let ((idx 0))
+         (org-element-map row 'table-cell
+           (lambda (cell)
+             ;; Length of the cell in the original buffer is only an
+             ;; approximation of the length of the cell in the
+             ;; output.  It can sometimes fail (e.g. it considers
+             ;; "/a/" being larger than "ab").
+             (let ((w (- (org-element-property :contents-end cell)
+                         (org-element-property :contents-begin cell))))
+               (aset widths idx (max w (aref widths idx))))
+             (incf idx))
+           info)))
+      info)
+    (format "{%s}" (mapconcat (lambda (w) (make-string w ?a)) widths "} {"))))
+
+;;;; Table Cell
 
 (defun org-texinfo-table-cell (table-cell contents info)
   "Transcode a TABLE-CELL element from Org to Texinfo.
@@ -1609,7 +1346,7 @@ a communication channel."
            contents)
          (when (org-export-get-next-element table-cell info) "\n@tab ")))
 
-;;; Table Row
+;;;; Table Row
 
 (defun org-texinfo-table-row (table-row contents info)
   "Transcode a TABLE-ROW element from Org to Texinfo.
@@ -1618,21 +1355,15 @@ a communication channel."
   ;; Rules are ignored since table separators are deduced from
   ;; borders of the current row.
   (when (eq (org-element-property :type table-row) 'standard)
-   (let ((rowgroup-tag
-         (cond
-          ;; Case 1: Belongs to second or subsequent rowgroup.
-          ((not (= 1 (org-export-table-row-group table-row info)))
-           "@item ")
-          ;; Case 2: Row is from first rowgroup.  Table has >=1 rowgroups.
-          ((org-export-table-has-header-p
-            (org-export-get-parent-table table-row) info)
-           "@headitem ")
-          ;; Case 3: Row is from first and only row group.
-          (t "@item "))))
-     (when (eq (org-element-property :type table-row) 'standard)
-       (concat rowgroup-tag contents "\n")))))
-
-;;; Target
+    (let ((rowgroup-tag
+          (if (and (= 1 (org-export-table-row-group table-row info))
+                   (org-export-table-has-header-p
+                    (org-export-get-parent-table table-row) info))
+              "@headitem "
+            "@item ")))
+      (concat rowgroup-tag contents "\n"))))
+
+;;;; Target
 
 (defun org-texinfo-target (target contents info)
   "Transcode a TARGET object from Org to Texinfo.
@@ -1641,7 +1372,7 @@ information."
   (format "@anchor{%s}"
          (org-export-solidify-link-text (org-element-property :value target))))
 
-;;; Timestamp
+;;;; Timestamp
 
 (defun org-texinfo-timestamp (timestamp contents info)
   "Transcode a TIMESTAMP object from Org to Texinfo.
@@ -1656,7 +1387,7 @@ information."
        (format org-texinfo-inactive-timestamp-format value))
       (t (format org-texinfo-diary-timestamp-format value)))))
 
-;;; Verbatim
+;;;; Verbatim
 
 (defun org-texinfo-verbatim (verbatim contents info)
   "Transcode a VERBATIM object from Org to Texinfo.
@@ -1664,26 +1395,13 @@ CONTENTS is nil.  INFO is a plist used as a communication
 channel."
   (org-texinfo--text-markup (org-element-property :value verbatim) 'verbatim))
 
-;;; Verse Block
+;;;; Verse Block
 
 (defun org-texinfo-verse-block (verse-block contents info)
   "Transcode a VERSE-BLOCK element from Org to Texinfo.
 CONTENTS is verse block contents. INFO is a plist holding
 contextual information."
-  ;; In a verse environment, add a line break to each newline
-  ;; character and change each white space at beginning of a line
-  ;; into a space of 1 em.  Also change each blank line with
-  ;; a vertical space of 1 em.
-  (progn
-    (setq contents (replace-regexp-in-string
-                   "^ *\\\\\\\\$" "\\\\vspace*{1em}"
-                   (replace-regexp-in-string
-                    "\\(\\\\\\\\\\)?[ \t]*\n" " \\\\\\\\\n" contents)))
-    (while (string-match "^[ \t]+" contents)
-      (let ((new-str (format "\\hspace*{%dem}"
-                            (length (match-string 0 contents)))))
-       (setq contents (replace-match new-str nil t contents))))
-    (format "\\begin{verse}\n%s\\end{verse}" contents)))
+  (format "@display\n%s@end display" contents))
 
 \f
 ;;; Interactive functions
@@ -1797,29 +1515,21 @@ Return INFO file name or an error if it couldn't be produced."
         errors)
     (message (format "Processing Texinfo file %s..." file))
     (save-window-excursion
-      (cond
-       ;; A function is provided: Apply it.
-       ((functionp org-texinfo-info-process)
-       (funcall org-texinfo-info-process (shell-quote-argument file)))
-       ;; A list is provided: Replace %b, %f and %o with appropriate
-       ;; values in each command before applying it.  Output is
-       ;; redirected to "*Org INFO Texinfo Output*" buffer.
-       ((consp org-texinfo-info-process)
-       (let ((outbuf (get-buffer-create "*Org INFO Texinfo Output*")))
-         (mapc
-          (lambda (command)
-            (shell-command
-             (replace-regexp-in-string
-              "%b" (shell-quote-argument base-name)
-              (replace-regexp-in-string
-               "%f" (shell-quote-argument full-name)
-               (replace-regexp-in-string
-                "%o" (shell-quote-argument out-dir) command t t) t t) t t)
-             outbuf))
-          org-texinfo-info-process)
-         ;; Collect standard errors from output buffer.
-         (setq errors (org-texinfo-collect-errors outbuf))))
-       (t (error "No valid command to process to Info")))
+      ;; Replace %b, %f and %o with appropriate values in each command
+      ;; before applying it.  Output is redirected to "*Org INFO
+      ;; Texinfo Output*" buffer.
+      (let ((outbuf (get-buffer-create "*Org INFO Texinfo Output*")))
+       (dolist (command org-texinfo-info-process)
+         (shell-command
+          (replace-regexp-in-string
+           "%b" (shell-quote-argument base-name)
+           (replace-regexp-in-string
+            "%f" (shell-quote-argument full-name)
+            (replace-regexp-in-string
+             "%o" (shell-quote-argument out-dir) command t t) t t) t t)
+          outbuf))
+       ;; Collect standard errors from output buffer.
+       (setq errors (org-texinfo-collect-errors outbuf)))
       (let ((infofile (concat out-dir base-name ".info")))
        ;; Check for process failure.  Provide collected errors if
        ;; possible.