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