mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-18 76bbd07de7add0f9d13c6914f158d19630fe2f62
commit | author | age
76bbd0 1 ;;; ox-odt.el --- OpenDocument Text Exporter for Org Mode -*- lexical-binding: t; -*-
C 2
3 ;; Copyright (C) 2010-2018 Free Software Foundation, Inc.
4
5 ;; Author: Jambunathan K <kjambunathan at gmail dot com>
6 ;; Keywords: outlines, hypermedia, calendar, wp
7 ;; Homepage: https://orgmode.org
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (require 'cl-lib)
29 (require 'format-spec)
30 (require 'ox)
31 (require 'org-compat)
32 (require 'table nil 'noerror)
33
34 ;;; Define Back-End
35
36 (org-export-define-backend 'odt
37   '((bold . org-odt-bold)
38     (center-block . org-odt-center-block)
39     (clock . org-odt-clock)
40     (code . org-odt-code)
41     (drawer . org-odt-drawer)
42     (dynamic-block . org-odt-dynamic-block)
43     (entity . org-odt-entity)
44     (example-block . org-odt-example-block)
45     (export-block . org-odt-export-block)
46     (export-snippet . org-odt-export-snippet)
47     (fixed-width . org-odt-fixed-width)
48     (footnote-definition . org-odt-footnote-definition)
49     (footnote-reference . org-odt-footnote-reference)
50     (headline . org-odt-headline)
51     (horizontal-rule . org-odt-horizontal-rule)
52     (inline-src-block . org-odt-inline-src-block)
53     (inlinetask . org-odt-inlinetask)
54     (italic . org-odt-italic)
55     (item . org-odt-item)
56     (keyword . org-odt-keyword)
57     (latex-environment . org-odt-latex-environment)
58     (latex-fragment . org-odt-latex-fragment)
59     (line-break . org-odt-line-break)
60     (link . org-odt-link)
61     (node-property . org-odt-node-property)
62     (paragraph . org-odt-paragraph)
63     (plain-list . org-odt-plain-list)
64     (plain-text . org-odt-plain-text)
65     (planning . org-odt-planning)
66     (property-drawer . org-odt-property-drawer)
67     (quote-block . org-odt-quote-block)
68     (radio-target . org-odt-radio-target)
69     (section . org-odt-section)
70     (special-block . org-odt-special-block)
71     (src-block . org-odt-src-block)
72     (statistics-cookie . org-odt-statistics-cookie)
73     (strike-through . org-odt-strike-through)
74     (subscript . org-odt-subscript)
75     (superscript . org-odt-superscript)
76     (table . org-odt-table)
77     (table-cell . org-odt-table-cell)
78     (table-row . org-odt-table-row)
79     (target . org-odt-target)
80     (template . org-odt-template)
81     (timestamp . org-odt-timestamp)
82     (underline . org-odt-underline)
83     (verbatim . org-odt-verbatim)
84     (verse-block . org-odt-verse-block))
85   :filters-alist '((:filter-parse-tree
86             . (org-odt--translate-latex-fragments
87                org-odt--translate-description-lists
88                org-odt--translate-list-tables
89                org-odt--translate-image-links)))
90   :menu-entry
91   '(?o "Export to ODT"
92        ((?o "As ODT file" org-odt-export-to-odt)
93     (?O "As ODT file and open"
94         (lambda (a s v b)
95           (if a (org-odt-export-to-odt t s v)
96         (org-open-file (org-odt-export-to-odt nil s v) 'system))))))
97   :options-alist
98   '((:odt-styles-file "ODT_STYLES_FILE" nil nil t)
99     (:description "DESCRIPTION" nil nil newline)
100     (:keywords "KEYWORDS" nil nil space)
101     (:subtitle "SUBTITLE" nil nil parse)
102     ;; Other variables.
103     (:odt-content-template-file nil nil org-odt-content-template-file)
104     (:odt-display-outline-level nil nil org-odt-display-outline-level)
105     (:odt-fontify-srcblocks nil nil org-odt-fontify-srcblocks)
106     (:odt-format-drawer-function nil nil org-odt-format-drawer-function)
107     (:odt-format-headline-function nil nil org-odt-format-headline-function)
108     (:odt-format-inlinetask-function nil nil org-odt-format-inlinetask-function)
109     (:odt-inline-formula-rules nil nil org-odt-inline-formula-rules)
110     (:odt-inline-image-rules nil nil org-odt-inline-image-rules)
111     (:odt-pixels-per-inch nil nil org-odt-pixels-per-inch)
112     (:odt-styles-file nil nil org-odt-styles-file)
113     (:odt-table-styles nil nil org-odt-table-styles)
114     (:odt-use-date-fields nil nil org-odt-use-date-fields)
115     ;; Redefine regular option.
116     (:with-latex nil "tex" org-odt-with-latex)
117     ;; Retrieve LaTeX header for fragments.
118     (:latex-header "LATEX_HEADER" nil nil newline)))
119
120
121 ;;; Dependencies
122
123 ;;; Hooks
124
125 ;;; Function and Dynamically Scoped Variables Declarations
126
127 (declare-function hfy-face-to-style "htmlfontify" (fn))
128 (declare-function hfy-face-or-def-to-name "htmlfontify" (fn))
129 (declare-function archive-zip-extract "arc-mode" (archive name))
130 (declare-function org-create-math-formula "org" (latex-frag &optional mathml-file))
131 (declare-function browse-url-file-url "browse-url" (file))
132
133 (defvar nxml-auto-insert-xml-declaration-flag) ; nxml-mode.el
134 (defvar archive-zip-extract)               ; arc-mode.el
135 (defvar hfy-end-span-handler)               ; htmlfontify.el
136 (defvar hfy-begin-span-handler)               ; htmlfontify.el
137 (defvar hfy-face-to-css)               ; htmlfontify.el
138 (defvar hfy-html-quote-map)               ; htmlfontify.el
139 (defvar hfy-html-quote-regex)               ; htmlfontify.el
140
141
142 ;;; Internal Variables
143
144 (defconst org-odt-lib-dir
145   (file-name-directory (or load-file-name (buffer-file-name)))
146   "Location of ODT exporter.
147 Use this to infer values of `org-odt-styles-dir' and
148 `org-odt-schema-dir'.")
149
150 (defvar org-odt-data-dir
151   (expand-file-name "../../etc/" org-odt-lib-dir)
152   "Data directory for ODT exporter.
153 Use this to infer values of `org-odt-styles-dir' and
154 `org-odt-schema-dir'.")
155
156 (defconst org-odt-special-string-regexps
157   '(("\\\\-" . "&#x00ad;\\1")        ; shy
158     ("---\\([^-]\\)" . "&#x2014;\\1")    ; mdash
159     ("--\\([^-]\\)" . "&#x2013;\\1")    ; ndash
160     ("\\.\\.\\." . "&#x2026;"))        ; hellip
161   "Regular expressions for special string conversion.")
162
163 (defconst org-odt-schema-dir-list
164   (list
165    (and org-odt-data-dir
166     (expand-file-name "./schema/" org-odt-data-dir)) ; bail out
167    (eval-when-compile
168      (and (boundp 'org-odt-data-dir) org-odt-data-dir ; see make install
169       (expand-file-name "./schema/" org-odt-data-dir))))
170   "List of directories to search for OpenDocument schema files.
171 Use this list to set the default value of
172 `org-odt-schema-dir'.  The entries in this list are
173 populated heuristically based on the values of `org-odt-lib-dir'
174 and `org-odt-data-dir'.")
175
176 (defconst org-odt-styles-dir-list
177   (list
178    (and org-odt-data-dir
179     (expand-file-name "./styles/" org-odt-data-dir)) ; bail out
180    (eval-when-compile
181      (and (boundp 'org-odt-data-dir) org-odt-data-dir ; see make install
182       (expand-file-name "./styles/" org-odt-data-dir)))
183    (expand-file-name "../etc/styles/" org-odt-lib-dir) ; git
184    (expand-file-name "./etc/styles/" org-odt-lib-dir)  ; elpa
185    (expand-file-name "./org/" data-directory)           ; system
186    )
187   "List of directories to search for OpenDocument styles files.
188 See `org-odt-styles-dir'.  The entries in this list are populated
189 heuristically based on the values of `org-odt-lib-dir' and
190 `org-odt-data-dir'.")
191
192 (defconst org-odt-styles-dir
193   (let ((styles-dir
194      (cl-find-if
195       (lambda (dir)
196         (and dir
197          (file-readable-p
198           (expand-file-name "OrgOdtContentTemplate.xml" dir))
199          (file-readable-p (expand-file-name "OrgOdtStyles.xml" dir))))
200       org-odt-styles-dir-list)))
201     (unless styles-dir
202       (error "Error (ox-odt): Cannot find factory styles files, aborting"))
203     styles-dir)
204   "Directory that holds auxiliary XML files used by the ODT exporter.
205
206 This directory contains the following XML files -
207  \"OrgOdtStyles.xml\" and \"OrgOdtContentTemplate.xml\".  These
208  XML files are used as the default values of
209  `org-odt-styles-file' and `org-odt-content-template-file'.
210
211 The default value of this variable varies depending on the
212 version of Org in use and is initialized from
213 `org-odt-styles-dir-list'.  Note that the user could be using Org
214 from one of: Org own private git repository, GNU ELPA tar or
215 standard Emacs.")
216
217 (defconst org-odt-bookmark-prefix "OrgXref.")
218
219 (defconst org-odt-manifest-file-entry-tag
220   "\n<manifest:file-entry manifest:media-type=\"%s\" manifest:full-path=\"%s\"%s/>")
221
222 (defconst org-odt-file-extensions
223   '(("odt" . "OpenDocument Text")
224     ("ott" . "OpenDocument Text Template")
225     ("odm" . "OpenDocument Master Document")
226     ("ods" . "OpenDocument Spreadsheet")
227     ("ots" . "OpenDocument Spreadsheet Template")
228     ("odg" . "OpenDocument Drawing (Graphics)")
229     ("otg" . "OpenDocument Drawing Template")
230     ("odp" . "OpenDocument Presentation")
231     ("otp" . "OpenDocument Presentation Template")
232     ("odi" . "OpenDocument Image")
233     ("odf" . "OpenDocument Formula")
234     ("odc" . "OpenDocument Chart")))
235
236 (defconst org-odt-table-style-format
237   "
238 <style:style style:name=\"%s\" style:family=\"table\">
239   <style:table-properties style:rel-width=\"%s%%\" fo:margin-top=\"0cm\" fo:margin-bottom=\"0.20cm\" table:align=\"center\"/>
240 </style:style>
241 "
242   "Template for auto-generated Table styles.")
243
244 (defvar org-odt-automatic-styles '()
245   "Registry of automatic styles for various OBJECT-TYPEs.
246 The variable has the following form:
247  ((OBJECT-TYPE-A
248    ((OBJECT-NAME-A.1 OBJECT-PROPS-A.1)
249     (OBJECT-NAME-A.2 OBJECT-PROPS-A.2) ...))
250   (OBJECT-TYPE-B
251    ((OBJECT-NAME-B.1 OBJECT-PROPS-B.1)
252     (OBJECT-NAME-B.2 OBJECT-PROPS-B.2) ...))
253   ...).
254
255 OBJECT-TYPEs could be \"Section\", \"Table\", \"Figure\" etc.
256 OBJECT-PROPS is (typically) a plist created by passing
257 \"#+ATTR_ODT: \" option to `org-odt-parse-block-attributes'.
258
259 Use `org-odt-add-automatic-style' to add update this variable.'")
260
261 (defvar org-odt-object-counters nil
262   "Running counters for various OBJECT-TYPEs.
263 Use this to generate automatic names and style-names. See
264 `org-odt-add-automatic-style'.")
265
266 (defvar org-odt-src-block-paragraph-format
267   "<style:style style:name=\"OrgSrcBlock\" style:family=\"paragraph\" style:parent-style-name=\"Preformatted_20_Text\">
268    <style:paragraph-properties fo:background-color=\"%s\" fo:padding=\"0.049cm\" fo:border=\"0.51pt solid #000000\" style:shadow=\"none\">
269     <style:background-image/>
270    </style:paragraph-properties>
271    <style:text-properties fo:color=\"%s\"/>
272   </style:style>"
273   "Custom paragraph style for colorized source and example blocks.
274 This style is much the same as that of \"OrgFixedWidthBlock\"
275 except that the foreground and background colors are set
276 according to the default face identified by the `htmlfontify'.")
277
278 (defvar hfy-optimizations)
279 (defvar org-odt-embedded-formulas-count 0)
280 (defvar org-odt-embedded-images-count 0)
281 (defvar org-odt-image-size-probe-method
282   (append (and (executable-find "identify") '(imagemagick)) ; See Bug#10675
283       '(emacs fixed))
284   "Ordered list of methods for determining image sizes.")
285
286 (defvar org-odt-default-image-sizes-alist
287   '(("as-char" . (5 . 0.4))
288     ("paragraph" . (5 . 5)))
289   "Hardcoded image dimensions one for each of the anchor
290   methods.")
291
292 ;; A4 page size is 21.0 by 29.7 cms
293 ;; The default page settings has 2cm margin on each of the sides. So
294 ;; the effective text area is 17.0 by 25.7 cm
295 (defvar org-odt-max-image-size '(17.0 . 20.0)
296   "Limiting dimensions for an embedded image.")
297
298 (defconst org-odt-label-styles
299   '(("math-formula" "%c" "text" "(%n)")
300     ("math-label" "(%n)" "text" "(%n)")
301     ("category-and-value" "%e %n: %c" "category-and-value" "%e %n")
302     ("value" "%e %n: %c" "value" "%n"))
303   "Specify how labels are applied and referenced.
304
305 This is an alist where each element is of the form:
306
307   (STYLE-NAME ATTACH-FMT REF-MODE REF-FMT)
308
309 ATTACH-FMT controls how labels and captions are attached to an
310 entity.  It may contain following specifiers - %e and %c.  %e is
311 replaced with the CATEGORY-NAME.  %n is replaced with
312 \"<text:sequence ...> SEQNO </text:sequence>\".  %c is replaced
313 with CAPTION.
314
315 REF-MODE and REF-FMT controls how label references are generated.
316 The following XML is generated for a label reference -
317 \"<text:sequence-ref text:reference-format=\"REF-MODE\" ...>
318 REF-FMT </text:sequence-ref>\".  REF-FMT may contain following
319 specifiers - %e and %n.  %e is replaced with the CATEGORY-NAME.
320 %n is replaced with SEQNO.
321
322 See also `org-odt-format-label'.")
323
324 (defvar org-odt-category-map-alist
325   '(("__Table__" "Table" "value" "Table" org-odt--enumerable-p)
326     ("__Figure__" "Illustration" "value" "Figure" org-odt--enumerable-image-p)
327     ("__MathFormula__" "Text" "math-formula" "Equation" org-odt--enumerable-formula-p)
328     ("__DvipngImage__" "Equation" "value" "Equation" org-odt--enumerable-latex-image-p)
329     ("__Listing__" "Listing" "value" "Listing" org-odt--enumerable-p))
330   "Map a CATEGORY-HANDLE to OD-VARIABLE and LABEL-STYLE.
331
332 This is a list where each entry is of the form:
333
334   (CATEGORY-HANDLE OD-VARIABLE LABEL-STYLE CATEGORY-NAME ENUMERATOR-PREDICATE)
335
336 CATEGORY_HANDLE identifies the captionable entity in question.
337
338 OD-VARIABLE is the OpenDocument sequence counter associated with
339 the entity.  These counters are declared within
340 \"<text:sequence-decls>...</text:sequence-decls>\" block of
341 `org-odt-content-template-file'.
342
343 LABEL-STYLE is a key into `org-odt-label-styles' and specifies
344 how a given entity should be captioned and referenced.
345
346 CATEGORY-NAME is used for qualifying captions on export.
347
348 ENUMERATOR-PREDICATE is used for assigning a sequence number to
349 the entity.  See `org-odt--enumerate'.")
350
351 (defvar org-odt-manifest-file-entries nil)
352 (defvar hfy-user-sheet-assoc)
353
354 (defvar org-odt-zip-dir nil
355   "Temporary work directory for OpenDocument exporter.")
356
357
358
359 ;;; User Configuration Variables
360
361 (defgroup org-export-odt nil
362   "Options for exporting Org mode files to ODT."
363   :tag "Org Export ODT"
364   :group 'org-export)
365
366
367 ;;;; Debugging
368
369 (defcustom org-odt-prettify-xml nil
370   "Specify whether or not the xml output should be prettified.
371 When this option is turned on, `indent-region' is run on all
372 component xml buffers before they are saved.  Turn this off for
373 regular use.  Turn this on if you need to examine the xml
374 visually."
375   :group 'org-export-odt
376   :version "24.1"
377   :type 'boolean)
378
379
380 ;;;; Document schema
381
382 (require 'rng-loc)
383 (defcustom org-odt-schema-dir
384   (cl-find-if
385    (lambda (dir)
386      (and dir
387       (file-expand-wildcards
388        (expand-file-name "od-manifest-schema*.rnc" dir))
389       (file-expand-wildcards (expand-file-name "od-schema*.rnc" dir))
390       (file-readable-p (expand-file-name "schemas.xml" dir))))
391    org-odt-schema-dir-list)
392   "Directory that contains OpenDocument schema files.
393
394 This directory contains:
395 1. rnc files for OpenDocument schema
396 2. a \"schemas.xml\" file that specifies locating rules needed
397    for auto validation of OpenDocument XML files.
398
399 Use the customize interface to set this variable.  This ensures
400 that `rng-schema-locating-files' is updated and auto-validation
401 of OpenDocument XML takes place based on the value
402 `rng-nxml-auto-validate-flag'.
403
404 The default value of this variable varies depending on the
405 version of org in use and is initialized from
406 `org-odt-schema-dir-list'.  The OASIS schema files are available
407 only in the org's private git repository.  It is *not* bundled
408 with GNU ELPA tar or standard Emacs distribution."
409   :type '(choice
410       (const :tag "Not set" nil)
411       (directory :tag "Schema directory"))
412   :group 'org-export-odt
413   :version "24.1"
414   :set
415   (lambda (var value)
416     "Set `org-odt-schema-dir'.
417 Also add it to `rng-schema-locating-files'."
418     (let ((schema-dir value))
419       (set var
420        (if (and
421         (file-expand-wildcards
422          (expand-file-name "od-manifest-schema*.rnc" schema-dir))
423         (file-expand-wildcards
424          (expand-file-name "od-schema*.rnc" schema-dir))
425         (file-readable-p
426          (expand-file-name "schemas.xml" schema-dir)))
427            schema-dir
428          (when value
429            (message "Error (ox-odt): %s has no OpenDocument schema files"
430             value))
431          nil)))
432     (when org-odt-schema-dir
433       (eval-after-load 'rng-loc
434     '(add-to-list 'rng-schema-locating-files
435               (expand-file-name "schemas.xml"
436                     org-odt-schema-dir))))))
437
438
439 ;;;; Document styles
440
441 (defcustom org-odt-content-template-file nil
442   "Template file for \"content.xml\".
443 The exporter embeds the exported content just before
444 \"</office:text>\" element.
445
446 If unspecified, the file named \"OrgOdtContentTemplate.xml\"
447 under `org-odt-styles-dir' is used."
448   :type '(choice (const nil)
449          (file))
450   :group 'org-export-odt
451   :version "24.3")
452
453 (defcustom org-odt-styles-file nil
454   "Default styles file for use with ODT export.
455 Valid values are one of:
456 1. nil
457 2. path to a styles.xml file
458 3. path to a *.odt or a *.ott file
459 4. list of the form (ODT-OR-OTT-FILE (FILE-MEMBER-1 FILE-MEMBER-2
460 ...))
461
462 In case of option 1, an in-built styles.xml is used. See
463 `org-odt-styles-dir' for more information.
464
465 In case of option 3, the specified file is unzipped and the
466 styles.xml embedded therein is used.
467
468 In case of option 4, the specified ODT-OR-OTT-FILE is unzipped
469 and FILE-MEMBER-1, FILE-MEMBER-2 etc are copied in to the
470 generated odt file.  Use relative path for specifying the
471 FILE-MEMBERS.  styles.xml must be specified as one of the
472 FILE-MEMBERS.
473
474 Use options 1, 2 or 3 only if styles.xml alone suffices for
475 achieving the desired formatting.  Use option 4, if the styles.xml
476 references additional files like header and footer images for
477 achieving the desired formatting.
478
479 Use \"#+ODT_STYLES_FILE: ...\" directive to set this variable on
480 a per-file basis.  For example,
481
482 #+ODT_STYLES_FILE: \"/path/to/styles.xml\" or
483 #+ODT_STYLES_FILE: (\"/path/to/file.ott\" (\"styles.xml\" \"image/hdr.png\"))."
484   :group 'org-export-odt
485   :version "24.1"
486   :type
487   '(choice
488     (const :tag "Factory settings" nil)
489     (file :must-match t :tag "styles.xml")
490     (file :must-match t :tag "ODT or OTT file")
491     (list :tag "ODT or OTT file + Members"
492       (file :must-match t :tag "ODF Text or Text Template file")
493       (cons :tag "Members"
494         (file :tag "    Member" "styles.xml")
495         (repeat (file :tag "Member"))))))
496
497 (defcustom org-odt-display-outline-level 2
498   "Outline levels considered for enumerating captioned entities."
499   :group 'org-export-odt
500   :version "24.4"
501   :package-version '(Org . "8.0")
502   :type 'integer)
503
504 ;;;; Document conversion
505
506 (defcustom org-odt-convert-processes
507   '(("LibreOffice"
508      "soffice --headless --convert-to %f%x --outdir %d %i")
509     ("unoconv"
510      "unoconv -f %f -o %d %i"))
511   "Specify a list of document converters and their usage.
512 The converters in this list are offered as choices while
513 customizing `org-odt-convert-process'.
514
515 This variable is a list where each element is of the
516 form (CONVERTER-NAME CONVERTER-CMD).  CONVERTER-NAME is the name
517 of the converter.  CONVERTER-CMD is the shell command for the
518 converter and can contain format specifiers.  These format
519 specifiers are interpreted as below:
520
521 %i input file name in full
522 %I input file name as a URL
523 %f format of the output file
524 %o output file name in full
525 %O output file name as a URL
526 %d output dir in full
527 %D output dir as a URL.
528 %x extra options as set in `org-odt-convert-capabilities'."
529   :group 'org-export-odt
530   :version "24.1"
531   :type
532   '(choice
533     (const :tag "None" nil)
534     (alist :tag "Converters"
535        :key-type (string :tag "Converter Name")
536        :value-type (group (string :tag "Command line")))))
537
538 (defcustom org-odt-convert-process "LibreOffice"
539   "Use this converter to convert from \"odt\" format to other formats.
540 During customization, the list of converter names are populated
541 from `org-odt-convert-processes'."
542   :group 'org-export-odt
543   :version "24.1"
544   :type '(choice :convert-widget
545          (lambda (w)
546            (apply 'widget-convert (widget-type w)
547               (eval (car (widget-get w :args)))))
548          `((const :tag "None" nil)
549            ,@(mapcar (lambda (c)
550                    `(const :tag ,(car c) ,(car c)))
551                  org-odt-convert-processes))))
552
553 (defcustom org-odt-convert-capabilities
554   '(("Text"
555      ("odt" "ott" "doc" "rtf" "docx")
556      (("pdf" "pdf") ("odt" "odt") ("rtf" "rtf") ("ott" "ott")
557       ("doc" "doc" ":\"MS Word 97\"") ("docx" "docx") ("html" "html")))
558     ("Web"
559      ("html")
560      (("pdf" "pdf") ("odt" "odt") ("html" "html")))
561     ("Spreadsheet"
562      ("ods" "ots" "xls" "csv" "xlsx")
563      (("pdf" "pdf") ("ots" "ots") ("html" "html") ("csv" "csv") ("ods" "ods")
564       ("xls" "xls") ("xlsx" "xlsx")))
565     ("Presentation"
566      ("odp" "otp" "ppt" "pptx")
567      (("pdf" "pdf") ("swf" "swf") ("odp" "odp") ("otp" "otp") ("ppt" "ppt")
568       ("pptx" "pptx") ("odg" "odg"))))
569   "Specify input and output formats of `org-odt-convert-process'.
570 More correctly, specify the set of input and output formats that
571 the user is actually interested in.
572
573 This variable is an alist where each element is of the
574 form (DOCUMENT-CLASS INPUT-FMT-LIST OUTPUT-FMT-ALIST).
575 INPUT-FMT-LIST is a list of INPUT-FMTs.  OUTPUT-FMT-ALIST is an
576 alist where each element is of the form (OUTPUT-FMT
577 OUTPUT-FILE-EXTENSION EXTRA-OPTIONS).
578
579 The variable is interpreted as follows:
580 `org-odt-convert-process' can take any document that is in
581 INPUT-FMT-LIST and produce any document that is in the
582 OUTPUT-FMT-LIST.  A document converted to OUTPUT-FMT will have
583 OUTPUT-FILE-EXTENSION as the file name extension.  OUTPUT-FMT
584 serves dual purposes:
585 - It is used for populating completion candidates during
586   `org-odt-convert' commands.
587 - It is used as the value of \"%f\" specifier in
588   `org-odt-convert-process'.
589
590 EXTRA-OPTIONS is used as the value of \"%x\" specifier in
591 `org-odt-convert-process'.
592
593 DOCUMENT-CLASS is used to group a set of file formats in
594 INPUT-FMT-LIST in to a single class.
595
596 Note that this variable inherently captures how LibreOffice based
597 converters work.  LibreOffice maps documents of various formats
598 to classes like Text, Web, Spreadsheet, Presentation etc and
599 allow document of a given class (irrespective of its source
600 format) to be converted to any of the export formats associated
601 with that class.
602
603 See default setting of this variable for a typical configuration."
604   :group 'org-export-odt
605   :version "24.1"
606   :type
607   '(choice
608     (const :tag "None" nil)
609     (alist :tag "Capabilities"
610        :key-type (string :tag "Document Class")
611        :value-type
612        (group (repeat :tag "Input formats" (string :tag "Input format"))
613           (alist :tag "Output formats"
614              :key-type (string :tag "Output format")
615              :value-type
616              (group (string :tag "Output file extension")
617                 (choice
618                  (const :tag "None" nil)
619                  (string :tag "Extra options"))))))))
620
621 (defcustom org-odt-preferred-output-format nil
622   "Automatically post-process to this format after exporting to \"odt\".
623 Command `org-odt-export-to-odt' exports first to \"odt\" format
624 and then uses `org-odt-convert-process' to convert the
625 resulting document to this format.  During customization of this
626 variable, the list of valid values are populated based on
627 `org-odt-convert-capabilities'.
628
629 You can set this option on per-file basis using file local
630 values.  See Info node `(emacs) File Variables'."
631   :group 'org-export-odt
632   :version "24.1"
633   :type '(choice :convert-widget
634          (lambda (w)
635            (apply 'widget-convert (widget-type w)
636               (eval (car (widget-get w :args)))))
637          `((const :tag "None" nil)
638            ,@(mapcar (lambda (c)
639                    `(const :tag ,c ,c))
640                  (org-odt-reachable-formats "odt")))))
641 ;;;###autoload
642 (put 'org-odt-preferred-output-format 'safe-local-variable 'stringp)
643
644
645 ;;;; Drawers
646
647 (defcustom org-odt-format-drawer-function (lambda (_name contents) contents)
648   "Function called to format a drawer in ODT code.
649
650 The function must accept two parameters:
651   NAME      the drawer name, like \"LOGBOOK\"
652   CONTENTS  the contents of the drawer.
653
654 The function should return the string to be exported.
655
656 The default value simply returns the value of CONTENTS."
657   :group 'org-export-odt
658   :version "26.1"
659   :package-version '(Org . "8.3")
660   :type 'function)
661
662
663 ;;;; Headline
664
665 (defcustom org-odt-format-headline-function
666   'org-odt-format-headline-default-function
667   "Function to format headline text.
668
669 This function will be called with 5 arguments:
670 TODO      the todo keyword (string or nil).
671 TODO-TYPE the type of todo (symbol: `todo', `done', nil)
672 PRIORITY  the priority of the headline (integer or nil)
673 TEXT      the main headline text (string).
674 TAGS      the tags string, separated with colons (string or nil).
675
676 The function result will be used as headline text."
677   :group 'org-export-odt
678   :version "26.1"
679   :package-version '(Org . "8.3")
680   :type 'function)
681
682
683 ;;;; Inlinetasks
684
685 (defcustom org-odt-format-inlinetask-function
686   'org-odt-format-inlinetask-default-function
687   "Function called to format an inlinetask in ODT code.
688
689 The function must accept six parameters:
690   TODO      the todo keyword, as a string
691   TODO-TYPE the todo type, a symbol among `todo', `done' and nil.
692   PRIORITY  the inlinetask priority, as a string
693   NAME      the inlinetask name, as a string.
694   TAGS      the inlinetask tags, as a string.
695   CONTENTS  the contents of the inlinetask, as a string.
696
697 The function should return the string to be exported."
698   :group 'org-export-odt
699   :version "26.1"
700   :package-version '(Org . "8.3")
701   :type 'function)
702
703
704 ;;;; LaTeX
705
706 (defcustom org-odt-with-latex org-export-with-latex
707   "Non-nil means process LaTeX math snippets.
708
709 When set, the exporter will process LaTeX environments and
710 fragments.
711
712 This option can also be set with the +OPTIONS line,
713 e.g. \"tex:mathjax\".  Allowed values are:
714
715 nil            Ignore math snippets.
716 `verbatim'     Keep everything in verbatim
717 `dvipng'       Process the LaTeX fragments to images.  This will also
718                include processing of non-math environments.
719 `imagemagick'  Convert the LaTeX fragments to pdf files and use
720                imagemagick to convert pdf files to png files.
721 `mathjax'      Do MathJax preprocessing and arrange for MathJax.js to
722                be loaded.
723
724 Any other symbol is a synonym for `mathjax'."
725   :group 'org-export-odt
726   :version "24.4"
727   :package-version '(Org . "8.0")
728   :type '(choice
729       (const :tag "Do not process math in any way" nil)
730       (const :tag "Leave math verbatim" verbatim)
731       (const :tag "Use dvipng to make images" dvipng)
732       (const :tag "Use imagemagick to make images" imagemagick)
733       (other :tag "Use MathJax to display math" mathjax)))
734
735
736 ;;;; Links
737
738 (defcustom org-odt-inline-formula-rules
739   '(("file" . "\\.\\(mathml\\|mml\\|odf\\)\\'"))
740   "Rules characterizing formula files that can be inlined into ODT.
741
742 A rule consists in an association whose key is the type of link
743 to consider, and value is a regexp that will be matched against
744 link's path."
745   :group 'org-export-odt
746   :version "24.4"
747   :package-version '(Org . "8.0")
748   :type '(alist :key-type (string :tag "Type")
749         :value-type (regexp :tag "Path")))
750
751 (defcustom org-odt-inline-image-rules
752   '(("file" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'"))
753   "Rules characterizing image files that can be inlined into ODT.
754
755 A rule consists in an association whose key is the type of link
756 to consider, and value is a regexp that will be matched against
757 link's path."
758   :group 'org-export-odt
759   :version "26.1"
760   :package-version '(Org . "8.3")
761   :type '(alist :key-type (string :tag "Type")
762         :value-type (regexp :tag "Path")))
763
764 (defcustom org-odt-pixels-per-inch 96.0
765   "Scaling factor for converting images pixels to inches.
766 Use this for sizing of embedded images.  See Info node `(org)
767 Images in ODT export' for more information."
768   :type 'float
769   :group 'org-export-odt
770   :version "24.4"
771   :package-version '(Org . "8.1"))
772
773
774 ;;;; Src Block
775
776 (defcustom org-odt-create-custom-styles-for-srcblocks t
777   "Whether custom styles for colorized source blocks be automatically created.
778 When this option is turned on, the exporter creates custom styles
779 for source blocks based on the advice of `htmlfontify'.  Creation
780 of custom styles happen as part of `org-odt-hfy-face-to-css'.
781
782 When this option is turned off exporter does not create such
783 styles.
784
785 Use the latter option if you do not want the custom styles to be
786 based on your current display settings.  It is necessary that the
787 styles.xml already contains needed styles for colorizing to work.
788
789 This variable is effective only if `org-odt-fontify-srcblocks' is
790 turned on."
791   :group 'org-export-odt
792   :version "24.1"
793   :type 'boolean)
794
795 (defcustom org-odt-fontify-srcblocks t
796   "Specify whether or not source blocks need to be fontified.
797 Turn this option on if you want to colorize the source code
798 blocks in the exported file.  For colorization to work, you need
799 to make available an enhanced version of `htmlfontify' library."
800   :type 'boolean
801   :group 'org-export-odt
802   :version "24.1")
803
804
805 ;;;; Table
806
807 (defcustom org-odt-table-styles
808   '(("OrgEquation" "OrgEquation"
809      ((use-first-column-styles . t)
810       (use-last-column-styles . t)))
811     ("TableWithHeaderRowAndColumn" "Custom"
812      ((use-first-row-styles . t)
813       (use-first-column-styles . t)))
814     ("TableWithFirstRowandLastRow" "Custom"
815      ((use-first-row-styles . t)
816       (use-last-row-styles . t)))
817     ("GriddedTable" "Custom" nil))
818   "Specify how Table Styles should be derived from a Table Template.
819 This is a list where each element is of the
820 form (TABLE-STYLE-NAME TABLE-TEMPLATE-NAME TABLE-CELL-OPTIONS).
821
822 TABLE-STYLE-NAME is the style associated with the table through
823 \"#+ATTR_ODT: :style TABLE-STYLE-NAME\" line.
824
825 TABLE-TEMPLATE-NAME is a set of - upto 9 - automatic
826 TABLE-CELL-STYLE-NAMEs and PARAGRAPH-STYLE-NAMEs (as defined
827 below) that is included in `org-odt-content-template-file'.
828
829 TABLE-CELL-STYLE-NAME := TABLE-TEMPLATE-NAME + TABLE-CELL-TYPE +
830                          \"TableCell\"
831 PARAGRAPH-STYLE-NAME  := TABLE-TEMPLATE-NAME + TABLE-CELL-TYPE +
832                          \"TableParagraph\"
833 TABLE-CELL-TYPE       := \"FirstRow\"   | \"LastColumn\" |
834                          \"FirstRow\"   | \"LastRow\"    |
835                          \"EvenRow\"    | \"OddRow\"     |
836                          \"EvenColumn\" | \"OddColumn\"  | \"\"
837 where \"+\" above denotes string concatenation.
838
839 TABLE-CELL-OPTIONS is an alist where each element is of the
840 form (TABLE-CELL-STYLE-SELECTOR . ON-OR-OFF).
841 TABLE-CELL-STYLE-SELECTOR := `use-first-row-styles'       |
842                              `use-last-row-styles'        |
843                              `use-first-column-styles'    |
844                              `use-last-column-styles'     |
845                              `use-banding-rows-styles'    |
846                              `use-banding-columns-styles' |
847                              `use-first-row-styles'
848 ON-OR-OFF                 := t | nil
849
850 For example, with the following configuration
851
852 \(setq org-odt-table-styles
853       \\='((\"TableWithHeaderRowsAndColumns\" \"Custom\"
854          ((use-first-row-styles . t)
855           (use-first-column-styles . t)))
856         (\"TableWithHeaderColumns\" \"Custom\"
857          ((use-first-column-styles . t)))))
858
859 1. A table associated with \"TableWithHeaderRowsAndColumns\"
860    style will use the following table-cell styles -
861    \"CustomFirstRowTableCell\", \"CustomFirstColumnTableCell\",
862    \"CustomTableCell\" and the following paragraph styles
863    \"CustomFirstRowTableParagraph\",
864    \"CustomFirstColumnTableParagraph\", \"CustomTableParagraph\"
865    as appropriate.
866
867 2. A table associated with \"TableWithHeaderColumns\" style will
868    use the following table-cell styles -
869    \"CustomFirstColumnTableCell\", \"CustomTableCell\" and the
870    following paragraph styles
871    \"CustomFirstColumnTableParagraph\", \"CustomTableParagraph\"
872    as appropriate..
873
874 Note that TABLE-TEMPLATE-NAME corresponds to the
875 \"<table:table-template>\" elements contained within
876 \"<office:styles>\".  The entries (TABLE-STYLE-NAME
877 TABLE-TEMPLATE-NAME TABLE-CELL-OPTIONS) correspond to
878 \"table:template-name\" and \"table:use-first-row-styles\" etc
879 attributes of \"<table:table>\" element.  Refer ODF-1.2
880 specification for more information.  Also consult the
881 implementation filed under `org-odt-get-table-cell-styles'.
882
883 The TABLE-STYLE-NAME \"OrgEquation\" is used internally for
884 formatting of numbered display equations.  Do not delete this
885 style from the list."
886   :group 'org-export-odt
887   :version "24.1"
888   :type '(choice
889           (const :tag "None" nil)
890           (repeat :tag "Table Styles"
891                   (list :tag "Table Style Specification"
892             (string :tag "Table Style Name")
893             (string  :tag "Table Template Name")
894             (alist :options (use-first-row-styles
895                      use-last-row-styles
896                      use-first-column-styles
897                      use-last-column-styles
898                      use-banding-rows-styles
899                      use-banding-columns-styles)
900                    :key-type symbol
901                    :value-type (const :tag "True" t))))))
902
903 ;;;; Timestamps
904
905 (defcustom org-odt-use-date-fields nil
906   "Non-nil, if timestamps should be exported as date fields.
907
908 When nil, export timestamps as plain text.
909
910 When non-nil, map `org-time-stamp-custom-formats' to a pair of
911 OpenDocument date-styles with names \"OrgDate1\" and \"OrgDate2\"
912 respectively.  A timestamp with no time component is formatted
913 with style \"OrgDate1\" while one with explicit hour and minutes
914 is formatted with style \"OrgDate2\".
915
916 This feature is experimental.  Most (but not all) of the common
917 %-specifiers in `format-time-string' are supported.
918 Specifically, locale-dependent specifiers like \"%c\", \"%x\" are
919 formatted as canonical Org timestamps.  For finer control, avoid
920 these %-specifiers.
921
922 Textual specifiers like \"%b\", \"%h\", \"%B\", \"%a\", \"%A\"
923 etc., are displayed by the application in the default language
924 and country specified in `org-odt-styles-file'.  Note that the
925 default styles file uses language \"en\" and country \"GB\".  You
926 can localize the week day and month strings in the exported
927 document by setting the default language and country either using
928 the application UI or through a custom styles file.
929
930 See `org-odt--build-date-styles' for implementation details."
931   :group 'org-export-odt
932   :version "24.4"
933   :package-version '(Org . "8.0")
934   :type 'boolean)
935
936
937
938 ;;; Internal functions
939
940 ;;;; Date
941
942 (defun org-odt--format-timestamp (timestamp &optional end iso-date-p)
943   (let* ((format-timestamp
944       (lambda (timestamp format &optional end utc)
945         (if timestamp
946         (org-timestamp-format timestamp format end utc)
947           (format-time-string format nil utc))))
948      (has-time-p (or (not timestamp)
949              (org-timestamp-has-time-p timestamp)))
950      (iso-date (let ((format (if has-time-p "%Y-%m-%dT%H:%M:%S"
951                    "%Y-%m-%dT%H:%M:%S")))
952              (funcall format-timestamp timestamp format end))))
953     (if iso-date-p iso-date
954       (let* ((style (if has-time-p "OrgDate2" "OrgDate1"))
955          ;; LibreOffice does not care about end goes as content
956          ;; within the "<text:date>...</text:date>" field.  The
957          ;; displayed date is automagically corrected to match the
958          ;; format requested by "style:data-style-name" attribute.  So
959          ;; don't bother about formatting the date contents to be
960          ;; compatible with "OrgDate1" and "OrgDateTime" styles.  A
961          ;; simple Org-style date should suffice.
962          (date (let* ((formats
963                (if org-display-custom-times
964                    (cons (substring
965                       (car org-time-stamp-custom-formats) 1 -1)
966                      (substring
967                       (cdr org-time-stamp-custom-formats) 1 -1))
968                  '("%Y-%m-%d %a" . "%Y-%m-%d %a %H:%M")))
969               (format (if has-time-p (cdr formats) (car formats))))
970              (funcall format-timestamp timestamp format end)))
971          (repeater (let ((repeater-type (org-element-property
972                          :repeater-type timestamp))
973                  (repeater-value (org-element-property
974                           :repeater-value timestamp))
975                  (repeater-unit (org-element-property
976                          :repeater-unit timestamp)))
977              (concat
978               (cl-case repeater-type
979                 (catchup "++") (restart ".+") (cumulate "+"))
980               (when repeater-value
981                 (number-to-string repeater-value))
982               (cl-case repeater-unit
983                 (hour "h") (day "d") (week "w") (month "m")
984                 (year "y"))))))
985     (concat
986      (format "<text:date text:date-value=\"%s\" style:data-style-name=\"%s\" text:fixed=\"true\">%s</text:date>"
987          iso-date style date)
988      (and (not (string= repeater ""))  " ")
989      repeater)))))
990
991 ;;;; Frame
992
993 (defun org-odt--frame (text width height style &optional extra
994                   anchor-type &rest title-and-desc)
995   (let ((frame-attrs
996      (concat
997       (if width (format " svg:width=\"%0.2fcm\"" width) "")
998       (if height (format " svg:height=\"%0.2fcm\"" height) "")
999       extra
1000       (format " text:anchor-type=\"%s\"" (or anchor-type "paragraph"))
1001       (format " draw:name=\"%s\""
1002           (car (org-odt-add-automatic-style "Frame"))))))
1003     (format
1004      "\n<draw:frame draw:style-name=\"%s\"%s>\n%s\n</draw:frame>"
1005      style frame-attrs
1006      (concat text
1007          (let ((title (car title-and-desc))
1008            (desc (cadr title-and-desc)))
1009            (concat (when title
1010              (format "<svg:title>%s</svg:title>"
1011                  (org-odt--encode-plain-text title t)))
1012                (when desc
1013              (format "<svg:desc>%s</svg:desc>"
1014                  (org-odt--encode-plain-text desc t)))))))))
1015
1016
1017 ;;;; Library wrappers
1018
1019 (defun org-odt--zip-extract (archive members target)
1020   (when (atom members) (setq members (list members)))
1021   (require 'arc-mode)
1022   (dolist (member members)
1023     (let* ((--quote-file-name
1024         ;; This is shamelessly stolen from `archive-zip-extract'.
1025         (lambda (name)
1026           (if (or (not (memq system-type '(windows-nt ms-dos)))
1027               (and (boundp 'w32-quote-process-args)
1028                (null w32-quote-process-args)))
1029           (shell-quote-argument name)
1030         name)))
1031        (target (funcall --quote-file-name target))
1032        (archive (expand-file-name archive))
1033        (archive-zip-extract
1034         (list "unzip" "-qq" "-o" "-d" target))
1035        exit-code command-output)
1036       (setq command-output
1037         (with-temp-buffer
1038           (setq exit-code (archive-zip-extract archive member))
1039           (buffer-string)))
1040       (unless (zerop exit-code)
1041     (message command-output)
1042     (error "Extraction failed")))))
1043
1044 ;;;; Target
1045
1046 (defun org-odt--target (text id)
1047   (if (not id) text
1048     (concat
1049      (format "\n<text:bookmark-start text:name=\"OrgXref.%s\"/>" id)
1050      (format "\n<text:bookmark text:name=\"%s\"/>" id) text
1051      (format "\n<text:bookmark-end text:name=\"OrgXref.%s\"/>" id))))
1052
1053 ;;;; Textbox
1054
1055 (defun org-odt--textbox (text width height style &optional
1056                 extra anchor-type)
1057   (org-odt--frame
1058    (format "\n<draw:text-box %s>%s\n</draw:text-box>"
1059        (concat (format " fo:min-height=\"%0.2fcm\"" (or height .2))
1060            (and (not width)
1061             (format " fo:min-width=\"%0.2fcm\"" (or width .2))))
1062        text)
1063    width nil style extra anchor-type))
1064
1065
1066
1067 ;;;; Table of Contents
1068
1069 (defun org-odt--format-toc (title entries depth)
1070   "Return a table of contents.
1071 TITLE is the title of the table, as a string, or nil.  ENTRIES is
1072 the contents of the table, as a string.  DEPTH is an integer
1073 specifying the depth of the table."
1074   (concat
1075    "
1076 <text:table-of-content text:style-name=\"OrgIndexSection\" text:protected=\"true\" text:name=\"Table of Contents\">\n"
1077    (format "  <text:table-of-content-source text:outline-level=\"%d\">" depth)
1078    (and title
1079     (format "
1080     <text:index-title-template text:style-name=\"Contents_20_Heading\">%s</text:index-title-template>
1081 "
1082         title))
1083
1084    (let ((levels (number-sequence 1 10)))
1085      (mapconcat
1086       (lambda (level)
1087     (format
1088      "
1089       <text:table-of-content-entry-template text:outline-level=\"%d\" text:style-name=\"Contents_20_%d\">
1090        <text:index-entry-link-start text:style-name=\"Internet_20_link\"/>
1091        <text:index-entry-chapter/>
1092        <text:index-entry-text/>
1093        <text:index-entry-link-end/>
1094       </text:table-of-content-entry-template>\n"
1095      level level)) levels ""))
1096    "
1097   </text:table-of-content-source>
1098   <text:index-body>"
1099    (and title
1100     (format "
1101     <text:index-title text:style-name=\"Sect1\" text:name=\"Table of Contents1_Head\">
1102       <text:p text:style-name=\"Contents_20_Heading\">%s</text:p>
1103     </text:index-title>\n"
1104         title))
1105    entries
1106    "
1107   </text:index-body>
1108 </text:table-of-content>"))
1109
1110 (cl-defun org-odt-format-toc-headline
1111     (todo _todo-type priority text tags
1112       &key _level section-number headline-label &allow-other-keys)
1113   (format "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>"
1114       headline-label
1115       (concat
1116        ;; Section number.
1117        (and section-number (concat section-number ". "))
1118        ;; Todo.
1119        (when todo
1120          (let ((style (if (member todo org-done-keywords)
1121                   "OrgDone" "OrgTodo")))
1122            (format "<text:span text:style-name=\"%s\">%s</text:span> "
1123                style todo)))
1124        (when priority
1125          (let* ((style (format "OrgPriority-%s" priority))
1126             (priority (format "[#%c]" priority)))
1127            (format "<text:span text:style-name=\"%s\">%s</text:span> "
1128                style priority)))
1129        ;; Title.
1130        text
1131        ;; Tags.
1132        (when tags
1133          (concat
1134           (format " <text:span text:style-name=\"%s\">[%s]</text:span>"
1135               "OrgTags"
1136               (mapconcat
1137                (lambda (tag)
1138              (format
1139               "<text:span text:style-name=\"%s\">%s</text:span>"
1140               "OrgTag" tag)) tags " : ")))))))
1141
1142 (defun org-odt-toc (depth info &optional scope)
1143   "Build a table of contents.
1144 DEPTH is an integer specifying the depth of the table.  INFO is
1145 a plist containing current export properties.  Optional argument
1146 SCOPE, when non-nil, defines the scope of the table.  Return the
1147 table of contents as a string, or nil."
1148   (cl-assert (wholenump depth))
1149   ;; When a headline is marked as a radio target, as in the example below:
1150   ;;
1151   ;; ** <<<Some Heading>>>
1152   ;;    Some text.
1153   ;;
1154   ;; suppress generation of radio targets.  i.e., Radio targets are to
1155   ;; be marked as targets within /document body/ and *not* within
1156   ;; /TOC/, as otherwise there will be duplicated anchors one in TOC
1157   ;; and one in the document body.
1158   ;;
1159   ;; Likewise, links, footnote references and regular targets are also
1160   ;; suppressed.
1161   (let* ((headlines (org-export-collect-headlines info depth scope))
1162      (backend (org-export-toc-entry-backend
1163               (org-export-backend-name (plist-get info :back-end)))))
1164     (when headlines
1165       (org-odt--format-toc
1166        (and (not scope) (org-export-translate "Table of Contents" :utf-8 info))
1167        (mapconcat
1168     (lambda (headline)
1169       (let* ((entry (org-odt-format-headline--wrap
1170              headline backend info 'org-odt-format-toc-headline))
1171          (level (org-export-get-relative-level headline info))
1172          (style (format "Contents_20_%d" level)))
1173         (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
1174             style entry)))
1175     headlines "\n")
1176        depth))))
1177
1178
1179 ;;;; Document styles
1180
1181 (defun org-odt-add-automatic-style (object-type &optional object-props)
1182   "Create an automatic style of type OBJECT-TYPE with param OBJECT-PROPS.
1183 OBJECT-PROPS is (typically) a plist created by passing
1184 \"#+ATTR_ODT: \" option of the object in question to
1185 `org-odt-parse-block-attributes'.
1186
1187 Use `org-odt-object-counters' to generate an automatic
1188 OBJECT-NAME and STYLE-NAME.  If OBJECT-PROPS is non-nil, add a
1189 new entry in `org-odt-automatic-styles'.  Return (OBJECT-NAME
1190 . STYLE-NAME)."
1191   (cl-assert (stringp object-type))
1192   (let* ((object (intern object-type))
1193      (seqvar object)
1194      (seqno (1+ (or (plist-get org-odt-object-counters seqvar) 0)))
1195      (object-name (format "%s%d" object-type seqno)) style-name)
1196     (setq org-odt-object-counters
1197       (plist-put org-odt-object-counters seqvar seqno))
1198     (when object-props
1199       (setq style-name (format "Org%s" object-name))
1200       (setq org-odt-automatic-styles
1201         (plist-put org-odt-automatic-styles object
1202                (append (list (list style-name object-props))
1203                    (plist-get org-odt-automatic-styles object)))))
1204     (cons object-name style-name)))
1205
1206 ;;;; Checkbox
1207
1208 (defun org-odt--checkbox (item)
1209   "Return check-box string associated to ITEM."
1210   (let ((checkbox (org-element-property :checkbox item)))
1211     (if (not checkbox) ""
1212       (format "<text:span text:style-name=\"%s\">%s</text:span>"
1213           "OrgCode" (cl-case checkbox
1214               (on "[&#x2713;] ") ; CHECK MARK
1215               (off "[ ] ")
1216               (trans "[-] "))))))
1217
1218 ;;; Template
1219
1220 (defun org-odt--build-date-styles (fmt style)
1221   ;; In LibreOffice 3.4.6, there doesn't seem to be a convenient way
1222   ;; to modify the date fields.  A date could be modified by
1223   ;; offsetting in days.  That's about it.  Also, date and time may
1224   ;; have to be emitted as two fields - a date field and a time field
1225   ;; - separately.
1226
1227   ;; One can add Form Controls to date and time fields so that they
1228   ;; can be easily modified.  But then, the exported document will
1229   ;; become tightly coupled with LibreOffice and may not function
1230   ;; properly with other OpenDocument applications.
1231
1232   ;; I have a strange feeling that Date styles are a bit flaky at the
1233   ;; moment.
1234
1235   ;; The feature is experimental.
1236   (when (and fmt style)
1237     (let* ((fmt-alist
1238         '(("%A" . "<number:day-of-week number:style=\"long\"/>")
1239           ("%B" . "<number:month number:textual=\"true\" number:style=\"long\"/>")
1240           ("%H" . "<number:hours number:style=\"long\"/>")
1241           ("%M" . "<number:minutes number:style=\"long\"/>")
1242           ("%S" . "<number:seconds number:style=\"long\"/>")
1243           ("%V" . "<number:week-of-year/>")
1244           ("%Y" . "<number:year number:style=\"long\"/>")
1245           ("%a" . "<number:day-of-week number:style=\"short\"/>")
1246           ("%b" . "<number:month number:textual=\"true\" number:style=\"short\"/>")
1247           ("%d" . "<number:day number:style=\"long\"/>")
1248           ("%e" . "<number:day number:style=\"short\"/>")
1249           ("%h" . "<number:month number:textual=\"true\" number:style=\"short\"/>")
1250           ("%k" . "<number:hours number:style=\"short\"/>")
1251           ("%m" . "<number:month number:style=\"long\"/>")
1252           ("%p" . "<number:am-pm/>")
1253           ("%y" . "<number:year number:style=\"short\"/>")))
1254        (case-fold-search nil)
1255        (re (mapconcat 'identity (mapcar 'car fmt-alist) "\\|"))
1256        match rpl (start 0) (filler-beg 0) filler-end filler output)
1257       (dolist (pair
1258            '(("\\(?:%[[:digit:]]*N\\)" . "") ; strip ns, us and ns
1259          ("%C" . "Y")        ; replace century with year
1260          ("%D" . "%m/%d/%y")
1261          ("%G" . "Y")          ; year corresponding to iso week
1262          ("%I" . "%H")          ; hour on a 12-hour clock
1263          ("%R" . "%H:%M")
1264          ("%T" . "%H:%M:%S")
1265          ("%U\\|%W" . "%V")   ; week no. starting on Sun./Mon.
1266          ("%Z" . "")          ; time zone name
1267          ("%c" . "%Y-%M-%d %a %H:%M" ) ; locale's date and time format
1268          ("%g" . "%y")
1269          ("%X" . "%x" )        ; locale's pref. time format
1270          ("%j" . "")        ; day of the year
1271          ("%l" . "%k")        ; like %I blank-padded
1272          ("%s" . "") ; no. of secs since 1970-01-01 00:00:00 +0000
1273          ("%n" . "<text:line-break/>")
1274          ("%r" . "%I:%M:%S %p")
1275          ("%t" . "<text:tab/>")
1276          ("%u\\|%w" . "") ; numeric day of week - Mon (1-7), Sun(0-6)
1277          ("%x" . "%Y-%M-%d %a")    ; locale's pref. time format
1278          ("%z" . "")        ; time zone in numeric form
1279          ))
1280     (setq fmt (replace-regexp-in-string (car pair) (cdr pair) fmt t t)))
1281       (while (string-match re fmt start)
1282     (setq match (match-string 0 fmt))
1283     (setq rpl (assoc-default match fmt-alist))
1284     (setq start (match-end 0))
1285     (setq filler-end (match-beginning 0))
1286     (setq filler (substring fmt (prog1 filler-beg
1287                       (setq filler-beg (match-end 0)))
1288                 filler-end))
1289     (setq filler (and (not (string= filler ""))
1290               (format "<number:text>%s</number:text>"
1291                   (org-odt--encode-plain-text filler))))
1292     (setq output (concat output "\n" filler "\n" rpl)))
1293       (setq filler (substring fmt filler-beg))
1294       (unless (string= filler "")
1295     (setq output (concat output
1296                  (format "\n<number:text>%s</number:text>"
1297                      (org-odt--encode-plain-text filler)))))
1298       (format "\n<number:date-style style:name=\"%s\" %s>%s\n</number:date-style>"
1299           style
1300           (concat " number:automatic-order=\"true\""
1301               " number:format-source=\"fixed\"")
1302           output ))))
1303
1304 (defun org-odt-template (contents info)
1305   "Return complete document string after ODT conversion.
1306 CONTENTS is the transcoded contents string.  RAW-DATA is the
1307 original parsed data.  INFO is a plist holding export options."
1308   ;; Write meta file.
1309   (let ((title (org-export-data (plist-get info :title) info))
1310     (subtitle (org-export-data (plist-get info :subtitle) info))
1311     (author (let ((author (plist-get info :author)))
1312           (if (not author) "" (org-export-data author info))))
1313     (keywords (or (plist-get info :keywords) ""))
1314     (description (or (plist-get info :description) "")))
1315     (write-region
1316      (concat
1317       "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
1318      <office:document-meta
1319          xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"
1320          xmlns:xlink=\"http://www.w3.org/1999/xlink\"
1321          xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
1322          xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"
1323          xmlns:ooo=\"http://openoffice.org/2004/office\"
1324          office:version=\"1.2\">
1325        <office:meta>\n"
1326       (format "<dc:creator>%s</dc:creator>\n" author)
1327       (format "<meta:initial-creator>%s</meta:initial-creator>\n" author)
1328       ;; Date, if required.
1329       (when (plist-get info :with-date)
1330     ;; Check if DATE is specified as an Org-timestamp.  If yes,
1331     ;; include it as meta information.  Otherwise, just use
1332     ;; today's date.
1333     (let* ((date (let ((date (plist-get info :date)))
1334                (and (not (cdr date))
1335                 (eq (org-element-type (car date)) 'timestamp)
1336                 (car date)))))
1337       (let ((iso-date (org-odt--format-timestamp date nil 'iso-date)))
1338         (concat
1339          (format "<dc:date>%s</dc:date>\n" iso-date)
1340          (format "<meta:creation-date>%s</meta:creation-date>\n"
1341              iso-date)))))
1342       (format "<meta:generator>%s</meta:generator>\n"
1343           (plist-get info :creator))
1344       (format "<meta:keyword>%s</meta:keyword>\n" keywords)
1345       (format "<dc:subject>%s</dc:subject>\n" description)
1346       (format "<dc:title>%s</dc:title>\n" title)
1347       (when (org-string-nw-p subtitle)
1348     (format
1349      "<meta:user-defined meta:name=\"subtitle\">%s</meta:user-defined>\n"
1350      subtitle))
1351       "\n"
1352       "  </office:meta>\n" "</office:document-meta>")
1353      nil (concat org-odt-zip-dir "meta.xml"))
1354     ;; Add meta.xml in to manifest.
1355     (org-odt-create-manifest-file-entry "text/xml" "meta.xml"))
1356
1357   ;; Update styles file.
1358   ;; Copy styles.xml.  Also dump htmlfontify styles, if there is any.
1359   ;; Write styles file.
1360   (let* ((styles-file (plist-get info :odt-styles-file))
1361      (styles-file (and (org-string-nw-p styles-file)
1362                (org-trim styles-file)))
1363      ;; Non-availability of styles.xml is not a critical
1364      ;; error. For now, throw an error.
1365      (styles-file (or styles-file
1366               (expand-file-name "OrgOdtStyles.xml"
1367                         org-odt-styles-dir)
1368               (error "org-odt: Missing styles file?"))))
1369     (cond
1370      ((listp styles-file)
1371       (let ((archive (nth 0 styles-file))
1372         (members (nth 1 styles-file)))
1373     (org-odt--zip-extract archive members org-odt-zip-dir)
1374     (dolist (member members)
1375       (when (org-file-image-p member)
1376         (let* ((image-type (file-name-extension member))
1377            (media-type (format "image/%s" image-type)))
1378           (org-odt-create-manifest-file-entry media-type member))))))
1379      ((and (stringp styles-file) (file-exists-p styles-file))
1380       (let ((styles-file-type (file-name-extension styles-file)))
1381     (cond
1382      ((string= styles-file-type "xml")
1383       (copy-file styles-file (concat org-odt-zip-dir "styles.xml") t))
1384      ((member styles-file-type '("odt" "ott"))
1385       (org-odt--zip-extract styles-file "styles.xml" org-odt-zip-dir)))))
1386      (t
1387       (error "Invalid specification of styles.xml file: %S"
1388          (plist-get info :odt-styles-file))))
1389
1390     ;; create a manifest entry for styles.xml
1391     (org-odt-create-manifest-file-entry "text/xml" "styles.xml")
1392
1393     ;; FIXME: Who is opening an empty styles.xml before this point?
1394     (with-current-buffer
1395     (find-file-noselect (concat org-odt-zip-dir "styles.xml") t)
1396       (revert-buffer t t)
1397
1398       ;; Write custom styles for source blocks
1399       ;; Save STYLES used for colorizing of source blocks.
1400       ;; Update styles.xml with styles that were collected as part of
1401       ;; `org-odt-hfy-face-to-css' callbacks.
1402       (let ((styles (mapconcat (lambda (style) (format " %s\n" (cddr style)))
1403                    hfy-user-sheet-assoc "")))
1404     (when styles
1405       (goto-char (point-min))
1406       (when (re-search-forward "</office:styles>" nil t)
1407         (goto-char (match-beginning 0))
1408         (insert "\n<!-- Org Htmlfontify Styles -->\n" styles "\n"))))
1409
1410       ;; Update styles.xml - take care of outline numbering
1411
1412       ;; Don't make automatic backup of styles.xml file. This setting
1413       ;; prevents the backed-up styles.xml file from being zipped in to
1414       ;; odt file. This is more of a hackish fix. Better alternative
1415       ;; would be to fix the zip command so that the output odt file
1416       ;; includes only the needed files and excludes any auto-generated
1417       ;; extra files like backups and auto-saves etc etc. Note that
1418       ;; currently the zip command zips up the entire temp directory so
1419       ;; that any auto-generated files created under the hood ends up in
1420       ;; the resulting odt file.
1421       (setq-local backup-inhibited t)
1422
1423       ;; Outline numbering is retained only upto LEVEL.
1424       ;; To disable outline numbering pass a LEVEL of 0.
1425
1426       (goto-char (point-min))
1427       (let ((regex
1428          "<text:outline-level-style\\([^>]*\\)text:level=\"\\([^\"]*\\)\"\\([^>]*\\)>")
1429         (replacement
1430          "<text:outline-level-style\\1text:level=\"\\2\" style:num-format=\"\">"))
1431     (while (re-search-forward regex nil t)
1432       (unless (let ((sec-num (plist-get info :section-numbers))
1433             (level (string-to-number (match-string 2))))
1434             (if (wholenump sec-num) (<= level sec-num) sec-num))
1435         (replace-match replacement t nil))))
1436       (save-buffer 0)))
1437   ;; Update content.xml.
1438
1439   (let* ( ;; `org-display-custom-times' should be accessed right
1440      ;; within the context of the Org buffer.  So obtain its
1441      ;; value before moving on to temp-buffer context down below.
1442      (custom-time-fmts
1443       (if org-display-custom-times
1444           (cons (substring (car org-time-stamp-custom-formats) 1 -1)
1445             (substring (cdr org-time-stamp-custom-formats) 1 -1))
1446         '("%Y-%M-%d %a" . "%Y-%M-%d %a %H:%M"))))
1447     (with-temp-buffer
1448       (insert-file-contents
1449        (or (plist-get info :odt-content-template-file)
1450        (expand-file-name "OrgOdtContentTemplate.xml"
1451                  org-odt-styles-dir)))
1452       ;; Write automatic styles.
1453       ;; - Position the cursor.
1454       (goto-char (point-min))
1455       (re-search-forward "  </office:automatic-styles>" nil t)
1456       (goto-char (match-beginning 0))
1457       ;; - Dump automatic table styles.
1458       (cl-loop for (style-name props) in
1459            (plist-get org-odt-automatic-styles 'Table) do
1460            (when (setq props (or (plist-get props :rel-width) "96"))
1461          (insert (format org-odt-table-style-format style-name props))))
1462       ;; - Dump date-styles.
1463       (when (plist-get info :odt-use-date-fields)
1464     (insert (org-odt--build-date-styles (car custom-time-fmts)
1465                         "OrgDate1")
1466         (org-odt--build-date-styles (cdr custom-time-fmts)
1467                         "OrgDate2")))
1468       ;; Update display level.
1469       ;; - Remove existing sequence decls.  Also position the cursor.
1470       (goto-char (point-min))
1471       (when (re-search-forward "<text:sequence-decls" nil t)
1472     (delete-region (match-beginning 0)
1473                (re-search-forward "</text:sequence-decls>" nil nil)))
1474       ;; Update sequence decls according to user preference.
1475       (insert
1476        (format
1477     "\n<text:sequence-decls>\n%s\n</text:sequence-decls>"
1478     (mapconcat
1479      (lambda (x)
1480        (format
1481         "<text:sequence-decl text:display-outline-level=\"%d\" text:name=\"%s\"/>"
1482         (plist-get info :odt-display-outline-level)
1483         (nth 1 x)))
1484      org-odt-category-map-alist "\n")))
1485       ;; Position the cursor to document body.
1486       (goto-char (point-min))
1487       (re-search-forward "</office:text>" nil nil)
1488       (goto-char (match-beginning 0))
1489
1490       ;; Preamble - Title, Author, Date etc.
1491       (insert
1492        (let* ((title (and (plist-get info :with-title)
1493               (org-export-data (plist-get info :title) info)))
1494           (subtitle (when title
1495               (org-export-data (plist-get info :subtitle) info)))
1496           (author (and (plist-get info :with-author)
1497                (let ((auth (plist-get info :author)))
1498                  (and auth (org-export-data auth info)))))
1499           (email (plist-get info :email))
1500           ;; Switch on or off above vars based on user settings
1501           (author (and (plist-get info :with-author) (or author email)))
1502           (email (and (plist-get info :with-email) email)))
1503      (concat
1504       ;; Title.
1505       (when (org-string-nw-p title)
1506         (concat
1507          (format "\n<text:p text:style-name=\"%s\">%s</text:p>\n"
1508              "OrgTitle" (format "\n<text:title>%s</text:title>" title))
1509          ;; Separator.
1510          "\n<text:p text:style-name=\"OrgTitle\"/>\n"
1511          ;; Subtitle.
1512          (when (org-string-nw-p subtitle)
1513            (concat
1514         (format "<text:p text:style-name=\"OrgSubtitle\">\n%s\n</text:p>\n"
1515             (concat
1516              "<text:user-defined style:data-style-name=\"N0\" text:name=\"subtitle\">\n"
1517              subtitle
1518              "</text:user-defined>\n"))
1519         ;; Separator.
1520         "<text:p text:style-name=\"OrgSubtitle\"/>\n"))))
1521       (cond
1522        ((and author (not email))
1523         ;; Author only.
1524         (concat
1525          (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
1526              "OrgSubtitle"
1527              (format "<text:initial-creator>%s</text:initial-creator>" author))
1528          ;; Separator.
1529          "\n<text:p text:style-name=\"OrgSubtitle\"/>"))
1530        ((and author email)
1531         ;; Author and E-mail.
1532         (concat
1533          (format
1534           "\n<text:p text:style-name=\"%s\">%s</text:p>"
1535           "OrgSubtitle"
1536           (format
1537            "<text:a xlink:type=\"simple\" xlink:href=\"%s\">%s</text:a>"
1538            (concat "mailto:" email)
1539            (format "<text:initial-creator>%s</text:initial-creator>" author)))
1540          ;; Separator.
1541          "\n<text:p text:style-name=\"OrgSubtitle\"/>")))
1542       ;; Date, if required.
1543       (when (plist-get info :with-date)
1544         (let* ((date (plist-get info :date))
1545            ;; Check if DATE is specified as a timestamp.
1546            (timestamp (and (not (cdr date))
1547                    (eq (org-element-type (car date)) 'timestamp)
1548                    (car date))))
1549           (when date
1550         (concat
1551          (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
1552              "OrgSubtitle"
1553              (if (and (plist-get info :odt-use-date-fields) timestamp)
1554                  (org-odt--format-timestamp (car date))
1555                (org-export-data date info)))
1556          ;; Separator
1557          "<text:p text:style-name=\"OrgSubtitle\"/>")))))))
1558       ;; Table of Contents
1559       (let* ((with-toc (plist-get info :with-toc))
1560          (depth (and with-toc (if (wholenump with-toc)
1561                       with-toc
1562                     (plist-get info :headline-levels)))))
1563     (when depth (insert (or (org-odt-toc depth info) ""))))
1564       ;; Contents.
1565       (insert contents)
1566       ;; Return contents.
1567       (buffer-substring-no-properties (point-min) (point-max)))))
1568
1569
1570
1571 ;;; Transcode Functions
1572
1573 ;;;; Bold
1574
1575 (defun org-odt-bold (_bold contents _info)
1576   "Transcode BOLD from Org to ODT.
1577 CONTENTS is the text with bold markup.  INFO is a plist holding
1578 contextual information."
1579   (format "<text:span text:style-name=\"%s\">%s</text:span>"
1580       "Bold" contents))
1581
1582
1583 ;;;; Center Block
1584
1585 (defun org-odt-center-block (_center-block contents _info)
1586   "Transcode a CENTER-BLOCK element from Org to ODT.
1587 CONTENTS holds the contents of the center block.  INFO is a plist
1588 holding contextual information."
1589   contents)
1590
1591
1592 ;;;; Clock
1593
1594 (defun org-odt-clock (clock contents info)
1595   "Transcode a CLOCK element from Org to ODT.
1596 CONTENTS is nil.  INFO is a plist used as a communication
1597 channel."
1598   (let ((timestamp (org-element-property :value clock))
1599     (duration (org-element-property :duration clock)))
1600     (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
1601         (if (eq (org-element-type (org-export-get-next-element clock info))
1602             'clock) "OrgClock" "OrgClockLastLine")
1603         (concat
1604          (format "<text:span text:style-name=\"%s\">%s</text:span>"
1605              "OrgClockKeyword" org-clock-string)
1606          (org-odt-timestamp timestamp contents info)
1607          (and duration (format " (%s)" duration))))))
1608
1609
1610 ;;;; Code
1611
1612 (defun org-odt-code (code _contents _info)
1613   "Transcode a CODE object from Org to ODT.
1614 CONTENTS is nil.  INFO is a plist used as a communication
1615 channel."
1616   (format "<text:span text:style-name=\"%s\">%s</text:span>"
1617       "OrgCode" (org-odt--encode-plain-text
1618              (org-element-property :value code))))
1619
1620
1621 ;;;; Drawer
1622
1623 (defun org-odt-drawer (drawer contents info)
1624   "Transcode a DRAWER element from Org to ODT.
1625 CONTENTS holds the contents of the block.  INFO is a plist
1626 holding contextual information."
1627   (let* ((name (org-element-property :drawer-name drawer))
1628      (output (funcall (plist-get info :odt-format-drawer-function)
1629               name contents)))
1630     output))
1631
1632
1633 ;;;; Dynamic Block
1634
1635 (defun org-odt-dynamic-block (_dynamic-block contents _info)
1636   "Transcode a DYNAMIC-BLOCK element from Org to ODT.
1637 CONTENTS holds the contents of the block.  INFO is a plist
1638 holding contextual information.  See `org-export-data'."
1639   contents)
1640
1641
1642 ;;;; Entity
1643
1644 (defun org-odt-entity (entity _contents _info)
1645   "Transcode an ENTITY object from Org to ODT.
1646 CONTENTS are the definition itself.  INFO is a plist holding
1647 contextual information."
1648   (org-element-property :utf-8 entity))
1649
1650
1651 ;;;; Example Block
1652
1653 (defun org-odt-example-block (example-block _contents info)
1654   "Transcode a EXAMPLE-BLOCK element from Org to ODT.
1655 CONTENTS is nil.  INFO is a plist holding contextual information."
1656   (org-odt-format-code example-block info))
1657
1658
1659 ;;;; Export Snippet
1660
1661 (defun org-odt-export-snippet (export-snippet _contents _info)
1662   "Transcode a EXPORT-SNIPPET object from Org to ODT.
1663 CONTENTS is nil.  INFO is a plist holding contextual information."
1664   (when (eq (org-export-snippet-backend export-snippet) 'odt)
1665     (org-element-property :value export-snippet)))
1666
1667
1668 ;;;; Export Block
1669
1670 (defun org-odt-export-block (export-block _contents _info)
1671   "Transcode a EXPORT-BLOCK element from Org to ODT.
1672 CONTENTS is nil.  INFO is a plist holding contextual information."
1673   (when (string= (org-element-property :type export-block) "ODT")
1674     (org-remove-indentation (org-element-property :value export-block))))
1675
1676
1677 ;;;; Fixed Width
1678
1679 (defun org-odt-fixed-width (fixed-width _contents info)
1680   "Transcode a FIXED-WIDTH element from Org to ODT.
1681 CONTENTS is nil.  INFO is a plist holding contextual information."
1682   (org-odt-do-format-code (org-element-property :value fixed-width) info))
1683
1684
1685 ;;;; Footnote Definition
1686
1687 ;; Footnote Definitions are ignored.
1688
1689
1690 ;;;; Footnote Reference
1691
1692 (defun org-odt-footnote-reference (footnote-reference _contents info)
1693   "Transcode a FOOTNOTE-REFERENCE element from Org to ODT.
1694 CONTENTS is nil.  INFO is a plist holding contextual information."
1695   (let ((--format-footnote-definition
1696      (lambda (n def)
1697        (setq n (format "%d" n))
1698        (let ((id (concat  "fn" n))
1699          (note-class "footnote"))
1700          (format
1701           "<text:note text:id=\"%s\" text:note-class=\"%s\">%s</text:note>"
1702           id note-class
1703           (concat
1704            (format "<text:note-citation>%s</text:note-citation>" n)
1705            (format "<text:note-body>%s</text:note-body>" def))))))
1706     (--format-footnote-reference
1707      (lambda (n)
1708        (setq n (format "%d" n))
1709        (let ((note-class "footnote")
1710          (ref-format "text")
1711          (ref-name (concat "fn" n)))
1712          (format
1713           "<text:span text:style-name=\"%s\">%s</text:span>"
1714           "OrgSuperscript"
1715           (format "<text:note-ref text:note-class=\"%s\" text:reference-format=\"%s\" text:ref-name=\"%s\">%s</text:note-ref>"
1716               note-class ref-format ref-name n))))))
1717     (concat
1718      ;; Insert separator between two footnotes in a row.
1719      (let ((prev (org-export-get-previous-element footnote-reference info)))
1720        (and (eq (org-element-type prev) 'footnote-reference)
1721         (format "<text:span text:style-name=\"%s\">%s</text:span>"
1722             "OrgSuperscript" ",")))
1723      ;; Transcode footnote reference.
1724      (let ((n (org-export-get-footnote-number footnote-reference info nil t)))
1725        (cond
1726     ((not
1727       (org-export-footnote-first-reference-p footnote-reference info nil t))
1728      (funcall --format-footnote-reference n))
1729     (t
1730      (let* ((raw (org-export-get-footnote-definition
1731               footnote-reference info))
1732         (def
1733          (let ((def (org-trim
1734                  (org-export-data-with-backend
1735                   raw
1736                   (org-export-create-backend
1737                    :parent 'odt
1738                    :transcoders
1739                    '((paragraph . (lambda (p c i)
1740                         (org-odt--format-paragraph
1741                          p c i
1742                          "Footnote"
1743                          "OrgFootnoteCenter"
1744                          "OrgFootnoteQuotations")))))
1745                   info))))
1746            ;; Inline definitions are secondary strings.  We
1747            ;; need to wrap them within a paragraph.
1748            (if (eq (org-element-class (car (org-element-contents raw)))
1749                'element)
1750                def
1751              (format
1752               "\n<text:p text:style-name=\"Footnote\">%s</text:p>"
1753               def)))))
1754        (funcall --format-footnote-definition n def))))))))
1755
1756
1757 ;;;; Headline
1758
1759 (defun org-odt-format-headline--wrap (headline backend info
1760                            &optional format-function
1761                            &rest extra-keys)
1762   "Transcode a HEADLINE element using BACKEND.
1763 INFO is a plist holding contextual information."
1764   (setq backend (or backend (plist-get info :back-end)))
1765   (let* ((level (+ (org-export-get-relative-level headline info)))
1766      (headline-number (org-export-get-headline-number headline info))
1767      (section-number (and (org-export-numbered-headline-p headline info)
1768                   (mapconcat 'number-to-string
1769                      headline-number ".")))
1770      (todo (and (plist-get info :with-todo-keywords)
1771             (let ((todo (org-element-property :todo-keyword headline)))
1772               (and todo
1773                (org-export-data-with-backend todo backend info)))))
1774      (todo-type (and todo (org-element-property :todo-type headline)))
1775      (priority (and (plist-get info :with-priority)
1776             (org-element-property :priority headline)))
1777      (text (org-export-data-with-backend
1778         (org-element-property :title headline) backend info))
1779      (tags (and (plist-get info :with-tags)
1780             (org-export-get-tags headline info)))
1781      (headline-label (org-export-get-reference headline info))
1782      (format-function
1783       (if (functionp format-function) format-function
1784         (cl-function
1785          (lambda (todo todo-type priority text tags
1786               &key _level _section-number _headline-label
1787               &allow-other-keys)
1788            (funcall (plist-get info :odt-format-headline-function)
1789             todo todo-type priority text tags))))))
1790     (apply format-function
1791        todo todo-type priority text tags
1792        :headline-label headline-label
1793        :level level
1794        :section-number section-number extra-keys)))
1795
1796 (defun org-odt-headline (headline contents info)
1797   "Transcode a HEADLINE element from Org to ODT.
1798 CONTENTS holds the contents of the headline.  INFO is a plist
1799 holding contextual information."
1800   ;; Case 1: This is a footnote section: ignore it.
1801   (unless (org-element-property :footnote-section-p headline)
1802     (let* ((full-text (org-odt-format-headline--wrap headline nil info))
1803        ;; Get level relative to current parsed data.
1804        (level (org-export-get-relative-level headline info))
1805        (numbered (org-export-numbered-headline-p headline info))
1806        ;; Get canonical label for the headline.
1807        (id (org-export-get-reference headline info))
1808        ;; Extra targets.
1809        (extra-targets
1810         (let ((id (org-element-property :ID headline)))
1811           (if id (org-odt--target "" (concat "ID-" id)) "")))
1812        ;; Title.
1813        (anchored-title (org-odt--target full-text id)))
1814       (cond
1815        ;; Case 2. This is a deep sub-tree: export it as a list item.
1816        ;;         Also export as items headlines for which no section
1817        ;;         format has been found.
1818        ((org-export-low-level-p headline info)
1819     ;; Build the real contents of the sub-tree.
1820     (concat
1821      (and (org-export-first-sibling-p headline info)
1822           (format "\n<text:list text:style-name=\"%s\" %s>"
1823               ;; Choose style based on list type.
1824               (if numbered "OrgNumberedList" "OrgBulletedList")
1825               ;; If top-level list, re-start numbering.  Otherwise,
1826               ;; continue numbering.
1827               (format "text:continue-numbering=\"%s\""
1828                   (let* ((parent (org-export-get-parent-headline
1829                           headline)))
1830                 (if (and parent
1831                      (org-export-low-level-p parent info))
1832                     "true" "false")))))
1833      (let ((headline-has-table-p
1834         (let ((section (assq 'section (org-element-contents headline))))
1835           (assq 'table (and section (org-element-contents section))))))
1836        (format "\n<text:list-item>\n%s\n%s"
1837            (concat
1838             (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
1839                 "Text_20_body"
1840                 (concat extra-targets anchored-title))
1841             contents)
1842            (if headline-has-table-p
1843                "</text:list-header>"
1844              "</text:list-item>")))
1845      (and (org-export-last-sibling-p headline info)
1846           "</text:list>")))
1847        ;; Case 3. Standard headline.  Export it as a section.
1848        (t
1849     (concat
1850      (format
1851       "\n<text:h text:style-name=\"%s\" text:outline-level=\"%s\" text:is-list-header=\"%s\">%s</text:h>"
1852       (format "Heading_20_%s%s"
1853           level (if numbered "" "_unnumbered"))
1854       level
1855       (if numbered "false" "true")
1856       (concat extra-targets anchored-title))
1857      contents))))))
1858
1859 (defun org-odt-format-headline-default-function
1860   (todo todo-type priority text tags)
1861   "Default format function for a headline.
1862 See `org-odt-format-headline-function' for details."
1863   (concat
1864    ;; Todo.
1865    (when todo
1866      (let ((style (if (eq todo-type 'done) "OrgDone" "OrgTodo")))
1867        (format "<text:span text:style-name=\"%s\">%s</text:span> " style todo)))
1868    (when priority
1869      (let* ((style (format "OrgPriority-%c" priority))
1870         (priority (format "[#%c]" priority)))
1871        (format "<text:span text:style-name=\"%s\">%s</text:span> "
1872            style priority)))
1873    ;; Title.
1874    text
1875    ;; Tags.
1876    (when tags
1877      (concat
1878       "<text:tab/>"
1879       (format "<text:span text:style-name=\"%s\">[%s]</text:span>"
1880           "OrgTags" (mapconcat
1881              (lambda (tag)
1882                (format
1883                 "<text:span text:style-name=\"%s\">%s</text:span>"
1884                 "OrgTag" tag)) tags " : "))))))
1885
1886
1887 ;;;; Horizontal Rule
1888
1889 (defun org-odt-horizontal-rule (_horizontal-rule _contents _info)
1890   "Transcode an HORIZONTAL-RULE  object from Org to ODT.
1891 CONTENTS is nil.  INFO is a plist holding contextual information."
1892   (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
1893       "Horizontal_20_Line" ""))
1894
1895
1896 ;;;; Inline Babel Call
1897
1898 ;; Inline Babel Calls are ignored.
1899
1900
1901 ;;;; Inline Src Block
1902
1903 (defun org-odt--find-verb-separator (s)
1904   "Return a character not used in string S.
1905 This is used to choose a separator for constructs like \\verb."
1906   (let ((ll "~,./?;':\"|!@#%^&-_=+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>()[]{}"))
1907     (cl-loop for c across ll
1908          when (not (string-match (regexp-quote (char-to-string c)) s))
1909          return (char-to-string c))))
1910
1911 (defun org-odt-inline-src-block (_inline-src-block _contents _info)
1912   "Transcode an INLINE-SRC-BLOCK element from Org to ODT.
1913 CONTENTS holds the contents of the item.  INFO is a plist holding
1914 contextual information."
1915   (error "FIXME"))
1916
1917
1918 ;;;; Inlinetask
1919
1920 (defun org-odt-inlinetask (inlinetask contents info)
1921   "Transcode an INLINETASK element from Org to ODT.
1922 CONTENTS holds the contents of the block.  INFO is a plist
1923 holding contextual information."
1924   (let* ((todo
1925       (and (plist-get info :with-todo-keywords)
1926            (let ((todo (org-element-property :todo-keyword inlinetask)))
1927          (and todo (org-export-data todo info)))))
1928      (todo-type (and todo (org-element-property :todo-type inlinetask)))
1929      (priority (and (plist-get info :with-priority)
1930             (org-element-property :priority inlinetask)))
1931      (text (org-export-data (org-element-property :title inlinetask) info))
1932      (tags (and (plist-get info :with-tags)
1933             (org-export-get-tags inlinetask info))))
1934     (funcall (plist-get info :odt-format-inlinetask-function)
1935          todo todo-type priority text tags contents)))
1936
1937 (defun org-odt-format-inlinetask-default-function
1938   (todo todo-type priority name tags contents)
1939   "Default format function for inlinetasks.
1940 See `org-odt-format-inlinetask-function' for details."
1941   (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
1942       "Text_20_body"
1943       (org-odt--textbox
1944        (concat
1945         (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
1946             "OrgInlineTaskHeading"
1947             (org-odt-format-headline-default-function
1948              todo todo-type priority name tags))
1949         contents)
1950        nil nil "OrgInlineTaskFrame" " style:rel-width=\"100%\"")))
1951
1952 ;;;; Italic
1953
1954 (defun org-odt-italic (_italic contents _info)
1955   "Transcode ITALIC from Org to ODT.
1956 CONTENTS is the text with italic markup.  INFO is a plist holding
1957 contextual information."
1958   (format "<text:span text:style-name=\"%s\">%s</text:span>"
1959       "Emphasis" contents))
1960
1961
1962 ;;;; Item
1963
1964 (defun org-odt-item (item contents info)
1965   "Transcode an ITEM element from Org to ODT.
1966 CONTENTS holds the contents of the item.  INFO is a plist holding
1967 contextual information."
1968   (let* ((plain-list (org-export-get-parent item))
1969      (type (org-element-property :type plain-list)))
1970     (unless (memq type '(ordered unordered descriptive-1 descriptive-2))
1971       (error "Unknown list type: %S" type))
1972     (format "\n<text:list-item>\n%s\n%s"
1973         contents
1974         (if (org-element-map item 'table #'identity info 'first-match)
1975         "</text:list-header>"
1976           "</text:list-item>"))))
1977
1978 ;;;; Keyword
1979
1980 (defun org-odt-keyword (keyword _contents info)
1981   "Transcode a KEYWORD element from Org to ODT.
1982 CONTENTS is nil.  INFO is a plist holding contextual
1983 information."
1984   (let ((key (org-element-property :key keyword))
1985     (value (org-element-property :value keyword)))
1986     (cond
1987      ((string= key "ODT") value)
1988      ((string= key "INDEX")
1989       ;; FIXME
1990       (ignore))
1991      ((string= key "TOC")
1992       (let ((case-fold-search t))
1993     (cond
1994      ((string-match-p "\\<headlines\\>" value)
1995       (let ((depth (or (and (string-match "\\<[0-9]+\\>" value)
1996                 (string-to-number (match-string 0 value)))
1997                (plist-get info :headline-levels)))
1998         (localp (string-match-p "\\<local\\>" value)))
1999         (org-odt-toc depth info (and localp keyword))))
2000      ((string-match-p "tables\\|figures\\|listings" value)
2001       ;; FIXME
2002       (ignore))))))))
2003
2004
2005 ;;;; Latex Environment
2006
2007
2008 ;; (eval-after-load 'ox-odt '(ad-deactivate 'org-format-latex-as-mathml))
2009 ;; (defadvice org-format-latex-as-mathml    ; FIXME
2010 ;;   (after org-odt-protect-latex-fragment activate)
2011 ;;   "Encode LaTeX fragment as XML.
2012 ;; Do this when translation to MathML fails."
2013 ;;   (unless (> (length ad-return-value) 0)
2014 ;;     (setq ad-return-value (org-odt--encode-plain-text (ad-get-arg 0)))))
2015
2016 (defun org-odt-latex-environment (latex-environment _contents info)
2017   "Transcode a LATEX-ENVIRONMENT element from Org to ODT.
2018 CONTENTS is nil.  INFO is a plist holding contextual information."
2019   (let* ((latex-frag (org-remove-indentation
2020               (org-element-property :value latex-environment))))
2021     (org-odt-do-format-code latex-frag info)))
2022
2023
2024 ;;;; Latex Fragment
2025
2026 ;; (when latex-frag            ; FIXME
2027 ;;     (setq href (propertize href :title "LaTeX Fragment"
2028 ;;                    :description latex-frag)))
2029 ;; handle verbatim
2030 ;; provide descriptions
2031
2032 (defun org-odt-latex-fragment (latex-fragment _contents _info)
2033   "Transcode a LATEX-FRAGMENT object from Org to ODT.
2034 CONTENTS is nil.  INFO is a plist holding contextual information."
2035   (let ((latex-frag (org-element-property :value latex-fragment)))
2036     (format "<text:span text:style-name=\"%s\">%s</text:span>"
2037         "OrgCode" (org-odt--encode-plain-text latex-frag t))))
2038
2039
2040 ;;;; Line Break
2041
2042 (defun org-odt-line-break (_line-break _contents _info)
2043   "Transcode a LINE-BREAK object from Org to ODT.
2044 CONTENTS is nil.  INFO is a plist holding contextual information."
2045   "<text:line-break/>")
2046
2047
2048 ;;;; Link
2049
2050 ;;;; Links :: Label references
2051
2052 (defun org-odt--enumerate (element info &optional predicate n)
2053   (when predicate (cl-assert (funcall predicate element info)))
2054   (let* ((--numbered-parent-headline-at-<=-n
2055       (lambda (element n info)
2056         (cl-loop for x in (org-element-lineage element)
2057              thereis (and (eq (org-element-type x) 'headline)
2058                   (<= (org-export-get-relative-level x info) n)
2059                   (org-export-numbered-headline-p x info)
2060                   x))))
2061      (--enumerate
2062       (lambda (element scope info &optional predicate)
2063         (let ((counter 0))
2064           (org-element-map (or scope (plist-get info :parse-tree))
2065           (org-element-type element)
2066         (lambda (el)
2067           (and (or (not predicate) (funcall predicate el info))
2068                (cl-incf counter)
2069                (eq element el)
2070                counter))
2071         info 'first-match))))
2072      (scope (funcall --numbered-parent-headline-at-<=-n
2073              element
2074              (or n (plist-get info :odt-display-outline-level))
2075              info))
2076      (ordinal (funcall --enumerate element scope info predicate))
2077      (tag
2078       (concat
2079        ;; Section number.
2080        (and scope
2081         (mapconcat 'number-to-string
2082                (org-export-get-headline-number scope info) "."))
2083        ;; Separator.
2084        (and scope ".")
2085        ;; Ordinal.
2086        (number-to-string ordinal))))
2087     tag))
2088
2089 (defun org-odt-format-label (element info op)
2090   "Return a label for ELEMENT.
2091
2092 ELEMENT is a `link', `table', `src-block' or `paragraph' type
2093 element.  INFO is a plist used as a communication channel.  OP is
2094 either `definition' or `reference', depending on the purpose of
2095 the generated string.
2096
2097 Return value is a string if OP is set to `reference' or a cons
2098 cell like CAPTION . SHORT-CAPTION) where CAPTION and
2099 SHORT-CAPTION are strings."
2100   (cl-assert (memq (org-element-type element) '(link table src-block paragraph)))
2101   (let* ((element-or-parent
2102       (cl-case (org-element-type element)
2103         (link (org-export-get-parent-element element))
2104         (t element)))
2105      ;; Get label and caption.
2106      (label (and (or (org-element-property :name element)
2107              (org-element-property :name element-or-parent))
2108              (org-export-get-reference element-or-parent info)))
2109      (caption (let ((c (org-export-get-caption element-or-parent)))
2110             (and c (org-export-data c info))))
2111      ;; FIXME: We don't use short-caption for now
2112      (short-caption nil))
2113     (when (or label caption)
2114       (let* ((default-category
2115            (cl-case (org-element-type element)
2116          (table "__Table__")
2117          (src-block "__Listing__")
2118          ((link paragraph)
2119           (cond
2120            ((org-odt--enumerable-latex-image-p element info)
2121             "__DvipngImage__")
2122            ((org-odt--enumerable-image-p element info)
2123             "__Figure__")
2124            ((org-odt--enumerable-formula-p element info)
2125             "__MathFormula__")
2126            (t (error "Don't know how to format label for link: %S"
2127                  element))))
2128          (t (error "Don't know how to format label for element type: %s"
2129                (org-element-type element)))))
2130          seqno)
2131     (cl-assert default-category)
2132     (pcase-let
2133         ((`(,counter ,label-style ,category ,predicate)
2134           (assoc-default default-category org-odt-category-map-alist)))
2135       ;; Compute sequence number of the element.
2136       (setq seqno (org-odt--enumerate element info predicate))
2137       ;; Localize category string.
2138       (setq category (org-export-translate category :utf-8 info))
2139       (cl-case op
2140         ;; Case 1: Handle Label definition.
2141         (definition
2142           (cons
2143            (concat
2144         ;; Sneak in a bookmark.  The bookmark is used when the
2145         ;; labeled element is referenced with a link that
2146         ;; provides its own description.
2147         (format "\n<text:bookmark text:name=\"%s\"/>" label)
2148         ;; Label definition: Typically formatted as below:
2149         ;;     CATEGORY SEQ-NO: LONG CAPTION
2150         ;; with translation for correct punctuation.
2151         (format-spec
2152          (org-export-translate
2153           (cadr (assoc-string label-style org-odt-label-styles t))
2154           :utf-8 info)
2155          `((?e . ,category)
2156            (?n . ,(format
2157                "<text:sequence text:ref-name=\"%s\" text:name=\"%s\" text:formula=\"ooow:%s+1\" style:num-format=\"1\">%s</text:sequence>"
2158                label counter counter seqno))
2159            (?c . ,(or caption "")))))
2160            short-caption))
2161         ;; Case 2: Handle Label reference.
2162         (reference
2163          (let* ((fmt (cddr (assoc-string label-style org-odt-label-styles t)))
2164             (fmt1 (car fmt))
2165             (fmt2 (cadr fmt)))
2166            (format "<text:sequence-ref text:reference-format=\"%s\" text:ref-name=\"%s\">%s</text:sequence-ref>"
2167                fmt1
2168                label
2169                (format-spec fmt2 `((?e . ,category) (?n . ,seqno))))))
2170         (t (error "Unknown %S on label" op))))))))
2171
2172
2173 ;;;; Links :: Inline Images
2174
2175 (defun org-odt--copy-image-file (path)
2176   "Returns the internal name of the file"
2177   (let* ((image-type (file-name-extension path))
2178      (media-type (format "image/%s" image-type))
2179      (target-dir "Images/")
2180      (target-file
2181       (format "%s%04d.%s" target-dir
2182           (cl-incf org-odt-embedded-images-count) image-type)))
2183     (message "Embedding %s as %s..."
2184          (substring-no-properties path) target-file)
2185
2186     (when (= 1 org-odt-embedded-images-count)
2187       (make-directory (concat org-odt-zip-dir target-dir))
2188       (org-odt-create-manifest-file-entry "" target-dir))
2189
2190     (copy-file path (concat org-odt-zip-dir target-file) 'overwrite)
2191     (org-odt-create-manifest-file-entry media-type target-file)
2192     target-file))
2193
2194 (defun org-odt--image-size
2195   (file info &optional user-width user-height scale dpi embed-as)
2196   (let* ((--pixels-to-cms
2197       (function (lambda (pixels dpi)
2198               (let ((cms-per-inch 2.54)
2199                 (inches (/ pixels dpi)))
2200             (* cms-per-inch inches)))))
2201      (--size-in-cms
2202       (function
2203        (lambda (size-in-pixels dpi)
2204          (and size-in-pixels
2205           (cons (funcall --pixels-to-cms (car size-in-pixels) dpi)
2206             (funcall --pixels-to-cms (cdr size-in-pixels) dpi))))))
2207      (dpi (or dpi (plist-get info :odt-pixels-per-inch)))
2208      (anchor-type (or embed-as "paragraph"))
2209      (user-width (and (not scale) user-width))
2210      (user-height (and (not scale) user-height))
2211      (size
2212       (and
2213        (not (and user-height user-width))
2214        (or
2215         ;; Use Imagemagick.
2216         (and (executable-find "identify")
2217          (let ((size-in-pixels
2218             (let ((dim (shell-command-to-string
2219                     (format "identify -format \"%%w:%%h\" \"%s\""
2220                         file))))
2221               (when (string-match "\\([0-9]+\\):\\([0-9]+\\)" dim)
2222                 (cons (string-to-number (match-string 1 dim))
2223                   (string-to-number (match-string 2 dim)))))))
2224            (funcall --size-in-cms size-in-pixels dpi)))
2225         ;; Use Emacs.
2226         (let ((size-in-pixels
2227            (ignore-errors    ; Emacs could be in batch mode
2228              (clear-image-cache)
2229              (image-size (create-image file) 'pixels))))
2230           (funcall --size-in-cms size-in-pixels dpi))
2231         ;; Use hard-coded values.
2232         (cdr (assoc-string anchor-type
2233                    org-odt-default-image-sizes-alist))
2234         ;; Error out.
2235         (error "Cannot determine image size, aborting"))))
2236      (width (car size)) (height (cdr size)))
2237     (cond
2238      (scale
2239       (setq width (* width scale) height (* height scale)))
2240      ((and user-height user-width)
2241       (setq width user-width height user-height))
2242      (user-height
2243       (setq width (* user-height (/ width height)) height user-height))
2244      (user-width
2245       (setq height (* user-width (/ height width)) width user-width))
2246      (t (ignore)))
2247     ;; ensure that an embedded image fits comfortably within a page
2248     (let ((max-width (car org-odt-max-image-size))
2249       (max-height (cdr org-odt-max-image-size)))
2250       (when (or (> width max-width) (> height max-height))
2251     (let* ((scale1 (/ max-width width))
2252            (scale2 (/ max-height height))
2253            (scale (min scale1 scale2)))
2254       (setq width (* scale width) height (* scale height)))))
2255     (cons width height)))
2256
2257 (defun org-odt-link--inline-image (element info)
2258   "Return ODT code for an inline image.
2259 LINK is the link pointing to the inline image.  INFO is a plist
2260 used as a communication channel."
2261   (cl-assert (eq (org-element-type element) 'link))
2262   (let* ((src (let* ((type (org-element-property :type element))
2263              (raw-path (org-element-property :path element)))
2264         (cond ((member type '("http" "https"))
2265                (concat type ":" raw-path))
2266               ((file-name-absolute-p raw-path)
2267                (expand-file-name raw-path))
2268               (t raw-path))))
2269      (src-expanded (if (file-name-absolute-p src) src
2270              (expand-file-name src (file-name-directory
2271                         (plist-get info :input-file)))))
2272      (href (format
2273         "\n<draw:image xlink:href=\"%s\" xlink:type=\"simple\" xlink:show=\"embed\" xlink:actuate=\"onLoad\"/>"
2274         (org-odt--copy-image-file src-expanded)))
2275      ;; Extract attributes from #+ATTR_ODT line.
2276      (attr-from (cl-case (org-element-type element)
2277               (link (org-export-get-parent-element element))
2278               (t element)))
2279      ;; Convert attributes to a plist.
2280      (attr-plist (org-export-read-attribute :attr_odt attr-from))
2281      ;; Handle `:anchor', `:style' and `:attributes' properties.
2282      (user-frame-anchor
2283       (car (assoc-string (plist-get attr-plist :anchor)
2284                  '(("as-char") ("paragraph") ("page")) t)))
2285      (user-frame-style
2286       (and user-frame-anchor (plist-get attr-plist :style)))
2287      (user-frame-attrs
2288       (and user-frame-anchor (plist-get attr-plist :attributes)))
2289      (user-frame-params
2290       (list user-frame-style user-frame-attrs user-frame-anchor))
2291      ;; (embed-as (or embed-as user-frame-anchor "paragraph"))
2292      ;;
2293      ;; Handle `:width', `:height' and `:scale' properties.  Read
2294      ;; them as numbers since we need them for computations.
2295      (size (org-odt--image-size
2296         src-expanded info
2297         (let ((width (plist-get attr-plist :width)))
2298           (and width (read width)))
2299         (let ((length (plist-get attr-plist :length)))
2300           (and length (read length)))
2301         (let ((scale (plist-get attr-plist :scale)))
2302           (and scale (read scale)))
2303         nil            ; embed-as
2304         "paragraph"        ; FIXME
2305         ))
2306      (width (car size)) (height (cdr size))
2307      (standalone-link-p (org-odt--standalone-link-p element info))
2308      (embed-as (if standalone-link-p "paragraph" "as-char"))
2309      (captions (org-odt-format-label element info 'definition))
2310      (caption (car captions))
2311      (entity (concat (and caption "Captioned") embed-as "Image"))
2312      ;; Check if this link was created by LaTeX-to-PNG converter.
2313      (replaces (org-element-property
2314             :replaces (if (not standalone-link-p) element
2315                 (org-export-get-parent-element element))))
2316      ;; If yes, note down the type of the element - LaTeX Fragment
2317      ;; or LaTeX environment.  It will go in to frame title.
2318      (title (and replaces (capitalize
2319                    (symbol-name (org-element-type replaces)))))
2320
2321      ;; If yes, note down its contents.  It will go in to frame
2322      ;; description.  This quite useful for debugging.
2323      (desc (and replaces (org-element-property :value replaces))))
2324     (org-odt--render-image/formula entity href width height
2325                    captions user-frame-params title desc)))
2326
2327
2328 ;;;; Links :: Math formula
2329
2330 (defun org-odt-link--inline-formula (element info)
2331   (let* ((src (let ((raw-path (org-element-property :path element)))
2332         (cond
2333          ((file-name-absolute-p raw-path)
2334           (expand-file-name raw-path))
2335          (t raw-path))))
2336      (src-expanded (if (file-name-absolute-p src) src
2337              (expand-file-name src (file-name-directory
2338                         (plist-get info :input-file)))))
2339      (href
2340       (format
2341        "\n<draw:object %s xlink:href=\"%s\" xlink:type=\"simple\"/>"
2342        " xlink:show=\"embed\" xlink:actuate=\"onLoad\""
2343        (file-name-directory (org-odt--copy-formula-file src-expanded))))
2344      (standalone-link-p (org-odt--standalone-link-p element info))
2345      (embed-as (if standalone-link-p 'paragraph 'character))
2346      (captions (org-odt-format-label element info 'definition))
2347      ;; Check if this link was created by LaTeX-to-MathML
2348      ;; converter.
2349      (replaces (org-element-property
2350             :replaces (if (not standalone-link-p) element
2351                 (org-export-get-parent-element element))))
2352      ;; If yes, note down the type of the element - LaTeX Fragment
2353      ;; or LaTeX environment.  It will go in to frame title.
2354      (title (and replaces (capitalize
2355                    (symbol-name (org-element-type replaces)))))
2356
2357      ;; If yes, note down its contents.  It will go in to frame
2358      ;; description.  This quite useful for debugging.
2359      (desc (and replaces (org-element-property :value replaces)))
2360      width height)
2361     (cond
2362      ((eq embed-as 'character)
2363       (org-odt--render-image/formula "InlineFormula" href width height
2364                      nil nil title desc))
2365      (t
2366       (let* ((equation (org-odt--render-image/formula
2367             "CaptionedDisplayFormula" href width height
2368             captions nil title desc))
2369          (label
2370           (let* ((org-odt-category-map-alist
2371               '(("__MathFormula__" "Text" "math-label" "Equation"
2372              org-odt--enumerable-formula-p))))
2373         (car (org-odt-format-label element info 'definition)))))
2374     (concat equation "<text:tab/>" label))))))
2375
2376 (defun org-odt--copy-formula-file (src-file)
2377   "Returns the internal name of the file"
2378   (let* ((target-dir (format "Formula-%04d/"
2379                  (cl-incf org-odt-embedded-formulas-count)))
2380      (target-file (concat target-dir "content.xml")))
2381     ;; Create a directory for holding formula file.  Also enter it in
2382     ;; to manifest.
2383     (make-directory (concat org-odt-zip-dir target-dir))
2384     (org-odt-create-manifest-file-entry
2385      "application/vnd.oasis.opendocument.formula" target-dir "1.2")
2386     ;; Copy over the formula file from user directory to zip
2387     ;; directory.
2388     (message "Embedding %s as %s..." src-file target-file)
2389     (let ((ext (file-name-extension src-file)))
2390       (cond
2391        ;; Case 1: Mathml.
2392        ((member ext '("mathml" "mml"))
2393     (copy-file src-file (concat org-odt-zip-dir target-file) 'overwrite))
2394        ;; Case 2: OpenDocument formula.
2395        ((string= ext "odf")
2396     (org-odt--zip-extract src-file "content.xml"
2397                 (concat org-odt-zip-dir target-dir)))
2398        (t (error "%s is not a formula file" src-file))))
2399     ;; Enter the formula file in to manifest.
2400     (org-odt-create-manifest-file-entry "text/xml" target-file)
2401     target-file))
2402
2403 ;;;; Targets
2404
2405 (defun org-odt--render-image/formula (cfg-key href width height &optional
2406                           captions user-frame-params
2407                           &rest title-and-desc)
2408   (let* ((frame-cfg-alist
2409       ;; Each element of this alist is of the form (CFG-HANDLE
2410       ;; INNER-FRAME-PARAMS OUTER-FRAME-PARAMS).
2411
2412       ;; CFG-HANDLE is the key to the alist.
2413
2414       ;; INNER-FRAME-PARAMS and OUTER-FRAME-PARAMS specify the
2415       ;; frame params for INNER-FRAME and OUTER-FRAME
2416       ;; respectively.  See below.
2417
2418       ;; Configurations that are meant to be applied to
2419       ;; non-captioned image/formula specifies no
2420       ;; OUTER-FRAME-PARAMS.
2421
2422       ;; TERMINOLOGY
2423       ;; ===========
2424       ;; INNER-FRAME :: Frame that directly surrounds an
2425       ;;                image/formula.
2426
2427       ;; OUTER-FRAME :: Frame that encloses the INNER-FRAME.  This
2428       ;;                frame also contains the caption, if any.
2429
2430       ;; FRAME-PARAMS :: List of the form (FRAME-STYLE-NAME
2431       ;;                 FRAME-ATTRIBUTES FRAME-ANCHOR).  Note
2432       ;;                 that these are the last three arguments
2433       ;;                 to `org-odt--frame'.
2434
2435       ;; Note that an un-captioned image/formula requires just an
2436       ;; INNER-FRAME, while a captioned image/formula requires
2437       ;; both an INNER and an OUTER-FRAME.
2438       '(("As-CharImage" ("OrgInlineImage" nil "as-char"))
2439         ("ParagraphImage" ("OrgDisplayImage" nil "paragraph"))
2440         ("PageImage" ("OrgPageImage" nil "page"))
2441         ("CaptionedAs-CharImage"
2442          ("OrgCaptionedImage"
2443           " style:rel-width=\"100%\" style:rel-height=\"scale\"" "paragraph")
2444          ("OrgInlineImage" nil "as-char"))
2445         ("CaptionedParagraphImage"
2446          ("OrgCaptionedImage"
2447           " style:rel-width=\"100%\" style:rel-height=\"scale\"" "paragraph")
2448          ("OrgImageCaptionFrame" nil "paragraph"))
2449         ("CaptionedPageImage"
2450          ("OrgCaptionedImage"
2451           " style:rel-width=\"100%\" style:rel-height=\"scale\"" "paragraph")
2452          ("OrgPageImageCaptionFrame" nil "page"))
2453         ("InlineFormula" ("OrgInlineFormula" nil "as-char"))
2454         ("DisplayFormula" ("OrgDisplayFormula" nil "as-char"))
2455         ("CaptionedDisplayFormula"
2456          ("OrgCaptionedFormula" nil "paragraph")
2457          ("OrgFormulaCaptionFrame" nil "paragraph"))))
2458      (caption (car captions)) (short-caption (cdr captions))
2459      ;; Retrieve inner and outer frame params, from configuration.
2460      (frame-cfg (assoc-string cfg-key frame-cfg-alist t))
2461      (inner (nth 1 frame-cfg))
2462      (outer (nth 2 frame-cfg))
2463      ;; User-specified frame params (from #+ATTR_ODT spec)
2464      (user user-frame-params)
2465      (--merge-frame-params (function
2466                 (lambda (default user)
2467                   "Merge default and user frame params."
2468                   (if (not user) default
2469                     (cl-assert (= (length default) 3))
2470                     (cl-assert (= (length user) 3))
2471                     (cl-loop for u in user
2472                          for d in default
2473                          collect (or u d)))))))
2474     (cond
2475      ;; Case 1: Image/Formula has no caption.
2476      ;;         There is only one frame, one that surrounds the image
2477      ;;         or formula.
2478      ((not caption)
2479       ;; Merge user frame params with that from configuration.
2480       (setq inner (funcall --merge-frame-params inner user))
2481       (apply 'org-odt--frame href width height
2482          (append inner title-and-desc)))
2483      ;; Case 2: Image/Formula is captioned or labeled.
2484      ;;         There are two frames: The inner one surrounds the
2485      ;;         image or formula.  The outer one contains the
2486      ;;         caption/sequence number.
2487      (t
2488       ;; Merge user frame params with outer frame params.
2489       (setq outer (funcall --merge-frame-params outer user))
2490       ;; Short caption, if specified, goes as part of inner frame.
2491       (setq inner (let ((frame-params (copy-sequence inner)))
2492             (setcar (cdr frame-params)
2493                 (concat
2494                  (cadr frame-params)
2495                  (when short-caption
2496                    (format " draw:name=\"%s\" " short-caption))))
2497             frame-params))
2498       (apply 'org-odt--textbox
2499          (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
2500              "Illustration"
2501              (concat
2502               (apply 'org-odt--frame href width height
2503                  (append inner title-and-desc))
2504               caption))
2505          width height outer)))))
2506
2507 (defun org-odt--enumerable-p (element _info)
2508   ;; Element should have a caption or label.
2509   (or (org-element-property :caption element)
2510       (org-element-property :name element)))
2511
2512 (defun org-odt--enumerable-image-p (element info)
2513   (org-odt--standalone-link-p
2514    element info
2515    ;; Paragraph should have a caption or label.  It SHOULD NOT be a
2516    ;; replacement element. (i.e., It SHOULD NOT be a result of LaTeX
2517    ;; processing.)
2518    (lambda (p)
2519      (and (not (org-element-property :replaces p))
2520       (or (org-element-property :caption p)
2521           (org-element-property :name p))))
2522    ;; Link should point to an image file.
2523    (lambda (l)
2524      (cl-assert (eq (org-element-type l) 'link))
2525      (org-export-inline-image-p l (plist-get info :odt-inline-image-rules)))))
2526
2527 (defun org-odt--enumerable-latex-image-p (element info)
2528   (org-odt--standalone-link-p
2529    element info
2530    ;; Paragraph should have a caption or label.  It SHOULD also be a
2531    ;; replacement element. (i.e., It SHOULD be a result of LaTeX
2532    ;; processing.)
2533    (lambda (p)
2534      (and (org-element-property :replaces p)
2535       (or (org-element-property :caption p)
2536           (org-element-property :name p))))
2537    ;; Link should point to an image file.
2538    (lambda (l)
2539      (cl-assert (eq (org-element-type l) 'link))
2540      (org-export-inline-image-p l (plist-get info :odt-inline-image-rules)))))
2541
2542 (defun org-odt--enumerable-formula-p (element info)
2543   (org-odt--standalone-link-p
2544    element info
2545    ;; Paragraph should have a caption or label.
2546    (lambda (p)
2547      (or (org-element-property :caption p)
2548      (org-element-property :name p)))
2549    ;; Link should point to a MathML or ODF file.
2550    (lambda (l)
2551      (cl-assert (eq (org-element-type l) 'link))
2552      (org-export-inline-image-p l (plist-get info :odt-inline-formula-rules)))))
2553
2554 (defun org-odt--standalone-link-p (element _info &optional
2555                        paragraph-predicate
2556                        link-predicate)
2557   "Test if ELEMENT is a standalone link for the purpose ODT export.
2558 INFO is a plist holding contextual information.
2559
2560 Return non-nil, if ELEMENT is of type paragraph satisfying
2561 PARAGRAPH-PREDICATE and its sole content, save for whitespaces,
2562 is a link that satisfies LINK-PREDICATE.
2563
2564 Return non-nil, if ELEMENT is of type link satisfying
2565 LINK-PREDICATE and its containing paragraph satisfies
2566 PARAGRAPH-PREDICATE in addition to having no other content save for
2567 leading and trailing whitespaces.
2568
2569 Return nil, otherwise."
2570   (let ((p (cl-case (org-element-type element)
2571          (paragraph element)
2572          (link (and (or (not link-predicate)
2573                 (funcall link-predicate element))
2574             (org-export-get-parent element)))
2575          (t nil))))
2576     (when (and p (eq (org-element-type p) 'paragraph))
2577       (when (or (not paragraph-predicate)
2578         (funcall paragraph-predicate p))
2579     (let ((contents (org-element-contents p)))
2580       (cl-loop for x in contents
2581            with inline-image-count = 0
2582            always (cl-case (org-element-type x)
2583                 (plain-text
2584                  (not (org-string-nw-p x)))
2585                 (link
2586                  (and (or (not link-predicate)
2587                       (funcall link-predicate x))
2588                   (= (cl-incf inline-image-count) 1)))
2589                 (t nil))))))))
2590
2591 (defun org-odt-link--infer-description (destination info)
2592   ;; DESTINATION is a headline or an element (like paragraph,
2593   ;; verse-block etc) to which a "#+NAME: label" can be attached.
2594
2595   ;; Note that labels that are attached to captioned entities - inline
2596   ;; images, math formulae and tables - get resolved as part of
2597   ;; `org-odt-format-label' and `org-odt--enumerate'.
2598
2599   ;; Create a cross-reference to DESTINATION but make best-efforts to
2600   ;; create a *meaningful* description.  Check item numbers, section
2601   ;; number and section title in that order.
2602
2603   ;; NOTE: Counterpart of `org-export-get-ordinal'.
2604   ;; FIXME: Handle footnote-definition footnote-reference?
2605   (let* ((genealogy (org-element-lineage destination))
2606      (data (reverse genealogy))
2607      (label (let ((type (org-element-type destination)))
2608           (if (memq type '(headline target))
2609               (org-export-get-reference destination info)
2610             (error "FIXME: Unable to resolve %S" destination)))))
2611     (or
2612      (let* ( ;; Locate top-level list.
2613         (top-level-list
2614          (cl-loop for x on data
2615               when (eq (org-element-type (car x)) 'plain-list)
2616               return x))
2617         ;; Get list item nos.
2618         (item-numbers
2619          (cl-loop for (plain-list item . rest) on top-level-list by #'cddr
2620               until (not (eq (org-element-type plain-list) 'plain-list))
2621               collect (when (eq (org-element-property :type
2622                                   plain-list)
2623                     'ordered)
2624                 (1+ (length (org-export-get-previous-element
2625                          item info t))))))
2626         ;; Locate top-most listified headline.
2627         (listified-headlines
2628          (cl-loop for x on data
2629               when (and (eq (org-element-type (car x)) 'headline)
2630                 (org-export-low-level-p (car x) info))
2631               return x))
2632         ;; Get listified headline numbers.
2633         (listified-headline-nos
2634          (cl-loop for el in listified-headlines
2635               when (eq (org-element-type el) 'headline)
2636               collect (when (org-export-numbered-headline-p el info)
2637                 (1+ (length (org-export-get-previous-element
2638                          el info t)))))))
2639        ;; Combine item numbers from both the listified headlines and
2640        ;; regular list items.
2641
2642        ;; Case 1: Check if all the parents of list item are numbered.
2643        ;; If yes, link to the item proper.
2644        (let ((item-numbers (append listified-headline-nos item-numbers)))
2645      (when (and item-numbers (not (memq nil item-numbers)))
2646        (format "<text:bookmark-ref text:reference-format=\"number-all-superior\" text:ref-name=\"%s\">%s</text:bookmark-ref>"
2647            label
2648            (mapconcat (lambda (n) (if (not n) " "
2649                        (concat (number-to-string n) ".")))
2650                   item-numbers "")))))
2651      ;; Case 2: Locate a regular and numbered headline in the
2652      ;; hierarchy.  Display its section number.
2653      (let ((headline
2654         (and
2655          ;; Test if destination is a numbered headline.
2656          (org-export-numbered-headline-p destination info)
2657          (cl-loop for el in (cons destination genealogy)
2658               when (and (eq (org-element-type el) 'headline)
2659                 (not (org-export-low-level-p el info))
2660                 (org-export-numbered-headline-p el info))
2661               return el))))
2662        ;; We found one.
2663        (when headline
2664      (format "<text:bookmark-ref text:reference-format=\"chapter\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>"
2665          label
2666          (mapconcat 'number-to-string (org-export-get-headline-number
2667                            headline info) "."))))
2668      ;; Case 4: Locate a regular headline in the hierarchy.  Display
2669      ;; its title.
2670      (let ((headline (cl-loop for el in (cons destination genealogy)
2671                   when (and (eq (org-element-type el) 'headline)
2672                     (not (org-export-low-level-p el info)))
2673                   return el)))
2674        ;; We found one.
2675        (when headline
2676      (format "<text:bookmark-ref text:reference-format=\"text\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>"
2677          label
2678          (let ((title (org-element-property :title headline)))
2679            (org-export-data title info)))))
2680      (error "FIXME?"))))
2681
2682 (defun org-odt-link (link desc info)
2683   "Transcode a LINK object from Org to ODT.
2684
2685 DESC is the description part of the link, or the empty string.
2686 INFO is a plist holding contextual information.  See
2687 `org-export-data'."
2688   (let* ((type (org-element-property :type link))
2689      (raw-path (org-element-property :path link))
2690      ;; Ensure DESC really exists, or set it to nil.
2691      (desc (and (not (string= desc "")) desc))
2692      (imagep (org-export-inline-image-p
2693           link (plist-get info :odt-inline-image-rules)))
2694      (path (cond
2695         ((member type '("http" "https" "ftp" "mailto"))
2696          (concat type ":" raw-path))
2697         ((string= type "file") (org-export-file-uri raw-path))
2698         (t raw-path)))
2699      ;; Convert & to &amp; for correct XML representation
2700      (path (replace-regexp-in-string "&" "&amp;" path)))
2701     (cond
2702      ;; Link type is handled by a special function.
2703      ((org-export-custom-protocol-maybe link desc 'odt))
2704      ;; Image file.
2705      ((and (not desc) imagep) (org-odt-link--inline-image link info))
2706      ;; Formula file.
2707      ((and (not desc)
2708        (org-export-inline-image-p
2709         link (plist-get info :odt-inline-formula-rules)))
2710       (org-odt-link--inline-formula link info))
2711      ;; Radio target: Transcode target's contents and use them as
2712      ;; link's description.
2713      ((string= type "radio")
2714       (let ((destination (org-export-resolve-radio-link link info)))
2715     (if (not destination) desc
2716       (format
2717        "<text:bookmark-ref text:reference-format=\"text\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>"
2718        (org-export-get-reference destination info)
2719        desc))))
2720      ;; Links pointing to a headline: Find destination and build
2721      ;; appropriate referencing command.
2722      ((member type '("custom-id" "fuzzy" "id"))
2723       (let ((destination (if (string= type "fuzzy")
2724                  (org-export-resolve-fuzzy-link link info)
2725                (org-export-resolve-id-link link info))))
2726     (cl-case (org-element-type destination)
2727       ;; Fuzzy link points to a headline.  If there's
2728       ;; a description, create a hyperlink.  Otherwise, try to
2729       ;; provide a meaningful description.
2730       (headline
2731        (if (not desc) (org-odt-link--infer-description destination info)
2732          (let ((label
2733             (or (and (string= type "custom-id")
2734                  (org-element-property :CUSTOM_ID destination))
2735             (org-export-get-reference destination info))))
2736            (format
2737         "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>"
2738         label desc))))
2739       ;; Fuzzy link points to a target.  If there's a description,
2740       ;; create a hyperlink.  Otherwise, try to provide
2741       ;; a meaningful description.
2742       (target
2743        (format "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>"
2744            (org-export-get-reference destination info)
2745            (or desc (org-export-get-ordinal destination info))))
2746       ;; Fuzzy link points to some element (e.g., an inline image,
2747       ;; a math formula or a table).
2748       (otherwise
2749        (let ((label-reference
2750           (ignore-errors
2751             (org-odt-format-label destination info 'reference))))
2752          (cond
2753           ((not label-reference)
2754            (org-odt-link--infer-description destination info))
2755           ;; LINK has no description.  Create
2756           ;; a cross-reference showing entity's sequence
2757           ;; number.
2758           ((not desc) label-reference)
2759           ;; LINK has description.  Insert a hyperlink with
2760           ;; user-provided description.
2761           (t
2762            (format
2763         "<text:a xlink:type=\"simple\" xlink:href=\"#%s\">%s</text:a>"
2764         (org-export-get-reference destination info)
2765         desc))))))))
2766      ;; Coderef: replace link with the reference name or the
2767      ;; equivalent line number.
2768      ((string= type "coderef")
2769       (let* ((line-no (format "%d" (org-export-resolve-coderef path info)))
2770          (href (concat "coderef-" path)))
2771     (format
2772      (org-export-get-coderef-format path desc)
2773      (format
2774       "<text:bookmark-ref text:reference-format=\"number\" text:ref-name=\"OrgXref.%s\">%s</text:bookmark-ref>"
2775       href line-no))))
2776      ;; External link with a description part.
2777      ((and path desc)
2778       (let ((link-contents (org-element-contents link)))
2779     ;; Check if description is a link to an inline image.
2780     (if (and (not (cdr link-contents))
2781          (let ((desc-element (car link-contents)))
2782            (and (eq (org-element-type desc-element) 'link)
2783             (org-export-inline-image-p
2784              desc-element
2785              (plist-get info :odt-inline-image-rules)))))
2786         ;; Format link as a clickable image.
2787         (format "\n<draw:a xlink:type=\"simple\" xlink:href=\"%s\">\n%s\n</draw:a>"
2788             path desc)
2789       ;; Otherwise, format it as a regular link.
2790       (format "<text:a xlink:type=\"simple\" xlink:href=\"%s\">%s</text:a>"
2791           path desc))))
2792      ;; External link without a description part.
2793      (path
2794       (format "<text:a xlink:type=\"simple\" xlink:href=\"%s\">%s</text:a>"
2795           path path))
2796      ;; No path, only description.  Try to do something useful.
2797      (t (format "<text:span text:style-name=\"%s\">%s</text:span>"
2798         "Emphasis" desc)))))
2799
2800
2801 ;;;; Node Property
2802
2803 (defun org-odt-node-property (node-property _contents _info)
2804   "Transcode a NODE-PROPERTY element from Org to ODT.
2805 CONTENTS is nil.  INFO is a plist holding contextual
2806 information."
2807   (org-odt--encode-plain-text
2808    (format "%s:%s"
2809        (org-element-property :key node-property)
2810        (let ((value (org-element-property :value node-property)))
2811          (if value (concat " " value) "")))))
2812
2813 ;;;; Paragraph
2814
2815 (defun org-odt--paragraph-style (paragraph)
2816   "Return style of PARAGRAPH.
2817 Style is a symbol among `quoted', `centered' and nil."
2818   (let ((up paragraph))
2819     (while (and (setq up (org-element-property :parent up))
2820         (not (memq (org-element-type up)
2821                '(center-block quote-block section)))))
2822     (cl-case (org-element-type up)
2823       (center-block 'centered)
2824       (quote-block 'quoted))))
2825
2826 (defun org-odt--format-paragraph (paragraph contents info default center quote)
2827   "Format paragraph according to given styles.
2828 PARAGRAPH is a paragraph type element.  CONTENTS is the
2829 transcoded contents of that paragraph, as a string.  INFO is
2830 a plist used as a communication channel.  DEFAULT, CENTER and
2831 QUOTE are, respectively, style to use when paragraph belongs to
2832 no special environment, a center block, or a quote block."
2833   (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
2834       (cl-case (org-odt--paragraph-style paragraph)
2835         (quoted quote)
2836         (centered center)
2837         (otherwise default))
2838       ;; If PARAGRAPH is a leading paragraph in an item that has
2839       ;; a checkbox, splice checkbox and paragraph contents
2840       ;; together.
2841       (concat (let ((parent (org-element-property :parent paragraph)))
2842             (and (eq (org-element-type parent) 'item)
2843              (not (org-export-get-previous-element paragraph info))
2844              (org-odt--checkbox parent)))
2845           contents)))
2846
2847 (defun org-odt-paragraph (paragraph contents info)
2848   "Transcode a PARAGRAPH element from Org to ODT.
2849 CONTENTS is the contents of the paragraph, as a string.  INFO is
2850 the plist used as a communication channel."
2851   (org-odt--format-paragraph
2852    paragraph contents info
2853    (or (org-element-property :style paragraph) "Text_20_body")
2854    "OrgCenter"
2855    "Quotations"))
2856
2857
2858 ;;;; Plain List
2859
2860 (defun org-odt-plain-list (plain-list contents _info)
2861   "Transcode a PLAIN-LIST element from Org to ODT.
2862 CONTENTS is the contents of the list.  INFO is a plist holding
2863 contextual information."
2864   (format "\n<text:list text:style-name=\"%s\" %s>\n%s</text:list>"
2865       ;; Choose style based on list type.
2866       (cl-case (org-element-property :type plain-list)
2867         (ordered "OrgNumberedList")
2868         (unordered "OrgBulletedList")
2869         (descriptive-1 "OrgDescriptionList")
2870         (descriptive-2 "OrgDescriptionList"))
2871       ;; If top-level list, re-start numbering.  Otherwise,
2872       ;; continue numbering.
2873       (format "text:continue-numbering=\"%s\""
2874           (let* ((parent (org-export-get-parent plain-list)))
2875             (if (and parent (eq (org-element-type parent) 'item))
2876             "true" "false")))
2877       contents))
2878
2879 ;;;; Plain Text
2880
2881 (defun org-odt--encode-tabs-and-spaces (line)
2882   (replace-regexp-in-string
2883    "\\(\t\\| \\{2,\\}\\)"
2884    (lambda (s)
2885      (if (string= s "\t") "<text:tab/>"
2886        (format " <text:s text:c=\"%d\"/>" (1- (length s)))))
2887    line))
2888
2889 (defun org-odt--encode-plain-text (text &optional no-whitespace-filling)
2890   (dolist (pair '(("&" . "&amp;") ("<" . "&lt;") (">" . "&gt;")))
2891     (setq text (replace-regexp-in-string (car pair) (cdr pair) text t t)))
2892   (if no-whitespace-filling text
2893     (org-odt--encode-tabs-and-spaces text)))
2894
2895 (defun org-odt-plain-text (text info)
2896   "Transcode a TEXT string from Org to ODT.
2897 TEXT is the string to transcode.  INFO is a plist holding
2898 contextual information."
2899   (let ((output text))
2900     ;; Protect &, < and >.
2901     (setq output (org-odt--encode-plain-text output t))
2902     ;; Handle smart quotes.  Be sure to provide original string since
2903     ;; OUTPUT may have been modified.
2904     (when (plist-get info :with-smart-quotes)
2905       (setq output (org-export-activate-smart-quotes output :utf-8 info text)))
2906     ;; Convert special strings.
2907     (when (plist-get info :with-special-strings)
2908       (dolist (pair org-odt-special-string-regexps)
2909     (setq output
2910           (replace-regexp-in-string (car pair) (cdr pair) output t nil))))
2911     ;; Handle break preservation if required.
2912     (when (plist-get info :preserve-breaks)
2913       (setq output (replace-regexp-in-string
2914             "\\(\\\\\\\\\\)?[ \t]*\n" "<text:line-break/>" output t)))
2915     ;; Return value.
2916     output))
2917
2918
2919 ;;;; Planning
2920
2921 (defun org-odt-planning (planning contents info)
2922   "Transcode a PLANNING element from Org to ODT.
2923 CONTENTS is nil.  INFO is a plist used as a communication
2924 channel."
2925   (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
2926       "OrgPlanning"
2927       (concat
2928        (let ((closed (org-element-property :closed planning)))
2929          (when closed
2930            (concat
2931         (format "<text:span text:style-name=\"%s\">%s</text:span>"
2932             "OrgClosedKeyword" org-closed-string)
2933         (org-odt-timestamp closed contents info))))
2934        (let ((deadline (org-element-property :deadline planning)))
2935          (when deadline
2936            (concat
2937         (format "<text:span text:style-name=\"%s\">%s</text:span>"
2938             "OrgDeadlineKeyword" org-deadline-string)
2939         (org-odt-timestamp deadline contents info))))
2940        (let ((scheduled (org-element-property :scheduled planning)))
2941          (when scheduled
2942            (concat
2943         (format "<text:span text:style-name=\"%s\">%s</text:span>"
2944             "OrgScheduledKeyword" org-deadline-string)
2945         (org-odt-timestamp scheduled contents info)))))))
2946
2947
2948 ;;;; Property Drawer
2949
2950 (defun org-odt-property-drawer (_property-drawer contents _info)
2951   "Transcode a PROPERTY-DRAWER element from Org to ODT.
2952 CONTENTS holds the contents of the drawer.  INFO is a plist
2953 holding contextual information."
2954   (and (org-string-nw-p contents)
2955        (format "<text:p text:style-name=\"OrgFixedWidthBlock\">%s</text:p>"
2956            contents)))
2957
2958
2959 ;;;; Quote Block
2960
2961 (defun org-odt-quote-block (_quote-block contents _info)
2962   "Transcode a QUOTE-BLOCK element from Org to ODT.
2963 CONTENTS holds the contents of the block.  INFO is a plist
2964 holding contextual information."
2965   contents)
2966
2967
2968 ;;;; Section
2969
2970 (defun org-odt-format-section (text style &optional name)
2971   (let ((default-name (car (org-odt-add-automatic-style "Section"))))
2972     (format "\n<text:section text:style-name=\"%s\" %s>\n%s\n</text:section>"
2973         style
2974         (format "text:name=\"%s\"" (or name default-name))
2975         text)))
2976
2977
2978 (defun org-odt-section (_section contents _info) ; FIXME
2979   "Transcode a SECTION element from Org to ODT.
2980 CONTENTS holds the contents of the section.  INFO is a plist
2981 holding contextual information."
2982   contents)
2983
2984 ;;;; Radio Target
2985
2986 (defun org-odt-radio-target (radio-target text info)
2987   "Transcode a RADIO-TARGET object from Org to ODT.
2988 TEXT is the text of the target.  INFO is a plist holding
2989 contextual information."
2990   (org-odt--target text (org-export-get-reference radio-target info)))
2991
2992
2993 ;;;; Special Block
2994
2995 (defun org-odt-special-block (special-block contents info)
2996   "Transcode a SPECIAL-BLOCK element from Org to ODT.
2997 CONTENTS holds the contents of the block.  INFO is a plist
2998 holding contextual information."
2999   (let ((type (org-element-property :type special-block))
3000     (attributes (org-export-read-attribute :attr_odt special-block)))
3001     (cond
3002      ;; Annotation.
3003      ((string= type "annotation")
3004       (let* ((author (or (plist-get attributes :author)
3005              (let ((author (plist-get info :author)))
3006                (and author (org-export-data author info)))))
3007          (date (or (plist-get attributes :date)
3008                ;; FIXME: Is `car' right thing to do below?
3009                (car (plist-get info :date)))))
3010     (format "\n<text:p>%s</text:p>"
3011         (format "<office:annotation>\n%s\n</office:annotation>"
3012             (concat
3013              (and author
3014                   (format "<dc:creator>%s</dc:creator>" author))
3015              (and date
3016                   (format "<dc:date>%s</dc:date>"
3017                       (org-odt--format-timestamp date nil 'iso-date)))
3018              contents)))))
3019      ;; Textbox.
3020      ((string= type "textbox")
3021       (let ((width (plist-get attributes :width))
3022         (height (plist-get attributes :height))
3023         (style (plist-get attributes :style))
3024         (extra (plist-get attributes :extra))
3025         (anchor (plist-get attributes :anchor)))
3026     (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
3027         "Text_20_body" (org-odt--textbox contents width height
3028                            style extra anchor))))
3029      (t contents))))
3030
3031
3032 ;;;; Src Block
3033
3034 (defun org-odt-hfy-face-to-css (fn)
3035   "Create custom style for face FN.
3036 When FN is the default face, use its foreground and background
3037 properties to create \"OrgSrcBlock\" paragraph style.  Otherwise
3038 use its color attribute to create a character style whose name
3039 is obtained from FN.  Currently all attributes of FN other than
3040 color are ignored.
3041
3042 The style name for a face FN is derived using the following
3043 operations on the face name in that order - de-dash, CamelCase
3044 and prefix with \"OrgSrc\".  For example,
3045 `font-lock-function-name-face' is associated with
3046 \"OrgSrcFontLockFunctionNameFace\"."
3047   (let* ((css-list (hfy-face-to-style fn))
3048      (style-name (concat "OrgSrc"
3049                              (mapconcat
3050                               'capitalize (split-string
3051                                            (hfy-face-or-def-to-name fn) "-")
3052                               "")))
3053      (color-val (cdr (assoc "color" css-list)))
3054      (background-color-val (cdr (assoc "background" css-list)))
3055      (style (and org-odt-create-custom-styles-for-srcblocks
3056              (cond
3057               ((eq fn 'default)
3058                (format org-odt-src-block-paragraph-format
3059                    background-color-val color-val))
3060               (t
3061                (format
3062             "
3063 <style:style style:name=\"%s\" style:family=\"text\">
3064   <style:text-properties fo:color=\"%s\"/>
3065  </style:style>" style-name color-val))))))
3066     (cons style-name style)))
3067
3068 (defun org-odt-htmlfontify-string (line)
3069   (let* ((hfy-html-quote-regex "\\([<\"&> \t]\\)")
3070      (hfy-html-quote-map '(("\"" "&quot;")
3071                    ("<" "&lt;")
3072                    ("&" "&amp;")
3073                    (">" "&gt;")
3074                    (" " "<text:s/>")
3075                    ("\t" "<text:tab/>")))
3076      (hfy-face-to-css 'org-odt-hfy-face-to-css)
3077      (hfy-optimizations-1 (copy-sequence hfy-optimizations))
3078      (hfy-optimizations (cl-pushnew 'body-text-only hfy-optimizations-1))
3079      (hfy-begin-span-handler
3080       (lambda (style _text-block _text-id _text-begins-block-p)
3081         (insert (format "<text:span text:style-name=\"%s\">" style))))
3082      (hfy-end-span-handler (lambda () (insert "</text:span>"))))
3083     (with-no-warnings (htmlfontify-string line))))
3084
3085 (defun org-odt-do-format-code
3086     (code info &optional lang refs retain-labels num-start)
3087   (let* ((lang (or (assoc-default lang org-src-lang-modes) lang))
3088      (lang-mode (and lang (intern (format "%s-mode" lang))))
3089      (code-lines (org-split-string code "\n"))
3090      (code-length (length code-lines))
3091      (use-htmlfontify-p (and (functionp lang-mode)
3092                  (plist-get info :odt-fontify-srcblocks)
3093                  (require 'htmlfontify nil t)
3094                  (fboundp 'htmlfontify-string)))
3095      (code (if (not use-htmlfontify-p) code
3096          (with-temp-buffer
3097            (insert code)
3098            (funcall lang-mode)
3099            (org-font-lock-ensure)
3100            (buffer-string))))
3101      (fontifier (if use-htmlfontify-p 'org-odt-htmlfontify-string
3102               'org-odt--encode-plain-text))
3103      (par-style (if use-htmlfontify-p "OrgSrcBlock"
3104               "OrgFixedWidthBlock"))
3105      (i 0))
3106     (cl-assert (= code-length (length (org-split-string code "\n"))))
3107     (setq code
3108       (org-export-format-code
3109        code
3110        (lambda (loc line-num ref)
3111          (setq par-style
3112            (concat par-style (and (= (cl-incf i) code-length)
3113                       "LastLine")))
3114
3115          (setq loc (concat loc (and ref retain-labels (format " (%s)" ref))))
3116          (setq loc (funcall fontifier loc))
3117          (when ref
3118            (setq loc (org-odt--target loc (concat "coderef-" ref))))
3119          (cl-assert par-style)
3120          (setq loc (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
3121                    par-style loc))
3122          (if (not line-num) loc
3123            (format "\n<text:list-item>%s\n</text:list-item>" loc)))
3124        num-start refs))
3125     (cond
3126      ((not num-start) code)
3127      ((= num-start 0)
3128       (format
3129        "\n<text:list text:style-name=\"OrgSrcBlockNumberedLine\"%s>%s</text:list>"
3130        " text:continue-numbering=\"false\"" code))
3131      (t
3132       (format
3133        "\n<text:list text:style-name=\"OrgSrcBlockNumberedLine\"%s>%s</text:list>"
3134        " text:continue-numbering=\"true\"" code)))))
3135
3136 (defun org-odt-format-code (element info)
3137   (let* ((lang (org-element-property :language element))
3138      ;; Extract code and references.
3139      (code-info (org-export-unravel-code element))
3140      (code (car code-info))
3141      (refs (cdr code-info))
3142      ;; Does the src block contain labels?
3143      (retain-labels (org-element-property :retain-labels element))
3144      ;; Does it have line numbers?
3145      (num-start (org-export-get-loc element info)))
3146     (org-odt-do-format-code code info lang refs retain-labels num-start)))
3147
3148 (defun org-odt-src-block (src-block _contents info)
3149   "Transcode a SRC-BLOCK element from Org to ODT.
3150 CONTENTS holds the contents of the item.  INFO is a plist holding
3151 contextual information."
3152   (let* ((attributes (org-export-read-attribute :attr_odt src-block))
3153      (caption (car (org-odt-format-label src-block info 'definition))))
3154     (concat
3155      (and caption
3156       (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
3157           "Listing" caption))
3158      (let ((--src-block (org-odt-format-code src-block info)))
3159        (if (not (plist-get attributes :textbox)) --src-block
3160      (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
3161          "Text_20_body"
3162          (org-odt--textbox --src-block nil nil nil)))))))
3163
3164
3165 ;;;; Statistics Cookie
3166
3167 (defun org-odt-statistics-cookie (statistics-cookie _contents _info)
3168   "Transcode a STATISTICS-COOKIE object from Org to ODT.
3169 CONTENTS is nil.  INFO is a plist holding contextual information."
3170   (let ((cookie-value (org-element-property :value statistics-cookie)))
3171     (format "<text:span text:style-name=\"%s\">%s</text:span>"
3172         "OrgCode" cookie-value)))
3173
3174
3175 ;;;; Strike-Through
3176
3177 (defun org-odt-strike-through (_strike-through contents _info)
3178   "Transcode STRIKE-THROUGH from Org to ODT.
3179 CONTENTS is the text with strike-through markup.  INFO is a plist
3180 holding contextual information."
3181   (format "<text:span text:style-name=\"%s\">%s</text:span>"
3182       "Strikethrough" contents))
3183
3184
3185 ;;;; Subscript
3186
3187 (defun org-odt-subscript (_subscript contents _info)
3188   "Transcode a SUBSCRIPT object from Org to ODT.
3189 CONTENTS is the contents of the object.  INFO is a plist holding
3190 contextual information."
3191   (format "<text:span text:style-name=\"%s\">%s</text:span>"
3192       "OrgSubscript" contents))
3193
3194
3195 ;;;; Superscript
3196
3197 (defun org-odt-superscript (_superscript contents _info)
3198   "Transcode a SUPERSCRIPT object from Org to ODT.
3199 CONTENTS is the contents of the object.  INFO is a plist holding
3200 contextual information."
3201   (format "<text:span text:style-name=\"%s\">%s</text:span>"
3202       "OrgSuperscript" contents))
3203
3204
3205 ;;;; Table Cell
3206
3207 (defun org-odt-table-style-spec (element info)
3208   (let* ((table (org-export-get-parent-table element))
3209      (table-attributes (org-export-read-attribute :attr_odt table))
3210      (table-style (plist-get table-attributes :style)))
3211     (assoc table-style (plist-get info :odt-table-styles))))
3212
3213 (defun org-odt-get-table-cell-styles (table-cell info)
3214   "Retrieve styles applicable to a table cell.
3215 R and C are (zero-based) row and column numbers of the table
3216 cell.  STYLE-SPEC is an entry in `org-odt-table-styles'
3217 applicable to the current table.  It is nil if the table is not
3218 associated with any style attributes.
3219
3220 Return a cons of (TABLE-CELL-STYLE-NAME . PARAGRAPH-STYLE-NAME).
3221
3222 When STYLE-SPEC is nil, style the table cell the conventional way
3223 - choose cell borders based on row and column groupings and
3224 choose paragraph alignment based on `org-col-cookies' text
3225 property.  See also
3226 `org-odt-get-paragraph-style-cookie-for-table-cell'.
3227
3228 When STYLE-SPEC is non-nil, ignore the above cookie and return
3229 styles congruent with the ODF-1.2 specification."
3230   (let* ((table-cell-address (org-export-table-cell-address table-cell info))
3231      (r (car table-cell-address)) (c (cdr table-cell-address))
3232      (style-spec (org-odt-table-style-spec table-cell info))
3233      (table-dimensions (org-export-table-dimensions
3234                 (org-export-get-parent-table table-cell)
3235                 info)))
3236     (when style-spec
3237       ;; LibreOffice - particularly the Writer - honors neither table
3238       ;; templates nor custom table-cell styles.  Inorder to retain
3239       ;; inter-operability with LibreOffice, only automatic styles are
3240       ;; used for styling of table-cells.  The current implementation is
3241       ;; congruent with ODF-1.2 specification and hence is
3242       ;; future-compatible.
3243
3244       ;; Additional Note: LibreOffice's AutoFormat facility for tables -
3245       ;; which recognizes as many as 16 different cell types - is much
3246       ;; richer. Unfortunately it is NOT amenable to easy configuration
3247       ;; by hand.
3248       (let* ((template-name (nth 1 style-spec))
3249          (cell-style-selectors (nth 2 style-spec))
3250          (cell-type
3251           (cond
3252            ((and (cdr (assq 'use-first-column-styles cell-style-selectors))
3253              (= c 0)) "FirstColumn")
3254            ((and (cdr (assq 'use-last-column-styles cell-style-selectors))
3255              (= (1+ c) (cdr table-dimensions)))
3256         "LastColumn")
3257            ((and (cdr (assq 'use-first-row-styles cell-style-selectors))
3258              (= r 0)) "FirstRow")
3259            ((and (cdr (assq 'use-last-row-styles cell-style-selectors))
3260              (= (1+ r) (car table-dimensions)))
3261         "LastRow")
3262            ((and (cdr (assq 'use-banding-rows-styles cell-style-selectors))
3263              (= (% r 2) 1)) "EvenRow")
3264            ((and (cdr (assq 'use-banding-rows-styles cell-style-selectors))
3265              (= (% r 2) 0)) "OddRow")
3266            ((and (cdr (assq 'use-banding-columns-styles cell-style-selectors))
3267              (= (% c 2) 1)) "EvenColumn")
3268            ((and (cdr (assq 'use-banding-columns-styles cell-style-selectors))
3269              (= (% c 2) 0)) "OddColumn")
3270            (t ""))))
3271     (concat template-name cell-type)))))
3272
3273 (defun org-odt-table-cell (table-cell contents info)
3274   "Transcode a TABLE-CELL element from Org to ODT.
3275 CONTENTS is nil.  INFO is a plist used as a communication
3276 channel."
3277   (let* ((table-cell-address (org-export-table-cell-address table-cell info))
3278      (r (car table-cell-address))
3279      (c (cdr table-cell-address))
3280      (horiz-span (or (org-export-table-cell-width table-cell info) 0))
3281      (table-row (org-export-get-parent table-cell))
3282      (custom-style-prefix (org-odt-get-table-cell-styles
3283                    table-cell info))
3284      (paragraph-style
3285       (or
3286        (and custom-style-prefix
3287         (format "%sTableParagraph" custom-style-prefix))
3288        (concat
3289         (cond
3290          ((and (= 1 (org-export-table-row-group table-row info))
3291            (org-export-table-has-header-p
3292             (org-export-get-parent-table table-row) info))
3293           "OrgTableHeading")
3294          ((let* ((table (org-export-get-parent-table table-cell))
3295              (table-attrs (org-export-read-attribute :attr_odt table))
3296              (table-header-columns
3297               (let ((cols (plist-get table-attrs :header-columns)))
3298             (and cols (read cols)))))
3299         (<= c (cond ((wholenump table-header-columns)
3300                  (- table-header-columns 1))
3301                 (table-header-columns 0)
3302                 (t -1))))
3303           "OrgTableHeading")
3304          (t "OrgTableContents"))
3305         (capitalize (symbol-name (org-export-table-cell-alignment
3306                       table-cell info))))))
3307      (cell-style-name
3308       (or
3309        (and custom-style-prefix (format "%sTableCell"
3310                         custom-style-prefix))
3311        (concat
3312         "OrgTblCell"
3313         (when (or (org-export-table-row-starts-rowgroup-p table-row info)
3314               (zerop r)) "T")
3315         (when (org-export-table-row-ends-rowgroup-p table-row info) "B")
3316         (when (and (org-export-table-cell-starts-colgroup-p table-cell info)
3317                (not (zerop c)) ) "L"))))
3318      (cell-attributes
3319       (concat
3320        (format " table:style-name=\"%s\"" cell-style-name)
3321        (and (> horiz-span 0)
3322         (format " table:number-columns-spanned=\"%d\""
3323             (1+ horiz-span))))))
3324     (unless contents (setq contents ""))
3325     (concat
3326      (cl-assert paragraph-style)
3327      (format "\n<table:table-cell%s>\n%s\n</table:table-cell>"
3328          cell-attributes
3329          (let ((table-cell-contents (org-element-contents table-cell)))
3330            (if (eq (org-element-class (car table-cell-contents)) 'element)
3331            contents
3332          (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
3333              paragraph-style contents))))
3334      (let (s)
3335        (dotimes (_ horiz-span s)
3336      (setq s (concat s "\n<table:covered-table-cell/>"))))
3337      "\n")))
3338
3339
3340 ;;;; Table Row
3341
3342 (defun org-odt-table-row (table-row contents info)
3343   "Transcode a TABLE-ROW element from Org to ODT.
3344 CONTENTS is the contents of the row.  INFO is a plist used as a
3345 communication channel."
3346   ;; Rules are ignored since table separators are deduced from
3347   ;; borders of the current row.
3348   (when (eq (org-element-property :type table-row) 'standard)
3349     (let* ((rowgroup-tags
3350         (if (and (= 1 (org-export-table-row-group table-row info))
3351              (org-export-table-has-header-p
3352               (org-export-get-parent-table table-row) info))
3353         ;; If the row belongs to the first rowgroup and the
3354         ;; table has more than one row groups, then this row
3355         ;; belongs to the header row group.
3356         '("\n<table:table-header-rows>" . "\n</table:table-header-rows>")
3357           ;; Otherwise, it belongs to non-header row group.
3358           '("\n<table:table-rows>" . "\n</table:table-rows>"))))
3359       (concat
3360        ;; Does this row begin a rowgroup?
3361        (when (org-export-table-row-starts-rowgroup-p table-row info)
3362      (car rowgroup-tags))
3363        ;; Actual table row
3364        (format "\n<table:table-row>\n%s\n</table:table-row>" contents)
3365        ;; Does this row end a rowgroup?
3366        (when (org-export-table-row-ends-rowgroup-p table-row info)
3367      (cdr rowgroup-tags))))))
3368
3369
3370 ;;;; Table
3371
3372 (defun org-odt-table-first-row-data-cells (table info)
3373   (let ((table-row
3374      (org-element-map table 'table-row
3375        (lambda (row)
3376          (unless (eq (org-element-property :type row) 'rule) row))
3377        info 'first-match))
3378     (special-column-p (org-export-table-has-special-column-p table)))
3379     (if (not special-column-p) (org-element-contents table-row)
3380       (cdr (org-element-contents table-row)))))
3381
3382 (defun org-odt--table (table contents info)
3383   "Transcode a TABLE element from Org to ODT.
3384 CONTENTS is the contents of the table.  INFO is a plist holding
3385 contextual information."
3386   (cl-case (org-element-property :type table)
3387     ;; Case 1: table.el doesn't support export to OD format.  Strip
3388     ;; such tables from export.
3389     (table.el
3390      (prog1 nil
3391        (message
3392     (concat
3393      "(ox-odt): Found table.el-type table in the source Org file."
3394      "  table.el doesn't support export to ODT format."
3395      "  Stripping the table from export."))))
3396     ;; Case 2: Native Org tables.
3397     (otherwise
3398      (let* ((captions (org-odt-format-label table info 'definition))
3399         (caption (car captions)) (short-caption (cdr captions))
3400         (attributes (org-export-read-attribute :attr_odt table))
3401         (custom-table-style (nth 1 (org-odt-table-style-spec table info)))
3402         (table-column-specs
3403          (lambda (table info)
3404            (let* ((table-style (or custom-table-style "OrgTable"))
3405               (column-style (format "%sColumn" table-style)))
3406          (mapconcat
3407           (lambda (table-cell)
3408             (let ((width (1+ (or (org-export-table-cell-width
3409                       table-cell info) 0)))
3410               (s (format
3411                   "\n<table:table-column table:style-name=\"%s\"/>"
3412                   column-style))
3413               out)
3414               (dotimes (_ width out) (setq out (concat s out)))))
3415           (org-odt-table-first-row-data-cells table info) "\n")))))
3416        (concat
3417     ;; caption.
3418     (when caption
3419       (format "\n<text:p text:style-name=\"%s\">%s</text:p>"
3420           "Table" caption))
3421     ;; begin table.
3422     (let* ((automatic-name
3423         (org-odt-add-automatic-style "Table" attributes)))
3424       (format
3425        "\n<table:table table:style-name=\"%s\"%s>"
3426        (or custom-table-style (cdr automatic-name) "OrgTable")
3427        (concat (when short-caption
3428              (format " table:name=\"%s\"" short-caption)))))
3429     ;; column specification.
3430     (funcall table-column-specs table info)
3431     ;; actual contents.
3432     "\n" contents
3433     ;; end table.
3434     "</table:table>")))))
3435
3436 (defun org-odt-table (table contents info)
3437   "Transcode a TABLE element from Org to ODT.
3438 CONTENTS is the contents of the table.  INFO is a plist holding
3439 contextual information.
3440
3441 Use `org-odt--table' to typeset the table.  Handle details
3442 pertaining to indentation here."
3443   (let* ((--element-preceded-by-table-p
3444       (lambda (element info)
3445         (cl-loop for el in (org-export-get-previous-element element info t)
3446              thereis (eq (org-element-type el) 'table))))
3447      (--walk-list-genealogy-and-collect-tags
3448       (lambda (table info)
3449         (let* ((genealogy (org-element-lineage table))
3450            (list-genealogy
3451             (when (eq (org-element-type (car genealogy)) 'item)
3452               (cl-loop for el in genealogy
3453                    when (memq (org-element-type el)
3454                       '(item plain-list))
3455                    collect el)))
3456            (llh-genealogy
3457             (apply #'nconc
3458                (cl-loop
3459                 for el in genealogy
3460                 when (and (eq (org-element-type el) 'headline)
3461                       (org-export-low-level-p el info))
3462                 collect
3463                 (list el
3464                   (assq 'headline
3465                     (org-element-contents
3466                      (org-export-get-parent el)))))))
3467            parent-list)
3468           (nconc
3469            ;; Handle list genealogy.
3470            (cl-loop
3471         for el in list-genealogy collect
3472         (cl-case (org-element-type el)
3473           (plain-list
3474            (setq parent-list el)
3475            (cons "</text:list>"
3476              (format "\n<text:list text:style-name=\"%s\" %s>"
3477                  (cl-case (org-element-property :type el)
3478                    (ordered "OrgNumberedList")
3479                    (unordered "OrgBulletedList")
3480                    (descriptive-1 "OrgDescriptionList")
3481                    (descriptive-2 "OrgDescriptionList"))
3482                  "text:continue-numbering=\"true\"")))
3483           (item
3484            (cond
3485             ((not parent-list)
3486              (if (funcall --element-preceded-by-table-p table info)
3487              '("</text:list-header>" . "<text:list-header>")
3488                '("</text:list-item>" . "<text:list-header>")))
3489             ((funcall --element-preceded-by-table-p
3490                   parent-list info)
3491              '("</text:list-header>" . "<text:list-header>"))
3492             (t '("</text:list-item>" . "<text:list-item>"))))))
3493            ;; Handle low-level headlines.
3494            (cl-loop for el in llh-genealogy
3495             with step = 'item collect
3496             (cl-case step
3497               (plain-list
3498                (setq step 'item) ; Flip-flop
3499                (setq parent-list el)
3500                (cons "</text:list>"
3501                  (format "\n<text:list text:style-name=\"%s\" %s>"
3502                      (if (org-export-numbered-headline-p
3503                           el info)
3504                          "OrgNumberedList"
3505                        "OrgBulletedList")
3506                      "text:continue-numbering=\"true\"")))
3507               (item
3508                (setq step 'plain-list) ; Flip-flop
3509                (cond
3510                 ((not parent-list)
3511                  (if (funcall --element-preceded-by-table-p table info)
3512                  '("</text:list-header>" . "<text:list-header>")
3513                    '("</text:list-item>" . "<text:list-header>")))
3514                 ((let ((section? (org-export-get-previous-element
3515                           parent-list info)))
3516                    (and section?
3517                     (eq (org-element-type section?) 'section)
3518                     (assq 'table (org-element-contents section?))))
3519                  '("</text:list-header>" . "<text:list-header>"))
3520                 (t
3521                  '("</text:list-item>" . "<text:list-item>"))))))))))
3522      (close-open-tags (funcall --walk-list-genealogy-and-collect-tags
3523                    table info)))
3524     ;; OpenDocument schema does not permit table to occur within a
3525     ;; list item.
3526
3527     ;; One solution - the easiest and lightweight, in terms of
3528     ;; implementation - is to put the table in an indented text box
3529     ;; and make the text box part of the list-item.  Unfortunately if
3530     ;; the table is big and spans multiple pages, the text box could
3531     ;; overflow.  In this case, the following attribute will come
3532     ;; handy.
3533
3534     ;; ,---- From OpenDocument-v1.1.pdf
3535     ;; | 15.27.28 Overflow behavior
3536     ;; |
3537     ;; | For text boxes contained within text document, the
3538     ;; | style:overflow-behavior property specifies the behavior of text
3539     ;; | boxes where the containing text does not fit into the text
3540     ;; | box.
3541     ;; |
3542     ;; | If the attribute's value is clip, the text that does not fit
3543     ;; | into the text box is not displayed.
3544     ;; |
3545     ;; | If the attribute value is auto-create-new-frame, a new frame
3546     ;; | will be created on the next page, with the same position and
3547     ;; | dimensions of the original frame.
3548     ;; |
3549     ;; | If the style:overflow-behavior property's value is
3550     ;; | auto-create-new-frame and the text box has a minimum width or
3551     ;; | height specified, then the text box will grow until the page
3552     ;; | bounds are reached before a new frame is created.
3553     ;; `----
3554
3555     ;; Unfortunately, LibreOffice-3.4.6 doesn't honor
3556     ;; auto-create-new-frame property and always resorts to clipping
3557     ;; the text box.  This results in table being truncated.
3558
3559     ;; So we solve the problem the hard (and fun) way using list
3560     ;; continuations.
3561
3562     ;; The problem only becomes more interesting if you take in to
3563     ;; account the following facts:
3564     ;;
3565     ;; - Description lists are simulated as plain lists.
3566     ;; - Low-level headlines can be listified.
3567     ;; - In Org mode, a table can occur not only as a regular list
3568     ;;   item, but also within description lists and low-level
3569     ;;   headlines.
3570
3571     ;; See `org-odt-translate-description-lists' and
3572     ;; `org-odt-translate-low-level-headlines' for how this is
3573     ;; tackled.
3574
3575     (concat "\n"
3576         ;; Discontinue the list.
3577         (mapconcat 'car close-open-tags "\n")
3578         ;; Put the table in an indented section.
3579         (let* ((table (org-odt--table table contents info))
3580            (level (/ (length (mapcar 'car close-open-tags)) 2))
3581            (style (format "OrgIndentedSection-Level-%d" level)))
3582           (when table (org-odt-format-section table style)))
3583         ;; Continue the list.
3584         (mapconcat 'cdr (nreverse close-open-tags) "\n"))))
3585
3586
3587 ;;;; Target
3588
3589 (defun org-odt-target (target _contents info)
3590   "Transcode a TARGET object from Org to ODT.
3591 CONTENTS is nil.  INFO is a plist holding contextual
3592 information."
3593   (org-odt--target "" (org-export-get-reference target info)))
3594
3595
3596 ;;;; Timestamp
3597
3598 (defun org-odt-timestamp (timestamp _contents info)
3599   "Transcode a TIMESTAMP object from Org to ODT.
3600 CONTENTS is nil.  INFO is a plist used as a communication
3601 channel."
3602   (let ((type (org-element-property :type timestamp)))
3603     (if (not (plist-get info :odt-use-date-fields))
3604     (let ((value (org-odt-plain-text
3605               (org-timestamp-translate timestamp) info)))
3606       (cl-case (org-element-property :type timestamp)
3607         ((active active-range)
3608          (format "<text:span text:style-name=\"%s\">%s</text:span>"
3609              "OrgActiveTimestamp" value))
3610         ((inactive inactive-range)
3611          (format "<text:span text:style-name=\"%s\">%s</text:span>"
3612              "OrgInactiveTimestamp" value))
3613         (otherwise value)))
3614       (cl-case type
3615     (active
3616      (format "<text:span text:style-name=\"%s\">%s</text:span>"
3617          "OrgActiveTimestamp"
3618          (format "&lt;%s&gt;" (org-odt--format-timestamp timestamp))))
3619     (inactive
3620      (format "<text:span text:style-name=\"%s\">%s</text:span>"
3621          "OrgInactiveTimestamp"
3622          (format "[%s]" (org-odt--format-timestamp timestamp))))
3623     (active-range
3624      (format "<text:span text:style-name=\"%s\">%s</text:span>"
3625          "OrgActiveTimestamp"
3626          (format "&lt;%s&gt;&#x2013;&lt;%s&gt;"
3627              (org-odt--format-timestamp timestamp)
3628              (org-odt--format-timestamp timestamp 'end))))
3629     (inactive-range
3630      (format "<text:span text:style-name=\"%s\">%s</text:span>"
3631          "OrgInactiveTimestamp"
3632          (format "[%s]&#x2013;[%s]"
3633              (org-odt--format-timestamp timestamp)
3634              (org-odt--format-timestamp timestamp 'end))))
3635     (otherwise
3636      (format "<text:span text:style-name=\"%s\">%s</text:span>"
3637          "OrgDiaryTimestamp"
3638          (org-odt-plain-text (org-timestamp-translate timestamp)
3639                      info)))))))
3640
3641
3642 ;;;; Underline
3643
3644 (defun org-odt-underline (_underline contents _info)
3645   "Transcode UNDERLINE from Org to ODT.
3646 CONTENTS is the text with underline markup.  INFO is a plist
3647 holding contextual information."
3648   (format "<text:span text:style-name=\"%s\">%s</text:span>"
3649       "Underline" contents))
3650
3651
3652 ;;;; Verbatim
3653
3654 (defun org-odt-verbatim (verbatim _contents _info)
3655   "Transcode a VERBATIM object from Org to ODT.
3656 CONTENTS is nil.  INFO is a plist used as a communication
3657 channel."
3658   (format "<text:span text:style-name=\"%s\">%s</text:span>"
3659       "OrgCode" (org-odt--encode-plain-text
3660              (org-element-property :value verbatim))))
3661
3662
3663 ;;;; Verse Block
3664
3665 (defun org-odt-verse-block (_verse-block contents _info)
3666   "Transcode a VERSE-BLOCK element from Org to ODT.
3667 CONTENTS is verse block contents.  INFO is a plist holding
3668 contextual information."
3669   (format "\n<text:p text:style-name=\"OrgVerse\">%s</text:p>"
3670       (replace-regexp-in-string
3671        ;; Replace leading tabs and spaces.
3672        "^[ \t]+" #'org-odt--encode-tabs-and-spaces
3673        ;; Add line breaks to each line of verse.
3674        (replace-regexp-in-string
3675         "\\(<text:line-break/>\\)?[ \t]*$" "<text:line-break/>" contents))))
3676
3677
3678
3679 ;;; Filters
3680
3681 ;;; Images
3682
3683 (defun org-odt--translate-image-links (data _backend info)
3684   (org-export-insert-image-links data info org-odt-inline-image-rules))
3685
3686 ;;;; LaTeX fragments
3687
3688 (defun org-odt--translate-latex-fragments (tree _backend info)
3689   (let ((processing-type (plist-get info :with-latex))
3690     (count 0))
3691     ;; Normalize processing-type to one of dvipng, mathml or verbatim.
3692     ;; If the desired converter is not available, force verbatim
3693     ;; processing.
3694     (cl-case processing-type
3695       ((t mathml)
3696        (if (and (fboundp 'org-format-latex-mathml-available-p)
3697         (org-format-latex-mathml-available-p))
3698        (setq processing-type 'mathml)
3699      (message "LaTeX to MathML converter not available.")
3700      (setq processing-type 'verbatim)))
3701       ((dvipng imagemagick)
3702        (unless (and (org-check-external-command "latex" "" t)
3703             (org-check-external-command
3704              (if (eq processing-type 'dvipng) "dvipng" "convert") "" t))
3705      (message "LaTeX to PNG converter not available.")
3706      (setq processing-type 'verbatim)))
3707       (otherwise
3708        (message "Unknown LaTeX option.  Forcing verbatim.")
3709        (setq processing-type 'verbatim)))
3710
3711     ;; Store normalized value for later use.
3712     (when (plist-get info :with-latex)
3713       (plist-put info :with-latex processing-type))
3714     (message "Formatting LaTeX using %s" processing-type)
3715
3716     ;; Convert `latex-fragment's and `latex-environment's.
3717     (when (memq processing-type '(mathml dvipng imagemagick))
3718       (org-element-map tree '(latex-fragment latex-environment)
3719     (lambda (latex-*)
3720       (cl-incf count)
3721       (let* ((latex-frag (org-element-property :value latex-*))
3722          (input-file (plist-get info :input-file))
3723          (cache-dir (file-name-directory input-file))
3724          (cache-subdir (concat
3725                 (cl-case processing-type
3726                   ((dvipng imagemagick) "ltxpng/")
3727                   (mathml "ltxmathml/"))
3728                 (file-name-sans-extension
3729                  (file-name-nondirectory input-file))))
3730          (display-msg
3731           (cl-case processing-type
3732             ((dvipng imagemagick)
3733              (format "Creating LaTeX Image %d..." count))
3734             (mathml (format "Creating MathML snippet %d..." count))))
3735          ;; Get an Org-style link to PNG image or the MathML
3736          ;; file.
3737          (link
3738           (with-temp-buffer
3739             (insert latex-frag)
3740             ;; When converting to a PNG image, make sure to
3741             ;; copy all LaTeX header specifications from the
3742             ;; Org source.
3743             (unless (eq processing-type 'mathml)
3744               (let ((h (plist-get info :latex-header)))
3745             (when h
3746               (insert "\n"
3747                   (replace-regexp-in-string
3748                    "^" "#+LATEX_HEADER: " h)))))
3749             (org-format-latex cache-subdir nil nil cache-dir
3750                       nil display-msg nil
3751                       processing-type)
3752             (goto-char (point-min))
3753             (skip-chars-forward " \t\n")
3754             (org-element-link-parser))))
3755         (if (not (eq 'link (org-element-type link)))
3756         (message "LaTeX Conversion failed.")
3757           ;; Conversion succeeded.  Parse above Org-style link to
3758           ;; a `link' object.
3759           (let ((replacement
3760              (cl-case (org-element-type latex-*)
3761                ;;LaTeX environment.  Mimic a "standalone image
3762                ;; or formula" by enclosing the `link' in
3763                ;; a `paragraph'.  Copy over original
3764                ;; attributes, captions to the enclosing
3765                ;; paragraph.
3766                (latex-environment
3767             (org-element-adopt-elements
3768              (list 'paragraph
3769                    (list :style "OrgFormula"
3770                      :name
3771                      (org-element-property :name latex-*)
3772                      :caption
3773                      (org-element-property :caption latex-*)))
3774              link))
3775                ;; LaTeX fragment.  No special action.
3776                (latex-fragment link))))
3777         ;; Note down the object that link replaces.
3778         (org-element-put-property replacement :replaces
3779                       (list (org-element-type latex-*)
3780                         (list :value latex-frag)))
3781         ;; Restore blank after initial element or object.
3782         (org-element-put-property
3783          replacement :post-blank
3784          (org-element-property :post-blank latex-*))
3785         ;; Replace now.
3786         (org-element-set-element latex-* replacement)))))
3787     info nil nil t)))
3788   tree)
3789
3790
3791 ;;;; Description lists
3792
3793 ;; This translator is necessary to handle indented tables in a uniform
3794 ;; manner.  See comment in `org-odt--table'.
3795
3796 (defun org-odt--translate-description-lists (tree _backend info)
3797   ;; OpenDocument has no notion of a description list.  So simulate it
3798   ;; using plain lists.  Description lists in the exported document
3799   ;; are typeset in the same manner as they are in a typical HTML
3800   ;; document.
3801   ;;
3802   ;; Specifically, a description list like this:
3803   ;;
3804   ;;     ,----
3805   ;;     | - term-1 :: definition-1
3806   ;;     | - term-2 :: definition-2
3807   ;;     `----
3808   ;;
3809   ;; gets translated in to the following form:
3810   ;;
3811   ;;     ,----
3812   ;;     | - term-1
3813   ;;     |   - definition-1
3814   ;;     | - term-2
3815   ;;     |   - definition-2
3816   ;;     `----
3817   ;;
3818   ;; Further effect is achieved by fixing the OD styles as below:
3819   ;;
3820   ;; 1. Set the :type property of the simulated lists to
3821   ;;    `descriptive-1' and `descriptive-2'.  Map these to list-styles
3822   ;;    that has *no* bullets whatsoever.
3823   ;;
3824   ;; 2. The paragraph containing the definition term is styled to be
3825   ;;    in bold.
3826   ;;
3827   (org-element-map tree 'plain-list
3828     (lambda (el)
3829       (when (eq (org-element-property :type el) 'descriptive)
3830     (org-element-set-element
3831      el
3832      (apply 'org-element-adopt-elements
3833         (list 'plain-list (list :type 'descriptive-1))
3834         (mapcar
3835          (lambda (item)
3836            (org-element-adopt-elements
3837             (list 'item (list :checkbox (org-element-property
3838                          :checkbox item)))
3839             (list 'paragraph (list :style "Text_20_body_20_bold")
3840               (or (org-element-property :tag item) "(no term)"))
3841             (org-element-adopt-elements
3842              (list 'plain-list (list :type 'descriptive-2))
3843              (apply 'org-element-adopt-elements
3844                 (list 'item nil)
3845                 (org-element-contents item)))))
3846          (org-element-contents el)))))
3847       nil)
3848     info)
3849   tree)
3850
3851 ;;;; List tables
3852
3853 ;; Lists that are marked with attribute `:list-table' are called as
3854 ;; list tables.  They will be rendered as a table within the exported
3855 ;; document.
3856
3857 ;; Consider an example.  The following list table
3858 ;;
3859 ;; #+attr_odt :list-table t
3860 ;; - Row 1
3861 ;;   - 1.1
3862 ;;   - 1.2
3863 ;;   - 1.3
3864 ;; - Row 2
3865 ;;   - 2.1
3866 ;;   - 2.2
3867 ;;   - 2.3
3868 ;;
3869 ;; will be exported as though it were an Org table like the one show
3870 ;; below.
3871 ;;
3872 ;; | Row 1 | 1.1 | 1.2 | 1.3 |
3873 ;; | Row 2 | 2.1 | 2.2 | 2.3 |
3874 ;;
3875 ;; Note that org-tables are NOT multi-line and each line is mapped to
3876 ;; a unique row in the exported document.  So if an exported table
3877 ;; needs to contain a single paragraph (with copious text) it needs to
3878 ;; be typed up in a single line.  Editing such long lines using the
3879 ;; table editor will be a cumbersome task.  Furthermore inclusion of
3880 ;; multi-paragraph text in a table cell is well-nigh impossible.
3881 ;;
3882 ;; A LIST-TABLE circumvents above problems.
3883 ;;
3884 ;; Note that in the example above the list items could be paragraphs
3885 ;; themselves and the list can be arbitrarily deep.
3886 ;;
3887 ;; Inspired by following thread:
3888 ;; https://lists.gnu.org/r/emacs-orgmode/2011-03/msg01101.html
3889
3890 ;; Translate lists to tables
3891
3892 (defun org-odt--translate-list-tables (tree _backend info)
3893   (org-element-map tree 'plain-list
3894     (lambda (l1-list)
3895       (when (org-export-read-attribute :attr_odt l1-list :list-table)
3896     ;; Replace list with table.
3897     (org-element-set-element
3898      l1-list
3899      ;; Build replacement table.
3900      (apply 'org-element-adopt-elements
3901         (list 'table '(:type org :attr_odt (":style \"GriddedTable\"")))
3902         (org-element-map l1-list 'item
3903           (lambda (l1-item)
3904             (let* ((l1-item-contents (org-element-contents l1-item))
3905                l1-item-leading-text l2-list)
3906               ;; Remove Level-2 list from the Level-item.  It
3907               ;; will be subsequently attached as table-cells.
3908               (let ((cur l1-item-contents) prev)
3909             (while (and cur (not (eq (org-element-type (car cur))
3910                          'plain-list)))
3911               (setq prev cur)
3912               (setq cur (cdr cur)))
3913             (when prev
3914               (setcdr prev nil)
3915               (setq l2-list (car cur)))
3916             (setq l1-item-leading-text l1-item-contents))
3917               ;; Level-1 items start a table row.
3918               (apply 'org-element-adopt-elements
3919                  (list 'table-row (list :type 'standard))
3920                  ;;  Leading text of level-1 item define
3921                  ;;  the first table-cell.
3922                  (apply 'org-element-adopt-elements
3923                     (list 'table-cell nil)
3924                     l1-item-leading-text)
3925                  ;; Level-2 items define subsequent
3926                  ;; table-cells of the row.
3927                  (org-element-map l2-list 'item
3928                    (lambda (l2-item)
3929                  (apply 'org-element-adopt-elements
3930                     (list 'table-cell nil)
3931                     (org-element-contents l2-item)))
3932                    info nil 'item))))
3933           info nil 'item))))
3934       nil)
3935     info)
3936   tree)
3937
3938
3939 ;;; Interactive functions
3940
3941 (defun org-odt-create-manifest-file-entry (&rest args)
3942   (push args org-odt-manifest-file-entries))
3943
3944 (defun org-odt-write-manifest-file ()
3945   (make-directory (concat org-odt-zip-dir "META-INF"))
3946   (let ((manifest-file (concat org-odt-zip-dir "META-INF/manifest.xml")))
3947     (with-current-buffer
3948     (let ((nxml-auto-insert-xml-declaration-flag nil))
3949       (find-file-noselect manifest-file t))
3950       (insert
3951        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
3952      <manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\" manifest:version=\"1.2\">\n")
3953       (dolist (file-entry org-odt-manifest-file-entries)
3954     (let* ((version (nth 2 file-entry))
3955            (extra (if (not version) ""
3956             (format " manifest:version=\"%s\"" version))))
3957       (insert
3958        (format org-odt-manifest-file-entry-tag
3959            (nth 0 file-entry) (nth 1 file-entry) extra))))
3960       (insert "\n</manifest:manifest>"))))
3961
3962 (defmacro org-odt--export-wrap (out-file &rest body)
3963   `(let* ((--out-file ,out-file)
3964       (out-file-type (file-name-extension --out-file))
3965       (org-odt-xml-files '("META-INF/manifest.xml" "content.xml"
3966                    "meta.xml" "styles.xml"))
3967       ;; Initialize temporary workarea.  All files that end up in
3968       ;; the exported document get parked/created here.
3969       (org-odt-zip-dir (file-name-as-directory
3970                 (make-temp-file (format "%s-" out-file-type) t)))
3971       (org-odt-manifest-file-entries nil)
3972       (--cleanup-xml-buffers
3973        (lambda ()
3974          ;; Kill all XML buffers.
3975          (dolist (file org-odt-xml-files)
3976            (let ((buf (find-buffer-visiting
3977                (concat org-odt-zip-dir file))))
3978          (when buf
3979            (with-current-buffer buf
3980              (set-buffer-modified-p nil)
3981              (kill-buffer buf)))))
3982          ;; Delete temporary directory and also other embedded
3983          ;; files that get copied there.
3984          (delete-directory org-odt-zip-dir t))))
3985      (condition-case err
3986      (progn
3987        (unless (executable-find "zip")
3988          ;; Not at all OSes ship with zip by default
3989          (error "Executable \"zip\" needed for creating OpenDocument files"))
3990        ;; Do export.  This creates a bunch of xml files ready to be
3991        ;; saved and zipped.
3992        (progn ,@body)
3993        ;; Create a manifest entry for content.xml.
3994        (org-odt-create-manifest-file-entry "text/xml" "content.xml")
3995        ;; Write mimetype file
3996        (let* ((mimetypes
3997            '(("odt" . "application/vnd.oasis.opendocument.text")
3998              ("odf" .  "application/vnd.oasis.opendocument.formula")))
3999           (mimetype (cdr (assoc-string out-file-type mimetypes t))))
4000          (unless mimetype
4001            (error "Unknown OpenDocument backend %S" out-file-type))
4002          (write-region mimetype nil (concat org-odt-zip-dir "mimetype"))
4003          (org-odt-create-manifest-file-entry mimetype "/" "1.2"))
4004        ;; Write out the manifest entries before zipping
4005        (org-odt-write-manifest-file)
4006        ;; Save all XML files.
4007        (dolist (file org-odt-xml-files)
4008          (let ((buf (find-buffer-visiting
4009              (concat org-odt-zip-dir file))))
4010            (when buf
4011          (with-current-buffer buf
4012            ;; Prettify output if needed.
4013            (when org-odt-prettify-xml
4014              (indent-region (point-min) (point-max)))
4015            (save-buffer 0)))))
4016        ;; Run zip.
4017        (let* ((target --out-file)
4018           (target-name (file-name-nondirectory target))
4019           (cmds `(("zip" "-mX0" ,target-name "mimetype")
4020               ("zip" "-rmTq" ,target-name "."))))
4021          ;; If a file with same name as the desired output file
4022          ;; exists, remove it.
4023          (when (file-exists-p target)
4024            (delete-file target))
4025          ;; Zip up the xml files.
4026          (let ((coding-system-for-write 'no-conversion) exitcode err-string)
4027            (message "Creating ODT file...")
4028            ;; Switch temporarily to content.xml.  This way Zip
4029            ;; process will inherit `org-odt-zip-dir' as the current
4030            ;; directory.
4031            (with-current-buffer
4032            (find-file-noselect (concat org-odt-zip-dir "content.xml") t)
4033          (dolist (cmd cmds)
4034            (message "Running %s" (mapconcat 'identity cmd " "))
4035            (setq err-string
4036              (with-output-to-string
4037                (setq exitcode
4038                  (apply 'call-process (car cmd)
4039                     nil standard-output nil (cdr cmd)))))
4040            (or (zerop exitcode)
4041                (error (concat "Unable to create OpenDocument file."
4042                       "  Zip failed with error (%s)")
4043                   err-string)))))
4044          ;; Move the zip file from temporary work directory to
4045          ;; user-mandated location.
4046          (rename-file (concat org-odt-zip-dir target-name) target)
4047          (message "Created %s" (expand-file-name target))
4048          ;; Cleanup work directory and work files.
4049          (funcall --cleanup-xml-buffers)
4050          ;; Open the OpenDocument file in archive-mode for
4051          ;; examination.
4052          (find-file-noselect target t)
4053          ;; Return exported file.
4054          (cond
4055           ;; Case 1: Conversion desired on exported file.  Run the
4056           ;; converter on the OpenDocument file.  Return the
4057           ;; converted file.
4058           (org-odt-preferred-output-format
4059            (or (org-odt-convert target org-odt-preferred-output-format)
4060            target))
4061           ;; Case 2: No further conversion.  Return exported
4062           ;; OpenDocument file.
4063           (t target))))
4064        (error
4065     ;; Cleanup work directory and work files.
4066     (funcall --cleanup-xml-buffers)
4067     (message "OpenDocument export failed: %s"
4068          (error-message-string err))))))
4069
4070
4071 ;;;; Export to OpenDocument formula
4072
4073 ;;;###autoload
4074 (defun org-odt-export-as-odf (latex-frag &optional odf-file)
4075   "Export LATEX-FRAG as OpenDocument formula file ODF-FILE.
4076 Use `org-create-math-formula' to convert LATEX-FRAG first to
4077 MathML.  When invoked as an interactive command, use
4078 `org-latex-regexps' to infer LATEX-FRAG from currently active
4079 region.  If no LaTeX fragments are found, prompt for it.  Push
4080 MathML source to kill ring depending on the value of
4081 `org-export-copy-to-kill-ring'."
4082   (interactive
4083    `(,(let (frag)
4084     (setq frag (and (setq frag (and (region-active-p)
4085                     (buffer-substring (region-beginning)
4086                               (region-end))))
4087             (cl-loop for e in org-latex-regexps
4088                  thereis (when (string-match (nth 1 e) frag)
4089                        (match-string (nth 2 e) frag)))))
4090     (read-string "LaTeX Fragment: " frag nil frag))
4091      ,(let ((odf-filename (expand-file-name
4092                (concat
4093                 (file-name-sans-extension
4094                  (or (file-name-nondirectory buffer-file-name)))
4095                 "." "odf")
4096                (file-name-directory buffer-file-name))))
4097     (read-file-name "ODF filename: " nil odf-filename nil
4098             (file-name-nondirectory odf-filename)))))
4099   (let ((filename (or odf-file
4100               (expand-file-name
4101                (concat
4102             (file-name-sans-extension
4103              (or (file-name-nondirectory buffer-file-name)))
4104             "." "odf")
4105                (file-name-directory buffer-file-name)))))
4106     (org-odt--export-wrap
4107      filename
4108      (let* ((buffer (progn
4109               (require 'nxml-mode)
4110               (let ((nxml-auto-insert-xml-declaration-flag nil))
4111             (find-file-noselect (concat org-odt-zip-dir
4112                             "content.xml") t))))
4113         (coding-system-for-write 'utf-8)
4114         (save-buffer-coding-system 'utf-8))
4115        (set-buffer buffer)
4116        (set-buffer-file-coding-system coding-system-for-write)
4117        (let ((mathml (org-create-math-formula latex-frag)))
4118      (unless mathml (error "No Math formula created"))
4119      (insert mathml)
4120      ;; Add MathML to kill ring, if needed.
4121      (when (org-export--copy-to-kill-ring-p)
4122        (org-kill-new (buffer-string))))))))
4123
4124 ;;;###autoload
4125 (defun org-odt-export-as-odf-and-open ()
4126   "Export LaTeX fragment as OpenDocument formula and immediately open it.
4127 Use `org-odt-export-as-odf' to read LaTeX fragment and OpenDocument
4128 formula file."
4129   (interactive)
4130   (org-open-file (call-interactively 'org-odt-export-as-odf) 'system))
4131
4132
4133 ;;;; Export to OpenDocument Text
4134
4135 ;;;###autoload
4136 (defun org-odt-export-to-odt (&optional async subtreep visible-only ext-plist)
4137   "Export current buffer to a ODT file.
4138
4139 If narrowing is active in the current buffer, only export its
4140 narrowed part.
4141
4142 If a region is active, export that region.
4143
4144 A non-nil optional argument ASYNC means the process should happen
4145 asynchronously.  The resulting file should be accessible through
4146 the `org-export-stack' interface.
4147
4148 When optional argument SUBTREEP is non-nil, export the sub-tree
4149 at point, extracting information from the headline properties
4150 first.
4151
4152 When optional argument VISIBLE-ONLY is non-nil, don't export
4153 contents of hidden elements.
4154
4155 EXT-PLIST, when provided, is a property list with external
4156 parameters overriding Org default settings, but still inferior to
4157 file-local settings.
4158
4159 Return output file's name."
4160   (interactive)
4161   (let ((outfile (org-export-output-file-name ".odt" subtreep)))
4162     (if async
4163     (org-export-async-start (lambda (f) (org-export-add-to-stack f 'odt))
4164       `(expand-file-name
4165         (org-odt--export-wrap
4166          ,outfile
4167          (let* ((org-odt-embedded-images-count 0)
4168             (org-odt-embedded-formulas-count 0)
4169             (org-odt-automatic-styles nil)
4170             (org-odt-object-counters nil)
4171             ;; Let `htmlfontify' know that we are interested in
4172             ;; collecting styles.
4173             (hfy-user-sheet-assoc nil))
4174            ;; Initialize content.xml and kick-off the export
4175            ;; process.
4176            (let ((out-buf
4177               (progn
4178             (require 'nxml-mode)
4179             (let ((nxml-auto-insert-xml-declaration-flag nil))
4180               (find-file-noselect
4181                (concat org-odt-zip-dir "content.xml") t))))
4182              (output (org-export-as
4183                   'odt ,subtreep ,visible-only nil ,ext-plist)))
4184          (with-current-buffer out-buf
4185            (erase-buffer)
4186            (insert output)))))))
4187       (org-odt--export-wrap
4188        outfile
4189        (let* ((org-odt-embedded-images-count 0)
4190           (org-odt-embedded-formulas-count 0)
4191           (org-odt-automatic-styles nil)
4192           (org-odt-object-counters nil)
4193           ;; Let `htmlfontify' know that we are interested in collecting
4194           ;; styles.
4195           (hfy-user-sheet-assoc nil))
4196      ;; Initialize content.xml and kick-off the export process.
4197      (let ((output (org-export-as 'odt subtreep visible-only nil ext-plist))
4198            (out-buf (progn
4199               (require 'nxml-mode)
4200               (let ((nxml-auto-insert-xml-declaration-flag nil))
4201                 (find-file-noselect
4202                  (concat org-odt-zip-dir "content.xml") t)))))
4203        (with-current-buffer out-buf (erase-buffer) (insert output))))))))
4204
4205
4206 ;;;; Convert between OpenDocument and other formats
4207
4208 (defun org-odt-reachable-p (in-fmt out-fmt)
4209   "Return non-nil if IN-FMT can be converted to OUT-FMT."
4210   (catch 'done
4211     (let ((reachable-formats (org-odt-do-reachable-formats in-fmt)))
4212       (dolist (e reachable-formats)
4213     (let ((out-fmt-spec (assoc out-fmt (cdr e))))
4214       (when out-fmt-spec
4215         (throw 'done (cons (car e) out-fmt-spec))))))))
4216
4217 (defun org-odt-do-convert (in-file out-fmt &optional open)
4218   "Workhorse routine for `org-odt-convert'."
4219   (require 'browse-url)
4220   (let* ((in-file (let ((f (expand-file-name (or in-file buffer-file-name))))
4221             (if (file-readable-p f) f
4222               (error "Cannot read %s" in-file))))
4223      (in-fmt (file-name-extension in-file))
4224      (out-fmt (or out-fmt (error "Output format unspecified")))
4225      (how (or (org-odt-reachable-p in-fmt out-fmt)
4226           (error "Cannot convert from %s format to %s format?"
4227              in-fmt out-fmt)))
4228      (convert-process (car how))
4229      (out-file (concat (file-name-sans-extension in-file) "."
4230                (nth 1 (or (cdr how) out-fmt))))
4231      (extra-options (or (nth 2 (cdr how)) ""))
4232      (out-dir (file-name-directory in-file))
4233      (cmd (format-spec convert-process
4234                `((?i . ,(shell-quote-argument in-file))
4235                  (?I . ,(browse-url-file-url in-file))
4236                  (?f . ,out-fmt)
4237                  (?o . ,out-file)
4238                  (?O . ,(browse-url-file-url out-file))
4239                  (?d . , (shell-quote-argument out-dir))
4240                  (?D . ,(browse-url-file-url out-dir))
4241                  (?x . ,extra-options)))))
4242     (when (file-exists-p out-file)
4243       (delete-file out-file))
4244
4245     (message "Executing %s" cmd)
4246     (let ((cmd-output (shell-command-to-string cmd)))
4247       (message "%s" cmd-output))
4248
4249     (cond
4250      ((file-exists-p out-file)
4251       (message "Exported to %s" out-file)
4252       (when open
4253     (message "Opening %s..."  out-file)
4254     (org-open-file out-file 'system))
4255       out-file)
4256      (t
4257       (message "Export to %s failed" out-file)
4258       nil))))
4259
4260 (defun org-odt-do-reachable-formats (in-fmt)
4261   "Return verbose info about formats to which IN-FMT can be converted.
4262 Return a list where each element is of the
4263 form (CONVERTER-PROCESS . OUTPUT-FMT-ALIST).  See
4264 `org-odt-convert-processes' for CONVERTER-PROCESS and see
4265 `org-odt-convert-capabilities' for OUTPUT-FMT-ALIST."
4266   (let* ((converter
4267       (and org-odt-convert-process
4268            (cadr (assoc-string org-odt-convert-process
4269                    org-odt-convert-processes t))))
4270      (capabilities
4271       (and org-odt-convert-process
4272            (cadr (assoc-string org-odt-convert-process
4273                    org-odt-convert-processes t))
4274            org-odt-convert-capabilities))
4275      reachable-formats)
4276     (when converter
4277       (dolist (c capabilities)
4278     (when (member in-fmt (nth 1 c))
4279       (push (cons converter (nth 2 c)) reachable-formats))))
4280     reachable-formats))
4281
4282 (defun org-odt-reachable-formats (in-fmt)
4283   "Return list of formats to which IN-FMT can be converted.
4284 The list of the form (OUTPUT-FMT-1 OUTPUT-FMT-2 ...)."
4285   (copy-sequence
4286    (apply #'append (mapcar
4287             (lambda (e) (mapcar #'car (cdr e)))
4288             (org-odt-do-reachable-formats in-fmt)))))
4289
4290 (defun org-odt-convert-read-params ()
4291   "Return IN-FILE and OUT-FMT params for `org-odt-do-convert'.
4292 This is a helper routine for interactive use."
4293   (let* ((input (if (featurep 'ido) 'ido-completing-read 'completing-read))
4294      (in-file (read-file-name "File to be converted: "
4295                   nil buffer-file-name t))
4296      (in-fmt (file-name-extension in-file))
4297      (out-fmt-choices (org-odt-reachable-formats in-fmt))
4298      (out-fmt
4299       (or (and out-fmt-choices
4300            (funcall input "Output format: "
4301                 out-fmt-choices nil nil nil))
4302           (error
4303            "No known converter or no known output formats for %s files"
4304            in-fmt))))
4305     (list in-file out-fmt)))
4306
4307 ;;;###autoload
4308 (defun org-odt-convert (&optional in-file out-fmt open)
4309   "Convert IN-FILE to format OUT-FMT using a command line converter.
4310 IN-FILE is the file to be converted.  If unspecified, it defaults
4311 to variable `buffer-file-name'.  OUT-FMT is the desired output
4312 format.  Use `org-odt-convert-process' as the converter.  If OPEN
4313 is non-nil then the newly converted file is opened using
4314 `org-open-file'."
4315   (interactive
4316    (append (org-odt-convert-read-params) current-prefix-arg))
4317   (org-odt-do-convert in-file out-fmt open))
4318
4319 ;;; Library Initializations
4320
4321 (dolist (desc org-odt-file-extensions)
4322   ;; Let Emacs open all OpenDocument files in archive mode.
4323   (add-to-list 'auto-mode-alist
4324            (cons (concat  "\\." (car desc) "\\'") 'archive-mode)))
4325
4326 (provide 'ox-odt)
4327
4328 ;; Local variables:
4329 ;; generated-autoload-file: "org-loaddefs.el"
4330 ;; End:
4331
4332 ;;; ox-odt.el ends here