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