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