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