summaryrefslogtreecommitdiff
path: root/lisp/org/org-odt.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/org/org-odt.el')
-rw-r--r--lisp/org/org-odt.el2626
1 files changed, 2626 insertions, 0 deletions
diff --git a/lisp/org/org-odt.el b/lisp/org/org-odt.el
new file mode 100644
index 00000000000..838a327b0c2
--- /dev/null
+++ b/lisp/org/org-odt.el
@@ -0,0 +1,2626 @@
+;;; org-odt.el --- OpenDocumentText export for Org-mode
+
+;; Copyright (C) 2010-2011 Free Software Foundation, Inc.
+
+;; Author: Jambunathan K <kjambunathan at gmail dot com>
+;; Keywords: outlines, hypermedia, calendar, wp
+;; Homepage: http://orgmode.org
+;;
+;; This file is not (yet) part of GNU Emacs.
+;; However, it is distributed under the same license.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; Commentary:
+
+;;; Code:
+(eval-when-compile
+ (require 'cl))
+(require 'org-lparse)
+
+(defgroup org-export-odt nil
+ "Options specific for ODT export of Org-mode files."
+ :tag "Org Export ODT"
+ :group 'org-export)
+
+(defun org-odt-end-export ()
+ (org-odt-fixup-label-references)
+
+ ;; remove empty paragraphs
+ (goto-char (point-min))
+ (while (re-search-forward
+ "<text:p\\( text:style-name=\"Text_20_body\"\\)?>[ \r\n\t]*</text:p>"
+ nil t)
+ (replace-match ""))
+ (goto-char (point-min))
+
+ ;; Convert whitespace place holders
+ (goto-char (point-min))
+ (let (beg end n)
+ (while (setq beg (next-single-property-change (point) 'org-whitespace))
+ (setq n (get-text-property beg 'org-whitespace)
+ end (next-single-property-change beg 'org-whitespace))
+ (goto-char beg)
+ (delete-region beg end)
+ (insert (format "<span style=\"visibility:hidden;\">%s</span>"
+ (make-string n ?x)))))
+
+ ;; Remove empty lines at the beginning of the file.
+ (goto-char (point-min))
+ (when (looking-at "\\s-+\n") (replace-match ""))
+
+ ;; Remove display properties
+ (remove-text-properties (point-min) (point-max) '(display t)))
+
+(defvar org-odt-suppress-xref nil)
+(defconst org-export-odt-special-string-regexps
+ '(("\\\\-" . "&#x00ad;\\1") ; shy
+ ("---\\([^-]\\)" . "&#x2014;\\1") ; mdash
+ ("--\\([^-]\\)" . "&#x2013;\\1") ; ndash
+ ("\\.\\.\\." . "&#x2026;")) ; hellip
+ "Regular expressions for special string conversion.")
+
+(defconst org-odt-lib-dir (file-name-directory load-file-name)
+ "Location of ODT exporter.
+Use this to infer values of `org-odt-styles-dir' and
+`org-export-odt-schema-dir'.")
+
+(defvar org-odt-data-dir nil
+ "Data directory for ODT exporter.
+Use this to infer values of `org-odt-styles-dir' and
+`org-export-odt-schema-dir'.")
+
+(defconst org-odt-schema-dir-list
+ (list
+ (and org-odt-data-dir
+ (expand-file-name "./schema/" org-odt-data-dir)) ; bail out
+ (eval-when-compile
+ (and (boundp 'org-odt-data-dir) org-odt-data-dir ; see make install
+ (expand-file-name "./schema/" org-odt-data-dir)))
+ (expand-file-name "../contrib/odt/etc/schema/" org-odt-lib-dir) ; git
+ )
+ "List of directories to search for OpenDocument schema files.
+Use this list to set the default value of
+`org-export-odt-schema-dir'. The entries in this list are
+populated heuristically based on the values of `org-odt-lib-dir'
+and `org-odt-data-dir'.")
+
+(defcustom org-export-odt-schema-dir
+ (let* ((schema-dir
+ (catch 'schema-dir
+ (message "Debug (org-odt): Searching for OpenDocument schema files...")
+ (mapc
+ (lambda (schema-dir)
+ (when schema-dir
+ (message "Debug (org-odt): Trying %s..." schema-dir)
+ (when (and (file-readable-p
+ (expand-file-name "od-manifest-schema-v1.2-cs01.rnc"
+ schema-dir))
+ (file-readable-p
+ (expand-file-name "od-schema-v1.2-cs01.rnc"
+ schema-dir))
+ (file-readable-p
+ (expand-file-name "schemas.xml" schema-dir)))
+ (message "Debug (org-odt): Using schema files under %s"
+ schema-dir)
+ (throw 'schema-dir schema-dir))))
+ org-odt-schema-dir-list)
+ (message "Debug (org-odt): No OpenDocument schema files installed")
+ nil)))
+ schema-dir)
+ "Directory that contains OpenDocument schema files.
+
+This directory contains:
+1. rnc files for OpenDocument schema
+2. a \"schemas.xml\" file that specifies locating rules needed
+ for auto validation of OpenDocument XML files.
+
+Use the customize interface to set this variable. This ensures
+that `rng-schema-locating-files' is updated and auto-validation
+of OpenDocument XML takes place based on the value
+`rng-nxml-auto-validate-flag'.
+
+The default value of this variable varies depending on the
+version of org in use and is initialized from
+`org-odt-schema-dir-list'. The OASIS schema files are available
+only in the org's private git repository. It is *not* bundled
+with GNU ELPA tar or standard Emacs distribution."
+ :type '(choice
+ (const :tag "Not set" nil)
+ (directory :tag "Schema directory"))
+ :group 'org-export-odt
+ :set
+ (lambda (var value)
+ "Set `org-export-odt-schema-dir'.
+Also add it to `rng-schema-locating-files'."
+ (let ((schema-dir value))
+ (set var
+ (if (and
+ (file-readable-p
+ (expand-file-name "od-manifest-schema-v1.2-cs01.rnc" schema-dir))
+ (file-readable-p
+ (expand-file-name "od-schema-v1.2-cs01.rnc" schema-dir))
+ (file-readable-p
+ (expand-file-name "schemas.xml" schema-dir)))
+ schema-dir
+ (when value
+ (message "Error (org-odt): %s has no OpenDocument schema files"
+ value))
+ nil)))
+ (when org-export-odt-schema-dir
+ (eval-after-load 'rng-loc
+ '(add-to-list 'rng-schema-locating-files
+ (expand-file-name "schemas.xml"
+ org-export-odt-schema-dir))))))
+
+(defconst org-odt-styles-dir-list
+ (list
+ (and org-odt-data-dir
+ (expand-file-name "./styles/" org-odt-data-dir)) ; bail out
+ (eval-when-compile
+ (and (boundp 'org-odt-data-dir) org-odt-data-dir ; see make install
+ (expand-file-name "./styles/" org-odt-data-dir)))
+ (expand-file-name "../etc/styles/" org-odt-lib-dir) ; git
+ (expand-file-name "./etc/styles/" org-odt-lib-dir) ; elpa
+ (expand-file-name "./org/" data-directory) ; system
+ )
+ "List of directories to search for OpenDocument styles files.
+See `org-odt-styles-dir'. The entries in this list are populated
+heuristically based on the values of `org-odt-lib-dir' and
+`org-odt-data-dir'.")
+
+(defconst org-odt-styles-dir
+ (let* ((styles-dir
+ (catch 'styles-dir
+ (message "Debug (org-odt): Searching for OpenDocument styles files...")
+ (mapc (lambda (styles-dir)
+ (when styles-dir
+ (message "Debug (org-odt): Trying %s..." styles-dir)
+ (when (and (file-readable-p
+ (expand-file-name
+ "OrgOdtContentTemplate.xml" styles-dir))
+ (file-readable-p
+ (expand-file-name
+ "OrgOdtStyles.xml" styles-dir)))
+ (message "Debug (org-odt): Using styles under %s"
+ styles-dir)
+ (throw 'styles-dir styles-dir))))
+ org-odt-styles-dir-list)
+ nil)))
+ (unless styles-dir
+ (error "Error (org-odt): Cannot find factory styles files. Aborting."))
+ styles-dir)
+ "Directory that holds auxiliary XML files used by the ODT exporter.
+
+This directory contains the following XML files -
+ \"OrgOdtStyles.xml\" and \"OrgOdtContentTemplate.xml\". These
+ XML files are used as the default values of
+ `org-export-odt-styles-file' and
+ `org-export-odt-content-template-file'.
+
+The default value of this variable varies depending on the
+version of org in use and is initialized from
+`org-odt-styles-dir-list'. Note that the user could be using org
+from one of: org's own private git repository, GNU ELPA tar or
+standard Emacs.")
+
+(defvar org-odt-file-extensions
+ '(("odt" . "OpenDocument Text")
+ ("ott" . "OpenDocument Text Template")
+ ("odm" . "OpenDocument Master Document")
+ ("ods" . "OpenDocument Spreadsheet")
+ ("ots" . "OpenDocument Spreadsheet Template")
+ ("odg" . "OpenDocument Drawing (Graphics)")
+ ("otg" . "OpenDocument Drawing Template")
+ ("odp" . "OpenDocument Presentation")
+ ("otp" . "OpenDocument Presentation Template")
+ ("odi" . "OpenDocument Image")
+ ("odf" . "OpenDocument Formula")
+ ("odc" . "OpenDocument Chart")))
+
+(mapc
+ (lambda (desc)
+ ;; Let Org open all OpenDocument files using system-registered app
+ (add-to-list 'org-file-apps
+ (cons (concat "\\." (car desc) "\\'") 'system))
+ ;; Let Emacs open all OpenDocument files in archive mode
+ (add-to-list 'auto-mode-alist
+ (cons (concat "\\." (car desc) "\\'") 'archive-mode)))
+ org-odt-file-extensions)
+
+;; register the odt exporter with the pre-processor
+(add-to-list 'org-export-backends 'odt)
+
+;; register the odt exporter with org-lparse library
+(org-lparse-register-backend 'odt)
+
+(defun org-odt-unload-function ()
+ (org-lparse-unregister-backend 'odt)
+ (remove-hook 'org-export-preprocess-after-blockquote-hook
+ 'org-export-odt-preprocess-latex-fragments)
+ nil)
+
+(defcustom org-export-odt-content-template-file nil
+ "Template file for \"content.xml\".
+The exporter embeds the exported content just before
+\"</office:text>\" element.
+
+If unspecified, the file named \"OrgOdtContentTemplate.xml\"
+under `org-odt-styles-dir' is used."
+ :type 'file
+ :group 'org-export-odt)
+
+(defcustom org-export-odt-styles-file nil
+ "Default styles file for use with ODT export.
+Valid values are one of:
+1. nil
+2. path to a styles.xml file
+3. path to a *.odt or a *.ott file
+4. list of the form (ODT-OR-OTT-FILE (FILE-MEMBER-1 FILE-MEMBER-2
+...))
+
+In case of option 1, an in-built styles.xml is used. See
+`org-odt-styles-dir' for more information.
+
+In case of option 3, the specified file is unzipped and the
+styles.xml embedded therein is used.
+
+In case of option 4, the specified ODT-OR-OTT-FILE is unzipped
+and FILE-MEMBER-1, FILE-MEMBER-2 etc are copied in to the
+generated odt file. Use relative path for specifying the
+FILE-MEMBERS. styles.xml must be specified as one of the
+FILE-MEMBERS.
+
+Use options 1, 2 or 3 only if styles.xml alone suffices for
+achieving the desired formatting. Use option 4, if the styles.xml
+references additional files like header and footer images for
+achieving the desired formattting.
+
+Use \"#+ODT_STYLES_FILE: ...\" directive to set this variable on
+a per-file basis. For example,
+
+#+ODT_STYLES_FILE: \"/path/to/styles.xml\" or
+#+ODT_STYLES_FILE: (\"/path/to/file.ott\" (\"styles.xml\" \"image/hdr.png\"))."
+ :group 'org-export-odt
+ :type
+ '(choice
+ (const :tag "Factory settings" nil)
+ (file :must-match t :tag "styles.xml")
+ (file :must-match t :tag "ODT or OTT file")
+ (list :tag "ODT or OTT file + Members"
+ (file :must-match t :tag "ODF Text or Text Template file")
+ (cons :tag "Members"
+ (file :tag " Member" "styles.xml")
+ (repeat (file :tag "Member"))))))
+
+(eval-after-load 'org-exp
+ '(add-to-list 'org-export-inbuffer-options-extra
+ '("ODT_STYLES_FILE" :odt-styles-file)))
+
+(defconst org-export-odt-tmpdir-prefix "%s-")
+(defconst org-export-odt-bookmark-prefix "OrgXref.")
+
+(defvar org-export-odt-embed-images t
+ "Should the images be copied in to the odt file or just linked?")
+
+(defvar org-export-odt-inline-images 'maybe) ; counterpart of
+ ; `org-export-html-inline-images'
+
+(defcustom org-export-odt-inline-image-extensions
+ '("png" "jpeg" "jpg" "gif")
+ "Extensions of image files that can be inlined into HTML."
+ :type '(repeat (string :tag "Extension"))
+ :group 'org-export-odt)
+
+(defcustom org-export-odt-pixels-per-inch display-pixels-per-inch
+ ;; FIXME add docstring
+ ""
+ :type 'float
+ :group 'org-export-odt)
+
+(defcustom org-export-odt-create-custom-styles-for-srcblocks t
+ "Whether custom styles for colorized source blocks be automatically created.
+When this option is turned on, the exporter creates custom styles
+for source blocks based on the advice of `htmlfontify'. Creation
+of custom styles happen as part of `org-odt-hfy-face-to-css'.
+
+When this option is turned off exporter does not create such
+styles.
+
+Use the latter option if you do not want the custom styles to be
+based on your current display settings. It is necessary that the
+styles.xml already contains needed styles for colorizing to work.
+
+This variable is effective only if
+`org-export-odt-fontify-srcblocks' is turned on."
+ :group 'org-export-odt
+ :type 'boolean)
+
+(defvar org-export-odt-default-org-styles-alist
+ '((paragraph . ((default . "Text_20_body")
+ (fixedwidth . "OrgFixedWidthBlock")
+ (verse . "OrgVerse")
+ (quote . "Quotations")
+ (blockquote . "Quotations")
+ (center . "OrgCenter")
+ (left . "OrgLeft")
+ (right . "OrgRight")
+ (title . "OrgTitle")
+ (subtitle . "OrgSubtitle")
+ (footnote . "Footnote")
+ (src . "OrgSrcBlock")
+ (illustration . "Illustration")
+ (table . "Table")
+ (definition-term . "Text_20_body_20_bold")
+ (horizontal-line . "Horizontal_20_Line")))
+ (character . ((bold . "Bold")
+ (emphasis . "Emphasis")
+ (code . "OrgCode")
+ (verbatim . "OrgCode")
+ (strike . "Strikethrough")
+ (underline . "Underline")
+ (subscript . "OrgSubscript")
+ (superscript . "OrgSuperscript")))
+ (list . ((ordered . "OrgNumberedList")
+ (unordered . "OrgBulletedList")
+ (description . "OrgDescriptionList"))))
+ "Default styles for various entities.")
+
+(defvar org-export-odt-org-styles-alist org-export-odt-default-org-styles-alist)
+(defun org-odt-get-style-name-for-entity (category &optional entity)
+ (let ((entity (or entity 'default)))
+ (or
+ (cdr (assoc entity (cdr (assoc category
+ org-export-odt-org-styles-alist))))
+ (cdr (assoc entity (cdr (assoc category
+ org-export-odt-default-org-styles-alist))))
+ (error "Cannot determine style name for entity %s of type %s"
+ entity category))))
+
+(defcustom org-export-odt-preferred-output-format nil
+ "Automatically post-process to this format after exporting to \"odt\".
+Interactive commands `org-export-as-odt' and
+`org-export-as-odt-and-open' export first to \"odt\" format and
+then use `org-export-odt-convert-process' to convert the
+resulting document to this format. During customization of this
+variable, the list of valid values are populated based on
+`org-export-odt-convert-capabilities'."
+ :group 'org-export-odt
+ :type '(choice :convert-widget
+ (lambda (w)
+ (apply 'widget-convert (widget-type w)
+ (eval (car (widget-get w :args)))))
+ `((const :tag "None" nil)
+ ,@(mapcar (lambda (c)
+ `(const :tag ,c ,c))
+ (org-lparse-reachable-formats "odt")))))
+
+;;;###autoload
+(defun org-export-as-odt-and-open (arg)
+ "Export the outline as ODT and immediately open it with a browser.
+If there is an active region, export only the region.
+The prefix ARG specifies how many levels of the outline should become
+headlines. The default is 3. Lower levels will become bulleted lists."
+ (interactive "P")
+ (org-lparse-and-open
+ (or org-export-odt-preferred-output-format "odt") "odt" arg))
+
+;;;###autoload
+(defun org-export-as-odt-batch ()
+ "Call the function `org-lparse-batch'.
+This function can be used in batch processing as:
+emacs --batch
+ --load=$HOME/lib/emacs/org.el
+ --eval \"(setq org-export-headline-levels 2)\"
+ --visit=MyFile --funcall org-export-as-odt-batch"
+ (org-lparse-batch "odt"))
+
+;;;###autoload
+(defun org-export-as-odt-to-buffer (arg)
+ "Call `org-lparse-odt` with output to a temporary buffer.
+No file is created. The prefix ARG is passed through to `org-lparse-to-buffer'."
+ (interactive "P")
+ (org-lparse-to-buffer "odt" arg))
+
+;;;###autoload
+(defun org-replace-region-by-odt (beg end)
+ "Assume the current region has org-mode syntax, and convert it to ODT.
+This can be used in any buffer. For example, you could write an
+itemized list in org-mode syntax in an ODT buffer and then use this
+command to convert it."
+ (interactive "r")
+ (org-replace-region-by "odt" beg end))
+
+;;;###autoload
+(defun org-export-region-as-odt (beg end &optional body-only buffer)
+ "Convert region from BEG to END in org-mode buffer to ODT.
+If prefix arg BODY-ONLY is set, omit file header, footer, and table of
+contents, and only produce the region of converted text, useful for
+cut-and-paste operations.
+If BUFFER is a buffer or a string, use/create that buffer as a target
+of the converted ODT. If BUFFER is the symbol `string', return the
+produced ODT as a string and leave not buffer behind. For example,
+a Lisp program could call this function in the following way:
+
+ (setq odt (org-export-region-as-odt beg end t 'string))
+
+When called interactively, the output buffer is selected, and shown
+in a window. A non-interactive call will only return the buffer."
+ (interactive "r\nP")
+ (org-lparse-region "odt" beg end body-only buffer))
+
+;;; org-export-as-odt
+;;;###autoload
+(defun org-export-as-odt (arg &optional hidden ext-plist
+ to-buffer body-only pub-dir)
+ "Export the outline as a OpenDocumentText file.
+If there is an active region, export only the region. The prefix
+ARG specifies how many levels of the outline should become
+headlines. The default is 3. Lower levels will become bulleted
+lists. HIDDEN is obsolete and does nothing.
+EXT-PLIST is a property list with external parameters overriding
+org-mode's default settings, but still inferior to file-local
+settings. When TO-BUFFER is non-nil, create a buffer with that
+name and export to that buffer. If TO-BUFFER is the symbol
+`string', don't leave any buffer behind but just return the
+resulting XML as a string. When BODY-ONLY is set, don't produce
+the file header and footer, simply return the content of
+<body>...</body>, without even the body tags themselves. When
+PUB-DIR is set, use this as the publishing directory."
+ (interactive "P")
+ (org-lparse (or org-export-odt-preferred-output-format "odt")
+ "odt" arg hidden ext-plist to-buffer body-only pub-dir))
+
+(defvar org-odt-entity-control-callbacks-alist
+ `((EXPORT
+ . (org-odt-begin-export org-odt-end-export))
+ (DOCUMENT-CONTENT
+ . (org-odt-begin-document-content org-odt-end-document-content))
+ (DOCUMENT-BODY
+ . (org-odt-begin-document-body org-odt-end-document-body))
+ (TOC
+ . (org-odt-begin-toc org-odt-end-toc))
+ (ENVIRONMENT
+ . (org-odt-begin-environment org-odt-end-environment))
+ (FOOTNOTE-DEFINITION
+ . (org-odt-begin-footnote-definition org-odt-end-footnote-definition))
+ (TABLE
+ . (org-odt-begin-table org-odt-end-table))
+ (TABLE-ROWGROUP
+ . (org-odt-begin-table-rowgroup org-odt-end-table-rowgroup))
+ (LIST
+ . (org-odt-begin-list org-odt-end-list))
+ (LIST-ITEM
+ . (org-odt-begin-list-item org-odt-end-list-item))
+ (OUTLINE
+ . (org-odt-begin-outline org-odt-end-outline))
+ (OUTLINE-TEXT
+ . (org-odt-begin-outline-text org-odt-end-outline-text))
+ (PARAGRAPH
+ . (org-odt-begin-paragraph org-odt-end-paragraph)))
+ "")
+
+(defvar org-odt-entity-format-callbacks-alist
+ `((EXTRA-TARGETS . org-lparse-format-extra-targets)
+ (ORG-TAGS . org-lparse-format-org-tags)
+ (SECTION-NUMBER . org-lparse-format-section-number)
+ (HEADLINE . org-odt-format-headline)
+ (TOC-ENTRY . org-odt-format-toc-entry)
+ (TOC-ITEM . org-odt-format-toc-item)
+ (TAGS . org-odt-format-tags)
+ (SPACES . org-odt-format-spaces)
+ (TABS . org-odt-format-tabs)
+ (LINE-BREAK . org-odt-format-line-break)
+ (FONTIFY . org-odt-format-fontify)
+ (TODO . org-lparse-format-todo)
+ (LINK . org-odt-format-link)
+ (INLINE-IMAGE . org-odt-format-inline-image)
+ (ORG-LINK . org-odt-format-org-link)
+ (HEADING . org-odt-format-heading)
+ (ANCHOR . org-odt-format-anchor)
+ (TABLE . org-lparse-format-table)
+ (TABLE-ROW . org-odt-format-table-row)
+ (TABLE-CELL . org-odt-format-table-cell)
+ (FOOTNOTES-SECTION . ignore)
+ (FOOTNOTE-REFERENCE . org-odt-format-footnote-reference)
+ (HORIZONTAL-LINE . org-odt-format-horizontal-line)
+ (COMMENT . org-odt-format-comment)
+ (LINE . org-odt-format-line)
+ (ORG-ENTITY . org-odt-format-org-entity))
+ "")
+
+;;;_. callbacks
+;;;_. control callbacks
+;;;_ , document body
+(defun org-odt-begin-office-body ()
+ ;; automatic styles
+ (insert-file-contents
+ (or org-export-odt-content-template-file
+ (expand-file-name "OrgOdtContentTemplate.xml"
+ org-odt-styles-dir)))
+ (goto-char (point-min))
+ (re-search-forward "</office:text>" nil nil)
+ (delete-region (match-beginning 0) (point-max)))
+
+;; Following variable is let bound when `org-do-lparse' is in
+;; progress. See org-html.el.
+(defvar org-lparse-toc)
+(defun org-odt-format-toc ()
+ (if (not org-lparse-toc) "" (concat "\n" org-lparse-toc "\n")))
+
+(defun org-odt-format-preamble (opt-plist)
+ (let* ((title (plist-get opt-plist :title))
+ (author (plist-get opt-plist :author))
+ (date (plist-get opt-plist :date))
+ (iso-date (org-odt-format-date date))
+ (date (org-odt-format-date date "%d %b %Y"))
+ (email (plist-get opt-plist :email))
+ ;; switch on or off above vars based on user settings
+ (author (and (plist-get opt-plist :author-info) (or author email)))
+ (email (and (plist-get opt-plist :email-info) email))
+ (date (and (plist-get opt-plist :time-stamp-file) date)))
+ (concat
+ ;; title
+ (when title
+ (concat
+ (org-odt-format-stylized-paragraph
+ 'title (org-odt-format-tags
+ '("<text:title>" . "</text:title>") title))
+ ;; separator
+ "<text:p text:style-name=\"OrgTitle\"/>"))
+ (cond
+ ((and author (not email))
+ ;; author only
+ (concat
+ (org-odt-format-stylized-paragraph
+ 'subtitle
+ (org-odt-format-tags
+ '("<text:initial-creator>" . "</text:initial-creator>")
+ author))
+ ;; separator
+ "<text:p text:style-name=\"OrgSubtitle\"/>"))
+ ((and author email)
+ ;; author and email
+ (concat
+ (org-odt-format-stylized-paragraph
+ 'subtitle
+ (org-odt-format-link
+ (org-odt-format-tags
+ '("<text:initial-creator>" . "</text:initial-creator>")
+ author) (concat "mailto:" email)))
+ ;; separator
+ "<text:p text:style-name=\"OrgSubtitle\"/>")))
+ ;; date
+ (when date
+ (concat
+ (org-odt-format-stylized-paragraph
+ 'subtitle
+ (org-odt-format-tags
+ '("<text:date style:data-style-name=\"%s\" text:date-value=\"%s\">"
+ . "</text:date>") date "N75" iso-date))
+ ;; separator
+ "<text:p text:style-name=\"OrgSubtitle\"/>"))
+ ;; toc
+ (org-odt-format-toc))))
+
+(defun org-odt-begin-document-body (opt-plist)
+ (org-odt-begin-office-body)
+ (insert (org-odt-format-preamble opt-plist)))
+
+(defvar org-lparse-body-only) ; let bound during org-do-lparse
+(defvar org-lparse-to-buffer) ; let bound during org-do-lparse
+(defun org-odt-end-document-body (opt-plist)
+ (unless org-lparse-body-only
+ (org-lparse-insert-tag "</office:text>")
+ (org-lparse-insert-tag "</office:body>")))
+
+(defun org-odt-begin-document-content (opt-plist)
+ (ignore))
+
+(defun org-odt-end-document-content ()
+ (org-lparse-insert-tag "</office:document-content>"))
+
+(defun org-odt-begin-outline (level1 snumber title tags
+ target extra-targets class)
+ (org-lparse-insert
+ 'HEADING (org-lparse-format
+ 'HEADLINE title extra-targets tags snumber level1)
+ level1 target))
+
+(defun org-odt-end-outline ()
+ (ignore))
+
+(defun org-odt-begin-outline-text (level1 snumber class)
+ (ignore))
+
+(defun org-odt-end-outline-text ()
+ (ignore))
+
+(defun org-odt-begin-paragraph (&optional style)
+ (org-lparse-insert-tag
+ "<text:p%s>" (org-odt-get-extra-attrs-for-paragraph-style style)))
+
+(defun org-odt-end-paragraph ()
+ (org-lparse-insert-tag "</text:p>"))
+
+(defun org-odt-get-extra-attrs-for-paragraph-style (style)
+ (let (style-name)
+ (setq style-name
+ (cond
+ ((stringp style) style)
+ ((symbolp style) (org-odt-get-style-name-for-entity
+ 'paragraph style))))
+ (unless style-name
+ (error "Don't know how to handle paragraph style %s" style))
+ (format " text:style-name=\"%s\"" style-name)))
+
+(defun org-odt-format-stylized-paragraph (style text)
+ (org-odt-format-tags
+ '("<text:p%s>" . "</text:p>") text
+ (org-odt-get-extra-attrs-for-paragraph-style style)))
+
+(defvar org-lparse-opt-plist) ; bound during org-do-lparse
+(defun org-odt-format-author (&optional author)
+ (when (setq author (or author (plist-get org-lparse-opt-plist :author)))
+ (org-odt-format-tags '("<dc:creator>" . "</dc:creator>") author)))
+
+(defun org-odt-format-date (&optional org-ts fmt)
+ (save-match-data
+ (let* ((time
+ (and (stringp org-ts)
+ (string-match org-ts-regexp0 org-ts)
+ (apply 'encode-time
+ (org-fix-decoded-time
+ (org-parse-time-string (match-string 0 org-ts) t)))))
+ date)
+ (cond
+ (fmt (format-time-string fmt time))
+ (t (setq date (format-time-string "%Y-%m-%dT%H:%M:%S%z" time))
+ (format "%s:%s" (substring date 0 -2) (substring date -2)))))))
+
+(defun org-odt-begin-annotation (&optional author date)
+ (org-lparse-insert-tag "<office:annotation>")
+ (when (setq author (org-odt-format-author author))
+ (insert author))
+ (insert (org-odt-format-tags
+ '("<dc:date>" . "</dc:date>")
+ (org-odt-format-date
+ (or date (plist-get org-lparse-opt-plist :date)))))
+ (org-lparse-begin-paragraph))
+
+(defun org-odt-end-annotation ()
+ (org-lparse-insert-tag "</office:annotation>"))
+
+(defun org-odt-begin-environment (style env-options-plist)
+ (case style
+ (annotation
+ (org-lparse-stash-save-paragraph-state)
+ (org-odt-begin-annotation (plist-get env-options-plist 'author)
+ (plist-get env-options-plist 'date)))
+ ((blockquote verse center quote)
+ (org-lparse-begin-paragraph style)
+ (list))
+ ((fixedwidth native)
+ (org-lparse-end-paragraph)
+ (list))
+ (t (error "Unknown environment %s" style))))
+
+(defun org-odt-end-environment (style env-options-plist)
+ (case style
+ (annotation
+ (org-lparse-end-paragraph)
+ (org-odt-end-annotation)
+ (org-lparse-stash-pop-paragraph-state))
+ ((blockquote verse center quote)
+ (org-lparse-end-paragraph)
+ (list))
+ ((fixedwidth native)
+ (org-lparse-begin-paragraph)
+ (list))
+ (t (error "Unknown environment %s" style))))
+
+(defvar org-lparse-list-level) ; dynamically bound in org-do-lparse
+(defun org-odt-begin-list (ltype)
+ (setq ltype (or (org-lparse-html-list-type-to-canonical-list-type ltype)
+ ltype))
+ (let* ((style-name (org-odt-get-style-name-for-entity 'list ltype))
+ (extra (concat (when (= org-lparse-list-level 1)
+ " text:continue-numbering=\"false\"")
+ (when style-name
+ (format " text:style-name=\"%s\"" style-name)))))
+ (case ltype
+ ((ordered unordered description)
+ (org-lparse-end-paragraph)
+ (org-lparse-insert-tag "<text:list%s>" extra))
+ (t (error "Unknown list type: %s" ltype)))))
+
+(defun org-odt-end-list (ltype)
+ (setq ltype (or (org-lparse-html-list-type-to-canonical-list-type ltype)
+ ltype))
+ (if ltype
+ (org-lparse-insert-tag "</text:list>")
+ (error "Unknown list type: %s" ltype)))
+
+(defun org-odt-begin-list-item (ltype &optional arg headline)
+ (setq ltype (or (org-lparse-html-list-type-to-canonical-list-type ltype)
+ ltype))
+ (case ltype
+ (ordered
+ (assert (not headline) t)
+ (let* ((counter arg) (extra ""))
+ (org-lparse-insert-tag "<text:list-item>")
+ (org-lparse-begin-paragraph)))
+ (unordered
+ (let* ((id arg) (extra ""))
+ (org-lparse-insert-tag "<text:list-item>")
+ (org-lparse-begin-paragraph)
+ (insert (if headline (org-odt-format-target headline id)
+ (org-odt-format-bookmark "" id)))))
+ (description
+ (assert (not headline) t)
+ (let ((term (or arg "(no term)")))
+ (insert
+ (org-odt-format-tags
+ '("<text:list-item>" . "</text:list-item>")
+ (org-odt-format-stylized-paragraph 'definition-term term)))
+ (org-lparse-begin-list-item 'unordered)
+ (org-lparse-begin-list 'description)
+ (org-lparse-begin-list-item 'unordered)))
+ (t (error "Unknown list type"))))
+
+(defun org-odt-end-list-item (ltype)
+ (setq ltype (or (org-lparse-html-list-type-to-canonical-list-type ltype)
+ ltype))
+ (case ltype
+ ((ordered unordered)
+ (org-lparse-insert-tag "</text:list-item>"))
+ (description
+ (org-lparse-end-list-item-1)
+ (org-lparse-end-list 'description)
+ (org-lparse-end-list-item-1))
+ (t (error "Unknown list type"))))
+
+;; Following variables are let bound when table emission is in
+;; progress. See org-lparse.el.
+(defvar org-lparse-table-begin-marker)
+(defvar org-lparse-table-ncols)
+(defvar org-lparse-table-rowgrp-open)
+(defvar org-lparse-table-rownum)
+(defvar org-lparse-table-cur-rowgrp-is-hdr)
+(defvar org-lparse-table-is-styled)
+(defvar org-lparse-table-rowgrp-info)
+(defvar org-lparse-table-colalign-vector)
+
+(defvar org-odt-table-style nil
+ "Table style specified by \"#+ATTR_ODT: <style-name>\" line.
+This is set during `org-odt-begin-table'.")
+
+(defvar org-odt-table-style-spec nil
+ "Entry for `org-odt-table-style' in `org-export-odt-table-styles'.")
+
+(defcustom org-export-odt-table-styles
+ '(("OrgEquation" "OrgEquation"
+ ((use-first-column-styles . t)
+ (use-last-column-styles . t))))
+ "Specify how Table Styles should be derived from a Table Template.
+This is a list where each element is of the
+form (TABLE-STYLE-NAME TABLE-TEMPLATE-NAME TABLE-CELL-OPTIONS).
+
+TABLE-STYLE-NAME is the style associated with the table through
+`org-odt-table-style'.
+
+TABLE-TEMPLATE-NAME is a set of - upto 9 - automatic
+TABLE-CELL-STYLE-NAMEs and PARAGRAPH-STYLE-NAMEs (as defined
+below) that is included in
+`org-export-odt-content-template-file'.
+
+TABLE-CELL-STYLE-NAME := TABLE-TEMPLATE-NAME + TABLE-CELL-TYPE +
+ \"TableCell\"
+PARAGRAPH-STYLE-NAME := TABLE-TEMPLATE-NAME + TABLE-CELL-TYPE +
+ \"TableParagraph\"
+TABLE-CELL-TYPE := \"FirstRow\" | \"LastColumn\" |
+ \"FirstRow\" | \"LastRow\" |
+ \"EvenRow\" | \"OddRow\" |
+ \"EvenColumn\" | \"OddColumn\" | \"\"
+where \"+\" above denotes string concatenation.
+
+TABLE-CELL-OPTIONS is an alist where each element is of the
+form (TABLE-CELL-STYLE-SELECTOR . ON-OR-OFF).
+TABLE-CELL-STYLE-SELECTOR := `use-first-row-styles' |
+ `use-last-row-styles' |
+ `use-first-column-styles' |
+ `use-last-column-styles' |
+ `use-banding-rows-styles' |
+ `use-banding-columns-styles' |
+ `use-first-row-styles'
+ON-OR-OFF := `t' | `nil'
+
+For example, with the following configuration
+
+\(setq org-export-odt-table-styles
+ '\(\(\"TableWithHeaderRowsAndColumns\" \"Custom\"
+ \(\(use-first-row-styles . t\)
+ \(use-first-column-styles . t\)\)\)
+ \(\"TableWithHeaderColumns\" \"Custom\"
+ \(\(use-first-column-styles . t\)\)\)\)\)
+
+1. A table associated with \"TableWithHeaderRowsAndColumns\"
+ style will use the following table-cell styles -
+ \"CustomFirstRowTableCell\", \"CustomFirstColumnTableCell\",
+ \"CustomTableCell\" and the following paragraph styles
+ \"CustomFirstRowTableParagraph\",
+ \"CustomFirstColumnTableParagraph\", \"CustomTableParagraph\"
+ as appropriate.
+
+2. A table associated with \"TableWithHeaderColumns\" style will
+ use the following table-cell styles -
+ \"CustomFirstColumnTableCell\", \"CustomTableCell\" and the
+ following paragraph styles
+ \"CustomFirstColumnTableParagraph\", \"CustomTableParagraph\"
+ as appropriate..
+
+Note that TABLE-TEMPLATE-NAME corresponds to the
+\"<table:table-template>\" elements contained within
+\"<office:styles>\". The entries (TABLE-STYLE-NAME
+TABLE-TEMPLATE-NAME TABLE-CELL-OPTIONS) correspond to
+\"table:template-name\" and \"table:use-first-row-styles\" etc
+attributes of \"<table:table>\" element. Refer ODF-1.2
+specification for more information. Also consult the
+implementation filed under `org-odt-get-table-cell-styles'.
+
+The TABLE-STYLE-NAME \"OrgEquation\" is used internally for
+formatting of numbered display equations. Do not delete this
+style from the list."
+ :group 'org-export-odt
+ :type '(choice
+ (const :tag "None" nil)
+ (repeat :tag "Table Styles"
+ (list :tag "Table Style Specification"
+ (string :tag "Table Style Name")
+ (string :tag "Table Template Name")
+ (alist :options (use-first-row-styles
+ use-last-row-styles
+ use-first-column-styles
+ use-last-column-styles
+ use-banding-rows-styles
+ use-banding-columns-styles)
+ :key-type symbol
+ :value-type (const :tag "True" t))))))
+
+(defun org-odt-begin-table (caption label attributes)
+ (setq org-odt-table-style attributes)
+ (setq org-odt-table-style-spec
+ (assoc org-odt-table-style org-export-odt-table-styles))
+ (when label
+ (insert
+ (org-odt-format-stylized-paragraph
+ 'table (org-odt-format-entity-caption label caption "__Table__"))))
+ (org-lparse-insert-tag
+ "<table:table table:name=\"%s\" table:style-name=\"%s\">"
+ (or label "") (or (nth 1 org-odt-table-style-spec) "OrgTable"))
+ (setq org-lparse-table-begin-marker (point)))
+
+(defvar org-lparse-table-colalign-info)
+(defun org-odt-end-table ()
+ (goto-char org-lparse-table-begin-marker)
+ (loop for level from 0 below org-lparse-table-ncols
+ do (let* ((col-cookie (and org-lparse-table-is-styled
+ (cdr (assoc (1+ level)
+ org-lparse-table-colalign-info))))
+ (extra-columns (or (nth 1 col-cookie) 0)))
+ (dotimes (i (1+ extra-columns))
+ (insert
+ (org-odt-format-tags
+ "<table:table-column table:style-name=\"%sColumn\"/>"
+ "" (or (nth 1 org-odt-table-style-spec) "OrgTable"))))
+ (insert "\n")))
+ ;; fill style attributes for table cells
+ (when org-lparse-table-is-styled
+ (while (re-search-forward "@@\\(table-cell:p\\|table-cell:style-name\\)@@\\([0-9]+\\)@@\\([0-9]+\\)@@" nil t)
+ (let* ((spec (match-string 1))
+ (r (string-to-number (match-string 2)))
+ (c (string-to-number (match-string 3)))
+ (cell-styles (org-odt-get-table-cell-styles
+ r c org-odt-table-style-spec))
+ (table-cell-style (car cell-styles))
+ (table-cell-paragraph-style (cdr cell-styles)))
+ (cond
+ ((equal spec "table-cell:p")
+ (replace-match table-cell-paragraph-style t t))
+ ((equal spec "table-cell:style-name")
+ (replace-match table-cell-style t t))))))
+ (goto-char (point-max))
+ (org-lparse-insert-tag "</table:table>"))
+
+(defun org-odt-begin-table-rowgroup (&optional is-header-row)
+ (when org-lparse-table-rowgrp-open
+ (org-lparse-end 'TABLE-ROWGROUP))
+ (org-lparse-insert-tag (if is-header-row
+ "<table:table-header-rows>"
+ "<table:table-rows>"))
+ (setq org-lparse-table-rowgrp-open t)
+ (setq org-lparse-table-cur-rowgrp-is-hdr is-header-row))
+
+(defun org-odt-end-table-rowgroup ()
+ (when org-lparse-table-rowgrp-open
+ (setq org-lparse-table-rowgrp-open nil)
+ (org-lparse-insert-tag
+ (if org-lparse-table-cur-rowgrp-is-hdr
+ "</table:table-header-rows>" "</table:table-rows>"))))
+
+(defun org-odt-format-table-row (row)
+ (org-odt-format-tags
+ '("<table:table-row>" . "</table:table-row>") row))
+
+(defun org-odt-get-table-cell-styles (r c &optional style-spec)
+ "Retrieve styles applicable to a table cell.
+R and C are (zero-based) row and column numbers of the table
+cell. STYLE-SPEC is an entry in `org-export-odt-table-styles'
+applicable to the current table. It is `nil' if the table is not
+associated with any style attributes.
+
+Return a cons of (TABLE-CELL-STYLE-NAME . PARAGRAPH-STYLE-NAME).
+
+When STYLE-SPEC is nil, style the table cell the conventional way
+- choose cell borders based on row and column groupings and
+choose paragraph alignment based on `org-col-cookies' text
+property. See also
+`org-odt-get-paragraph-style-cookie-for-table-cell'.
+
+When STYLE-SPEC is non-nil, ignore the above cookie and return
+styles congruent with the ODF-1.2 specification."
+ (cond
+ (style-spec
+
+ ;; LibreOffice - particularly the Writer - honors neither table
+ ;; templates nor custom table-cell styles. Inorder to retain
+ ;; inter-operability with LibreOffice, only automatic styles are
+ ;; used for styling of table-cells. The current implementation is
+ ;; congruent with ODF-1.2 specification and hence is
+ ;; future-compatible.
+
+ ;; Additional Note: LibreOffice's AutoFormat facility for tables -
+ ;; which recognizes as many as 16 different cell types - is much
+ ;; richer. Unfortunately it is NOT amenable to easy configuration
+ ;; by hand.
+
+ (let* ((template-name (nth 1 style-spec))
+ (cell-style-selectors (nth 2 style-spec))
+ (cell-type
+ (cond
+ ((and (cdr (assoc 'use-first-column-styles cell-style-selectors))
+ (= c 0)) "FirstColumn")
+ ((and (cdr (assoc 'use-last-column-styles cell-style-selectors))
+ (= c (1- org-lparse-table-ncols))) "LastColumn")
+ ((and (cdr (assoc 'use-first-row-styles cell-style-selectors))
+ (= r 0)) "FirstRow")
+ ((and (cdr (assoc 'use-last-row-styles cell-style-selectors))
+ (= r org-lparse-table-rownum))
+ "LastRow")
+ ((and (cdr (assoc 'use-banding-rows-styles cell-style-selectors))
+ (= (% r 2) 1)) "EvenRow")
+ ((and (cdr (assoc 'use-banding-rows-styles cell-style-selectors))
+ (= (% r 2) 0)) "OddRow")
+ ((and (cdr (assoc 'use-banding-columns-styles cell-style-selectors))
+ (= (% c 2) 1)) "EvenColumn")
+ ((and (cdr (assoc 'use-banding-columns-styles cell-style-selectors))
+ (= (% c 2) 0)) "OddColumn")
+ (t ""))))
+ (cons
+ (concat template-name cell-type "TableCell")
+ (concat template-name cell-type "TableParagraph"))))
+ (t
+ (cons
+ (concat
+ "OrgTblCell"
+ (cond
+ ((= r 0) "T")
+ ((eq (cdr (assoc r org-lparse-table-rowgrp-info)) :start) "T")
+ (t ""))
+ (when (= r org-lparse-table-rownum) "B")
+ (cond
+ ((= c 0) "")
+ ((or (memq (nth c org-table-colgroup-info) '(:start :startend))
+ (memq (nth (1- c) org-table-colgroup-info) '(:end :startend))) "L")
+ (t "")))
+ (capitalize (aref org-lparse-table-colalign-vector c))))))
+
+(defun org-odt-get-paragraph-style-cookie-for-table-cell (r c)
+ (concat
+ (and (not org-odt-table-style-spec)
+ (cond
+ (org-lparse-table-cur-rowgrp-is-hdr "OrgTableHeading")
+ ((and (= c 0) (org-lparse-get 'TABLE-FIRST-COLUMN-AS-LABELS))
+ "OrgTableHeading")
+ (t "OrgTableContents")))
+ (and org-lparse-table-is-styled
+ (format "@@table-cell:p@@%03d@@%03d@@" r c))))
+
+(defun org-odt-get-style-name-cookie-for-table-cell (r c)
+ (when org-lparse-table-is-styled
+ (format "@@table-cell:style-name@@%03d@@%03d@@" r c)))
+
+(defun org-odt-format-table-cell (data r c horiz-span)
+ (concat
+ (let* ((paragraph-style-cookie
+ (org-odt-get-paragraph-style-cookie-for-table-cell r c))
+ (style-name-cookie
+ (org-odt-get-style-name-cookie-for-table-cell r c))
+ (extra (and style-name-cookie
+ (format " table:style-name=\"%s\"" style-name-cookie)))
+ (extra (concat extra
+ (and (> horiz-span 0)
+ (format " table:number-columns-spanned=\"%d\""
+ (1+ horiz-span))))))
+ (org-odt-format-tags
+ '("<table:table-cell%s>" . "</table:table-cell>")
+ (if org-lparse-list-table-p data
+ (org-odt-format-stylized-paragraph paragraph-style-cookie data)) extra))
+ (let (s)
+ (dotimes (i horiz-span)
+ (setq s (concat s "\n<table:covered-table-cell/>"))) s)
+ "\n"))
+
+(defun org-odt-begin-footnote-definition (n)
+ (org-lparse-begin-paragraph 'footnote))
+
+(defun org-odt-end-footnote-definition (n)
+ (org-lparse-end-paragraph))
+
+(defun org-odt-begin-toc (lang-specific-heading max-level)
+ (insert
+ (format "
+ <text:table-of-content text:style-name=\"Sect2\" text:protected=\"true\" text:name=\"Table of Contents1\">
+ <text:table-of-content-source text:outline-level=\"%d\">
+ <text:index-title-template text:style-name=\"Contents_20_Heading\">%s</text:index-title-template>
+" max-level lang-specific-heading))
+ (loop for level from 1 upto 10
+ do (insert (format
+ "
+ <text:table-of-content-entry-template text:outline-level=\"%d\" text:style-name=\"Contents_20_%d\">
+ <text:index-entry-link-start text:style-name=\"Internet_20_link\"/>
+ <text:index-entry-chapter/>
+ <text:index-entry-text/>
+ <text:index-entry-link-end/>
+ </text:table-of-content-entry-template>
+" level level)))
+
+ (insert
+ (format "
+ </text:table-of-content-source>
+
+ <text:index-body>
+ <text:index-title text:style-name=\"Sect1\" text:name=\"Table of Contents1_Head\">
+ <text:p text:style-name=\"Contents_20_Heading\">%s</text:p>
+ </text:index-title>
+" lang-specific-heading)))
+
+(defun org-odt-end-toc ()
+ (insert "
+ </text:index-body>
+ </text:table-of-content>
+"))
+
+(defun org-odt-format-toc-entry (snumber todo headline tags href)
+ (setq headline (concat
+ (and org-export-with-section-numbers
+ (concat snumber ". "))
+ headline
+ (and tags
+ (concat
+ (org-lparse-format 'SPACES 3)
+ (org-lparse-format 'FONTIFY tags "tag")))))
+ (when todo
+ (setq headline (org-lparse-format 'FONTIFY headline "todo")))
+
+ (let ((org-odt-suppress-xref t))
+ (org-odt-format-link headline (concat "#" href))))
+
+(defun org-odt-format-toc-item (toc-entry level org-last-level)
+ (let ((style (format "Contents_20_%d"
+ (+ level (or (org-lparse-get 'TOPLEVEL-HLEVEL) 1) -1))))
+ (insert "\n" (org-odt-format-stylized-paragraph style toc-entry) "\n")))
+
+;; Following variable is let bound during 'ORG-LINK callback. See
+;; org-html.el
+(defvar org-lparse-link-description-is-image nil)
+(defun org-odt-format-link (desc href &optional attr)
+ (cond
+ ((and (= (string-to-char href) ?#) (not org-odt-suppress-xref))
+ (setq href (concat org-export-odt-bookmark-prefix (substring href 1)))
+ (let ((xref-format "text"))
+ (when (numberp desc)
+ (setq desc (format "%d" desc) xref-format "number"))
+ (org-odt-format-tags
+ '("<text:bookmark-ref text:reference-format=\"%s\" text:ref-name=\"%s\">" .
+ "</text:bookmark-ref>")
+ desc xref-format href)))
+ (org-lparse-link-description-is-image
+ (org-odt-format-tags
+ '("<draw:a xlink:type=\"simple\" xlink:href=\"%s\" %s>" . "</draw:a>")
+ desc href (or attr "")))
+ (t
+ (org-odt-format-tags
+ '("<text:a xlink:type=\"simple\" xlink:href=\"%s\" %s>" . "</text:a>")
+ desc href (or attr "")))))
+
+(defun org-odt-format-spaces (n)
+ (cond
+ ((= n 1) " ")
+ ((> n 1) (concat
+ " " (org-odt-format-tags "<text:s text:c=\"%d\"/>" "" (1- n))))
+ (t "")))
+
+(defun org-odt-format-tabs (&optional n)
+ (let ((tab "<text:tab/>")
+ (n (or n 1)))
+ (insert tab)))
+
+(defun org-odt-format-line-break ()
+ (org-odt-format-tags "<text:line-break/>" ""))
+
+(defun org-odt-format-horizontal-line ()
+ (org-odt-format-stylized-paragraph 'horizontal-line ""))
+
+(defun org-odt-encode-plain-text (line &optional no-whitespace-filling)
+ (setq line (org-xml-encode-plain-text line))
+ (if no-whitespace-filling line
+ (org-odt-fill-tabs-and-spaces line)))
+
+(defun org-odt-format-line (line)
+ (case org-lparse-dyn-current-environment
+ (fixedwidth (concat
+ (org-odt-format-stylized-paragraph
+ 'fixedwidth (org-odt-encode-plain-text line)) "\n"))
+ (t (concat line "\n"))))
+
+(defun org-odt-format-comment (fmt &rest args)
+ (let ((comment (apply 'format fmt args)))
+ (format "\n<!-- %s -->\n" comment)))
+
+(defun org-odt-format-org-entity (wd)
+ (org-entity-get-representation wd 'utf8))
+
+(defun org-odt-fill-tabs-and-spaces (line)
+ (replace-regexp-in-string
+ "\\([\t]\\|\\([ ]+\\)\\)" (lambda (s)
+ (cond
+ ((string= s "\t") (org-odt-format-tabs))
+ (t (org-odt-format-spaces (length s))))) line))
+
+(defcustom org-export-odt-fontify-srcblocks t
+ "Specify whether or not source blocks need to be fontified.
+Turn this option on if you want to colorize the source code
+blocks in the exported file. For colorization to work, you need
+to make available an enhanced version of `htmlfontify' library."
+ :type 'boolean
+ :group 'org-export-odt)
+
+(defun org-odt-format-source-line-with-line-number-and-label
+ (line rpllbl num fontifier par-style)
+
+ (let ((keep-label (not (numberp rpllbl)))
+ (ref (org-find-text-property-in-string 'org-coderef line)))
+ (setq line (concat line (and keep-label ref (format "(%s)" ref))))
+ (setq line (funcall fontifier line))
+ (when ref
+ (setq line (org-odt-format-target line (concat "coderef-" ref))))
+ (setq line (org-odt-format-stylized-paragraph par-style line))
+ (if (not num) line
+ (org-odt-format-tags '("<text:list-item>" . "</text:list-item>") line))))
+
+(defun org-odt-format-source-code-or-example-plain
+ (lines lang caption textareap cols rows num cont rpllbl fmt)
+ "Format source or example blocks much like fixedwidth blocks.
+Use this when `org-export-odt-fontify-srcblocks' option is turned
+off."
+ (let* ((lines (org-split-string lines "[\r\n]"))
+ (line-count (length lines))
+ (i 0))
+ (mapconcat
+ (lambda (line)
+ (incf i)
+ (org-odt-format-source-line-with-line-number-and-label
+ line rpllbl num 'org-odt-encode-plain-text
+ (if (= i line-count) "OrgFixedWidthBlockLastLine"
+ "OrgFixedWidthBlock")))
+ lines "\n")))
+
+(defvar org-src-block-paragraph-format
+ "<style:style style:name=\"OrgSrcBlock\" style:family=\"paragraph\" style:parent-style-name=\"Preformatted_20_Text\">
+ <style:paragraph-properties fo:background-color=\"%s\" fo:padding=\"0.049cm\" fo:border=\"0.51pt solid #000000\" style:shadow=\"none\">
+ <style:background-image/>
+ </style:paragraph-properties>
+ <style:text-properties fo:color=\"%s\"/>
+ </style:style>"
+ "Custom paragraph style for colorized source and example blocks.
+This style is much the same as that of \"OrgFixedWidthBlock\"
+except that the foreground and background colors are set
+according to the default face identified by the `htmlfontify'.")
+
+(defvar hfy-optimisations)
+(declare-function hfy-face-to-style "htmlfontify" (fn))
+(declare-function hfy-face-or-def-to-name "htmlfontify" (fn))
+
+(defun org-odt-hfy-face-to-css (fn)
+ "Create custom style for face FN.
+When FN is the default face, use it's foreground and background
+properties to create \"OrgSrcBlock\" paragraph style. Otherwise
+use it's color attribute to create a character style whose name
+is obtained from FN. Currently all attributes of FN other than
+color are ignored.
+
+The style name for a face FN is derived using the following
+operations on the face name in that order - de-dash, CamelCase
+and prefix with \"OrgSrc\". For example,
+`font-lock-function-name-face' is associated with
+\"OrgSrcFontLockFunctionNameFace\"."
+ (let* ((css-list (hfy-face-to-style fn))
+ (style-name ((lambda (fn)
+ (concat "OrgSrc"
+ (mapconcat
+ 'capitalize (split-string
+ (hfy-face-or-def-to-name fn) "-")
+ ""))) fn))
+ (color-val (cdr (assoc "color" css-list)))
+ (background-color-val (cdr (assoc "background" css-list)))
+ (style (and org-export-odt-create-custom-styles-for-srcblocks
+ (cond
+ ((eq fn 'default)
+ (format org-src-block-paragraph-format
+ background-color-val color-val))
+ (t
+ (format
+ "
+<style:style style:name=\"%s\" style:family=\"text\">
+ <style:text-properties fo:color=\"%s\"/>
+ </style:style>" style-name color-val))))))
+ (cons style-name style)))
+
+(defun org-odt-insert-custom-styles-for-srcblocks (styles)
+ "Save STYLES used for colorizing of source blocks.
+Update styles.xml with styles that were collected as part of
+`org-odt-hfy-face-to-css' callbacks."
+ (when styles
+ (with-current-buffer
+ (find-file-noselect (expand-file-name "styles.xml") t)
+ (goto-char (point-min))
+ (when (re-search-forward "</office:styles>" nil t)
+ (goto-char (match-beginning 0))
+ (insert "\n<!-- Org Htmlfontify Styles -->\n" styles "\n")))))
+
+(defun org-odt-format-source-code-or-example-colored
+ (lines lang caption textareap cols rows num cont rpllbl fmt)
+ "Format source or example blocks using `htmlfontify-string'.
+Use this routine when `org-export-odt-fontify-srcblocks' option
+is turned on."
+ (let* ((lang-m (and lang (or (cdr (assoc lang org-src-lang-modes)) lang)))
+ (mode (and lang-m (intern (concat (if (symbolp lang-m)
+ (symbol-name lang-m)
+ lang-m) "-mode"))))
+ (org-inhibit-startup t)
+ (org-startup-folded nil)
+ (lines (with-temp-buffer
+ (insert lines)
+ (if (functionp mode) (funcall mode) (fundamental-mode))
+ (font-lock-fontify-buffer)
+ (buffer-string)))
+ (hfy-html-quote-regex "\\([<\"&> ]\\)")
+ (hfy-html-quote-map '(("\"" "&quot;")
+ ("<" "&lt;")
+ ("&" "&amp;")
+ (">" "&gt;")
+ (" " "<text:s/>")
+ (" " "<text:tab/>")))
+ (hfy-face-to-css 'org-odt-hfy-face-to-css)
+ (hfy-optimisations-1 (copy-seq hfy-optimisations))
+ (hfy-optimisations (add-to-list 'hfy-optimisations-1
+ 'body-text-only))
+ (hfy-begin-span-handler
+ (lambda (style text-block text-id text-begins-block-p)
+ (insert (format "<text:span text:style-name=\"%s\">" style))))
+ (hfy-end-span-handler (lambda nil (insert "</text:span>"))))
+ (when (fboundp 'htmlfontify-string)
+ (let* ((lines (org-split-string lines "[\r\n]"))
+ (line-count (length lines))
+ (i 0))
+ (mapconcat
+ (lambda (line)
+ (incf i)
+ (org-odt-format-source-line-with-line-number-and-label
+ line rpllbl num 'htmlfontify-string
+ (if (= i line-count) "OrgSrcBlockLastLine" "OrgSrcBlock")))
+ lines "\n")))))
+
+(defun org-odt-format-source-code-or-example (lines lang caption textareap
+ cols rows num cont
+ rpllbl fmt)
+ "Format source or example blocks for export.
+Use `org-odt-format-source-code-or-example-plain' or
+`org-odt-format-source-code-or-example-colored' depending on the
+value of `org-export-odt-fontify-srcblocks."
+ (setq lines (org-export-number-lines
+ lines 0 0 num cont rpllbl fmt 'preprocess)
+ lines (funcall
+ (or (and org-export-odt-fontify-srcblocks
+ (or (featurep 'htmlfontify)
+ ;; htmlfontify.el was introduced in Emacs 23.2
+ ;; So load it with some caution
+ (require 'htmlfontify nil t))
+ (fboundp 'htmlfontify-string)
+ 'org-odt-format-source-code-or-example-colored)
+ 'org-odt-format-source-code-or-example-plain)
+ lines lang caption textareap cols rows num cont rpllbl fmt))
+ (if (not num) lines
+ (let ((extra (format " text:continue-numbering=\"%s\""
+ (if cont "true" "false"))))
+ (org-odt-format-tags
+ '("<text:list text:style-name=\"OrgSrcBlockNumberedLine\"%s>"
+ . "</text:list>") lines extra))))
+
+(defun org-odt-remap-stylenames (style-name)
+ (or
+ (cdr (assoc style-name '(("timestamp-wrapper" . "OrgTimestampWrapper")
+ ("timestamp" . "OrgTimestamp")
+ ("timestamp-kwd" . "OrgTimestampKeyword")
+ ("tag" . "OrgTag")
+ ("todo" . "OrgTodo")
+ ("done" . "OrgDone")
+ ("target" . "OrgTarget"))))
+ style-name))
+
+(defun org-odt-format-fontify (text style &optional id)
+ (let* ((style-name
+ (cond
+ ((stringp style)
+ (org-odt-remap-stylenames style))
+ ((symbolp style)
+ (org-odt-get-style-name-for-entity 'character style))
+ ((listp style)
+ (assert (< 1 (length style)))
+ (let ((parent-style (pop style)))
+ (mapconcat (lambda (s)
+ ;; (assert (stringp s) t)
+ (org-odt-remap-stylenames s)) style "")
+ (org-odt-remap-stylenames parent-style)))
+ (t (error "Don't how to handle style %s" style)))))
+ (org-odt-format-tags
+ '("<text:span text:style-name=\"%s\">" . "</text:span>")
+ text style-name)))
+
+(defun org-odt-relocate-relative-path (path dir)
+ (if (file-name-absolute-p path) path
+ (file-relative-name (expand-file-name path dir)
+ (expand-file-name "eyecandy" dir))))
+
+(defun org-odt-format-inline-image (thefile)
+ (let* ((thelink (if (file-name-absolute-p thefile) thefile
+ (org-xml-format-href
+ (org-odt-relocate-relative-path
+ thefile org-current-export-file))))
+ (href
+ (org-odt-format-tags
+ "<draw:image xlink:href=\"%s\" xlink:type=\"simple\" xlink:show=\"embed\" xlink:actuate=\"onLoad\"/>" ""
+ (if org-export-odt-embed-images
+ (org-odt-copy-image-file thefile) thelink))))
+ (org-export-odt-format-image thefile href)))
+
+(defun org-export-odt-format-formula (src href &optional embed-as)
+ "Create image tag with source and attributes."
+ (save-match-data
+ (let* ((caption (org-find-text-property-in-string 'org-caption src))
+ (caption (and caption (org-xml-format-desc caption)))
+ (label (org-find-text-property-in-string 'org-label src))
+ (latex-frag (org-find-text-property-in-string 'org-latex-src src))
+ (embed-as (or embed-as
+ (and latex-frag
+ (org-find-text-property-in-string
+ 'org-latex-src-embed-type src))
+ (if (or caption label) 'paragraph 'character)))
+ width height)
+ (when latex-frag
+ (setq href (org-propertize href :title "LaTeX Fragment"
+ :description latex-frag)))
+ (cond
+ ((eq embed-as 'character)
+ (org-odt-format-entity "InlineFormula" href width height))
+ (t
+ (org-lparse-end-paragraph)
+ (org-lparse-insert-list-table
+ `((,(org-odt-format-entity
+ (if caption "CaptionedDisplayFormula" "DisplayFormula")
+ href width height :caption caption :label nil)
+ ,(if (not label) ""
+ (org-odt-format-entity-caption label nil "__MathFormula__"))))
+ nil nil nil "OrgEquation" nil '((1 "c" 8) (2 "c" 1)))
+ (throw 'nextline nil))))))
+
+(defvar org-odt-embedded-formulas-count 0)
+(defun org-odt-copy-formula-file (path)
+ "Returns the internal name of the file"
+ (let* ((src-file (expand-file-name
+ path (file-name-directory org-current-export-file)))
+ (target-dir (format "Formula-%04d/"
+ (incf org-odt-embedded-formulas-count)))
+ (target-file (concat target-dir "content.xml")))
+ (when (not org-lparse-to-buffer)
+ (message "Embedding %s as %s ..."
+ (substring-no-properties path) target-file)
+
+ (make-directory target-dir)
+ (org-odt-create-manifest-file-entry
+ "application/vnd.oasis.opendocument.formula" target-dir "1.2")
+
+ (case (org-odt-is-formula-link-p src-file)
+ (mathml
+ (copy-file src-file target-file 'overwrite))
+ (odf
+ (org-odt-zip-extract-one src-file "content.xml" target-dir))
+ (t
+ (error "%s is not a formula file" src-file)))
+
+ (org-odt-create-manifest-file-entry "text/xml" target-file))
+ target-file))
+
+(defun org-odt-format-inline-formula (thefile)
+ (let* ((thelink (if (file-name-absolute-p thefile) thefile
+ (org-xml-format-href
+ (org-odt-relocate-relative-path
+ thefile org-current-export-file))))
+ (href
+ (org-odt-format-tags
+ "<draw:object xlink:href=\"%s\" xlink:type=\"simple\" xlink:show=\"embed\" xlink:actuate=\"onLoad\"/>" ""
+ (file-name-directory (org-odt-copy-formula-file thefile)))))
+ (org-export-odt-format-formula thefile href)))
+
+(defun org-odt-is-formula-link-p (file)
+ (let ((case-fold-search nil))
+ (cond
+ ((string-match "\\.\\(mathml\\|mml\\)\\'" file)
+ 'mathml)
+ ((string-match "\\.odf\\'" file)
+ 'odf))))
+
+(defun org-odt-format-org-link (opt-plist type-1 path fragment desc attr
+ descp)
+ "Make a OpenDocument link.
+OPT-PLIST is an options list.
+TYPE-1 is the device-type of the link (THIS://foo.html).
+PATH is the path of the link (http://THIS#location).
+FRAGMENT is the fragment part of the link, if any (foo.html#THIS).
+DESC is the link description, if any.
+ATTR is a string of other attributes of the a element."
+ (declare (special org-lparse-par-open))
+ (save-match-data
+ (let* ((may-inline-p
+ (and (member type-1 '("http" "https" "file"))
+ (org-lparse-should-inline-p path descp)
+ (not fragment)))
+ (type (if (equal type-1 "id") "file" type-1))
+ (filename path)
+ (thefile path))
+ (cond
+ ;; check for inlined images
+ ((and (member type '("file"))
+ (not fragment)
+ (org-file-image-p
+ filename org-export-odt-inline-image-extensions)
+ (or (eq t org-export-odt-inline-images)
+ (and org-export-odt-inline-images (not descp))))
+ (org-odt-format-inline-image thefile))
+ ;; check for embedded formulas
+ ((and (member type '("file"))
+ (not fragment)
+ (org-odt-is-formula-link-p filename)
+ (or (not descp)))
+ (org-odt-format-inline-formula thefile))
+ ((string= type "coderef")
+ (let* ((ref fragment)
+ (lineno-or-ref (cdr (assoc ref org-export-code-refs)))
+ (desc (and descp desc))
+ (org-odt-suppress-xref nil)
+ (href (org-xml-format-href (concat "#coderef-" ref))))
+ (cond
+ ((and (numberp lineno-or-ref) (not desc))
+ (org-odt-format-link lineno-or-ref href))
+ ((and (numberp lineno-or-ref) desc
+ (string-match (regexp-quote (concat "(" ref ")")) desc))
+ (format (replace-match "%s" t t desc)
+ (org-odt-format-link lineno-or-ref href)))
+ (t
+ (setq desc (format
+ (if (and desc (string-match
+ (regexp-quote (concat "(" ref ")"))
+ desc))
+ (replace-match "%s" t t desc)
+ (or desc "%s"))
+ lineno-or-ref))
+ (org-odt-format-link (org-xml-format-desc desc) href)))))
+ (t
+ (when (string= type "file")
+ (setq thefile
+ (cond
+ ((file-name-absolute-p path)
+ (concat "file://" (expand-file-name path)))
+ (t (org-odt-relocate-relative-path
+ thefile org-current-export-file)))))
+
+ (when (and (member type '("" "http" "https" "file")) fragment)
+ (setq thefile (concat thefile "#" fragment)))
+
+ (setq thefile (org-xml-format-href thefile))
+
+ (when (not (member type '("" "file")))
+ (setq thefile (concat type ":" thefile)))
+
+ (let ((org-odt-suppress-xref nil))
+ (org-odt-format-link
+ (org-xml-format-desc desc) thefile attr)))))))
+
+(defun org-odt-format-heading (text level &optional id)
+ (let* ((text (if id (org-odt-format-target text id) text)))
+ (org-odt-format-tags
+ '("<text:h text:style-name=\"Heading_20_%s\" text:outline-level=\"%s\">" .
+ "</text:h>") text level level)))
+
+(defun org-odt-format-headline (title extra-targets tags
+ &optional snumber level)
+ (concat
+ (org-lparse-format 'EXTRA-TARGETS extra-targets)
+
+ ;; No need to generate section numbers. They are auto-generated by
+ ;; the application
+
+ ;; (concat (org-lparse-format 'SECTION-NUMBER snumber level) " ")
+ title
+ (and tags (concat (org-lparse-format 'SPACES 3)
+ (org-lparse-format 'ORG-TAGS tags)))))
+
+(defun org-odt-format-anchor (text name &optional class)
+ (org-odt-format-target text name))
+
+(defun org-odt-format-bookmark (text id)
+ (if id
+ (org-odt-format-tags "<text:bookmark text:name=\"%s\"/>" text id)
+ text))
+
+(defun org-odt-format-target (text id)
+ (let ((name (concat org-export-odt-bookmark-prefix id)))
+ (concat
+ (and id (org-odt-format-tags
+ "<text:bookmark-start text:name=\"%s\"/>" "" name))
+ (org-odt-format-bookmark text id)
+ (and id (org-odt-format-tags
+ "<text:bookmark-end text:name=\"%s\"/>" "" name)))))
+
+(defun org-odt-format-footnote (n def)
+ (let ((id (concat "fn" n))
+ (note-class "footnote")
+ (par-style "Footnote"))
+ (org-odt-format-tags
+ '("<text:note text:id=\"%s\" text:note-class=\"%s\">" .
+ "</text:note>")
+ (concat
+ (org-odt-format-tags
+ '("<text:note-citation>" . "</text:note-citation>")
+ n)
+ (org-odt-format-tags
+ '("<text:note-body>" . "</text:note-body>")
+ def))
+ id note-class)))
+
+(defun org-odt-format-footnote-reference (n def refcnt)
+ (if (= refcnt 1)
+ (org-odt-format-footnote n def)
+ (org-odt-format-footnote-ref n)))
+
+(defun org-odt-format-footnote-ref (n)
+ (let ((note-class "footnote")
+ (ref-format "text")
+ (ref-name (concat "fn" n)))
+ (org-odt-format-tags
+ '("<text:span text:style-name=\"%s\">" . "</text:span>")
+ (org-odt-format-tags
+ '("<text:note-ref text:note-class=\"%s\" text:reference-format=\"%s\" text:ref-name=\"%s\">" . "</text:note-ref>")
+ n note-class ref-format ref-name)
+ "OrgSuperscript")))
+
+(defun org-odt-get-image-name (file-name)
+ (require 'sha1)
+ (file-relative-name
+ (expand-file-name
+ (concat (sha1 file-name) "." (file-name-extension file-name)) "Pictures")))
+
+(defun org-export-odt-format-image (src href)
+ "Create image tag with source and attributes."
+ (save-match-data
+ (let* ((caption (org-find-text-property-in-string 'org-caption src))
+ (caption (and caption (org-xml-format-desc caption)))
+ (attr (org-find-text-property-in-string 'org-attributes src))
+ (label (org-find-text-property-in-string 'org-label src))
+ (latex-frag (org-find-text-property-in-string
+ 'org-latex-src src))
+ (category (and latex-frag "__DvipngImage__"))
+ (attr-plist (org-lparse-get-block-params attr))
+ (user-frame-anchor
+ (car (assoc-string (plist-get attr-plist :anchor)
+ (if (or caption label)
+ '(("paragraph") ("page"))
+ '(("character") ("paragraph") ("page"))) t)))
+ (user-frame-style
+ (and user-frame-anchor (plist-get attr-plist :style)))
+ (user-frame-attrs
+ (and user-frame-anchor (plist-get attr-plist :attributes)))
+ (user-frame-params
+ (list user-frame-style user-frame-attrs user-frame-anchor))
+ (embed-as (cond
+ (latex-frag
+ (symbol-name
+ (or (org-find-text-property-in-string
+ 'org-latex-src-embed-type src) 'character)))
+ (user-frame-anchor)
+ (t "paragraph")))
+ (size (org-odt-image-size-from-file
+ src (plist-get attr-plist :width)
+ (plist-get attr-plist :height)
+ (plist-get attr-plist :scale) nil embed-as))
+ (width (car size)) (height (cdr size)))
+ (when latex-frag
+ (setq href (org-propertize href :title "LaTeX Fragment"
+ :description latex-frag)))
+ (let ((frame-style-handle (concat (and (or caption label) "Captioned")
+ embed-as "Image")))
+ (org-odt-format-entity
+ frame-style-handle href width height
+ :caption caption :label label :category category
+ :user-frame-params user-frame-params)))))
+
+(defun org-odt-format-object-description (title description)
+ (concat (and title (org-odt-format-tags
+ '("<svg:title>" . "</svg:title>")
+ (org-odt-encode-plain-text title t)))
+ (and description (org-odt-format-tags
+ '("<svg:desc>" . "</svg:desc>")
+ (org-odt-encode-plain-text description t)))))
+
+(defun org-odt-format-frame (text width height style &optional
+ extra anchor-type)
+ (let ((frame-attrs
+ (concat
+ (if width (format " svg:width=\"%0.2fcm\"" width) "")
+ (if height (format " svg:height=\"%0.2fcm\"" height) "")
+ extra
+ (format " text:anchor-type=\"%s\"" (or anchor-type "paragraph")))))
+ (org-odt-format-tags
+ '("<draw:frame draw:style-name=\"%s\"%s>" . "</draw:frame>")
+ (concat text (org-odt-format-object-description
+ (get-text-property 0 :title text)
+ (get-text-property 0 :description text)))
+ style frame-attrs)))
+
+(defun org-odt-format-textbox (text width height style &optional
+ extra anchor-type)
+ (org-odt-format-frame
+ (org-odt-format-tags
+ '("<draw:text-box %s>" . "</draw:text-box>")
+ text (concat (format " fo:min-height=\"%0.2fcm\"" (or height .2))
+ (format " fo:min-width=\"%0.2fcm\"" (or width .2))))
+ width nil style extra anchor-type))
+
+(defun org-odt-format-inlinetask (heading content
+ &optional todo priority tags)
+ (org-odt-format-stylized-paragraph
+ nil (org-odt-format-textbox
+ (concat (org-odt-format-stylized-paragraph
+ "OrgInlineTaskHeading"
+ (org-lparse-format
+ 'HEADLINE (concat (org-lparse-format-todo todo) " " heading)
+ nil tags))
+ content) nil nil "OrgInlineTaskFrame" " style:rel-width=\"100%\"")))
+
+(defvar org-odt-entity-frame-styles
+ '(("CharacterImage" "__Figure__" ("OrgInlineImage" nil "as-char"))
+ ("ParagraphImage" "__Figure__" ("OrgDisplayImage" nil "paragraph"))
+ ("PageImage" "__Figure__" ("OrgPageImage" nil "page"))
+ ("CaptionedParagraphImage" "__Figure__"
+ ("OrgCaptionedImage"
+ " style:rel-width=\"100%\" style:rel-height=\"scale\"" "paragraph")
+ ("OrgImageCaptionFrame" nil "paragraph"))
+ ("CaptionedPageImage" "__Figure__"
+ ("OrgCaptionedImage"
+ " style:rel-width=\"100%\" style:rel-height=\"scale\"" "paragraph")
+ ("OrgPageImageCaptionFrame" nil "page"))
+ ("InlineFormula" "__MathFormula__" ("OrgInlineFormula" nil "as-char"))
+ ("DisplayFormula" "__MathFormula__" ("OrgDisplayFormula" nil "as-char"))
+ ("CaptionedDisplayFormula" "__MathFormula__"
+ ("OrgCaptionedFormula" nil "paragraph")
+ ("OrgFormulaCaptionFrame" nil "as-char"))))
+
+(defun org-odt-merge-frame-params(default-frame-params user-frame-params)
+ (if (not user-frame-params) default-frame-params
+ (assert (= (length default-frame-params) 3))
+ (assert (= (length user-frame-params) 3))
+ (loop for user-frame-param in user-frame-params
+ for default-frame-param in default-frame-params
+ collect (or user-frame-param default-frame-param))))
+
+(defun* org-odt-format-entity (entity href width height
+ &key caption label category
+ user-frame-params)
+ (let* ((entity-style (assoc-string entity org-odt-entity-frame-styles t))
+ default-frame-params frame-params)
+ (cond
+ ((not (or caption label))
+ (setq default-frame-params (nth 2 entity-style))
+ (setq frame-params (org-odt-merge-frame-params
+ default-frame-params user-frame-params))
+ (apply 'org-odt-format-frame href width height frame-params))
+ (t
+ (setq default-frame-params (nth 3 entity-style))
+ (setq frame-params (org-odt-merge-frame-params
+ default-frame-params user-frame-params))
+ (apply 'org-odt-format-textbox
+ (org-odt-format-stylized-paragraph
+ 'illustration
+ (concat
+ (apply 'org-odt-format-frame href width height
+ (nth 2 entity-style))
+ (org-odt-format-entity-caption
+ label caption (or category (nth 1 entity-style)))))
+ width height frame-params)))))
+
+(defvar org-odt-embedded-images-count 0)
+(defun org-odt-copy-image-file (path)
+ "Returns the internal name of the file"
+ (let* ((image-type (file-name-extension path))
+ (media-type (format "image/%s" image-type))
+ (src-file (expand-file-name
+ path (file-name-directory org-current-export-file)))
+ (target-dir "Images/")
+ (target-file
+ (format "%s%04d.%s" target-dir
+ (incf org-odt-embedded-images-count) image-type)))
+ (when (not org-lparse-to-buffer)
+ (message "Embedding %s as %s ..."
+ (substring-no-properties path) target-file)
+
+ (when (= 1 org-odt-embedded-images-count)
+ (make-directory target-dir)
+ (org-odt-create-manifest-file-entry "" target-dir))
+
+ (copy-file src-file target-file 'overwrite)
+ (org-odt-create-manifest-file-entry media-type target-file))
+ target-file))
+
+(defvar org-export-odt-image-size-probe-method
+ '(emacs imagemagick force)
+ "Ordered list of methods by for determining size of an embedded
+ image.")
+
+(defvar org-export-odt-default-image-sizes-alist
+ '(("character" . (5 . 0.4))
+ ("paragraph" . (5 . 5)))
+ "Hardcoded image dimensions one for each of the anchor
+ methods.")
+
+;; A4 page size is 21.0 by 29.7 cms
+;; The default page settings has 2cm margin on each of the sides. So
+;; the effective text area is 17.0 by 25.7 cm
+(defvar org-export-odt-max-image-size '(17.0 . 20.0)
+ "Limiting dimensions for an embedded image.")
+
+(defun org-odt-do-image-size (probe-method file &optional dpi anchor-type)
+ (setq dpi (or dpi org-export-odt-pixels-per-inch))
+ (setq anchor-type (or anchor-type "paragraph"))
+ (flet ((size-in-cms (size-in-pixels)
+ (flet ((pixels-to-cms (pixels)
+ (let* ((cms-per-inch 2.54)
+ (inches (/ pixels dpi)))
+ (* cms-per-inch inches))))
+ (and size-in-pixels
+ (cons (pixels-to-cms (car size-in-pixels))
+ (pixels-to-cms (cdr size-in-pixels)))))))
+ (case probe-method
+ (emacs
+ (size-in-cms (ignore-errors (image-size (create-image file) 'pixels))))
+ (imagemagick
+ (size-in-cms
+ (let ((dim (shell-command-to-string
+ (format "identify -format \"%%w:%%h\" \"%s\"" file))))
+ (when (string-match "\\([0-9]+\\):\\([0-9]+\\)" dim)
+ (cons (string-to-number (match-string 1 dim))
+ (string-to-number (match-string 2 dim)))))))
+ (t
+ (cdr (assoc-string anchor-type
+ org-export-odt-default-image-sizes-alist))))))
+
+(defun org-odt-image-size-from-file (file &optional user-width
+ user-height scale dpi embed-as)
+ (unless (file-name-absolute-p file)
+ (setq file (expand-file-name
+ file (file-name-directory org-current-export-file))))
+ (let* (size width height)
+ (unless (and user-height user-width)
+ (loop for probe-method in org-export-odt-image-size-probe-method
+ until size
+ do (setq size (org-odt-do-image-size
+ probe-method file dpi embed-as)))
+ (or size (error "Cannot determine Image size. Aborting ..."))
+ (setq width (car size) height (cdr size)))
+ (cond
+ (scale
+ (setq width (* width scale) height (* height scale)))
+ ((and user-height user-width)
+ (setq width user-width height user-height))
+ (user-height
+ (setq width (* user-height (/ width height)) height user-height))
+ (user-width
+ (setq height (* user-width (/ height width)) width user-width))
+ (t (ignore)))
+ ;; ensure that an embedded image fits comfortably within a page
+ (let ((max-width (car org-export-odt-max-image-size))
+ (max-height (cdr org-export-odt-max-image-size)))
+ (when (or (> width max-width) (> height max-height))
+ (let* ((scale1 (/ max-width width))
+ (scale2 (/ max-height height))
+ (scale (min scale1 scale2)))
+ (setq width (* scale width) height (* scale height)))))
+ (cons width height)))
+
+(defvar org-odt-entity-labels-alist nil
+ "Associate Labels with the Labelled entities.
+Each element of the alist is of the form (LABEL-NAME
+CATEGORY-NAME SEQNO LABEL-STYLE-NAME). LABEL-NAME is same as
+that specified by \"#+LABEL: ...\" line. CATEGORY-NAME is the
+type of the entity that LABEL-NAME is attached to. CATEGORY-NAME
+can be one of \"Table\", \"Figure\" or \"Equation\". SEQNO is
+the unique number assigned to the referenced entity on a
+per-CATEGORY basis. It is generated sequentially and is 1-based.
+LABEL-STYLE-NAME is a key `org-odt-label-styles'.
+
+See `org-odt-add-label-definition' and
+`org-odt-fixup-label-references'.")
+
+(defvar org-odt-entity-counts-plist nil
+ "Plist of running counters of SEQNOs for each of the CATEGORY-NAMEs.
+See `org-odt-entity-labels-alist' for known CATEGORY-NAMEs.")
+
+(defvar org-odt-label-styles
+ '(("text" "(%n)" "text" "(%n)")
+ ("category-and-value" "%e %n%c" "category-and-value" "%e %n"))
+ "Specify how labels are applied and referenced.
+This is an alist where each element is of the
+form (LABEL-STYLE-NAME LABEL-ATTACH-FMT LABEL-REF-MODE
+LABEL-REF-FMT).
+
+LABEL-ATTACH-FMT controls how labels and captions are attached to
+an entity. It may contain following specifiers - %e, %n and %c.
+%e is replaced with the CATEGORY-NAME. %n is replaced with
+\"<text:sequence ...> SEQNO </text:sequence>\". %c is replaced
+with CAPTION. See `org-odt-format-label-definition'.
+
+LABEL-REF-MODE and LABEL-REF-FMT controls how label references
+are generated. The following XML is generated for a label
+reference - \"<text:sequence-ref
+text:reference-format=\"LABEL-REF-MODE\" ...> LABEL-REF-FMT
+</text:sequence-ref>\". LABEL-REF-FMT may contain following
+specifiers - %e and %n. %e is replaced with the CATEGORY-NAME.
+%n is replaced with SEQNO. See
+`org-odt-format-label-reference'.")
+
+(defvar org-odt-category-map-alist
+ '(("__Table__" "Table" "category-and-value")
+ ("__Figure__" "Figure" "category-and-value")
+ ("__MathFormula__" "Equation" "text")
+ ("__DvipngImage__" "Equation" "category-and-value"))
+ "Map a CATEGORY-HANDLE to CATEGORY-NAME and LABEL-STYLE.
+This is an alist where each element is of the form
+\\(CATEGORY-HANDLE CATEGORY-NAME LABEL-STYLE\\). CATEGORY_HANDLE
+could either be one of the internal handles (as seen above) or be
+derived from the \"#+LABEL:<label-name>\" specification. See
+`org-export-odt-get-category-from-label'. CATEGORY-NAME and
+LABEL-STYLE are used for generating ODT labels. See
+`org-odt-label-styles'.")
+
+(defvar org-export-odt-user-categories
+ '("Illustration" "Table" "Text" "Drawing" "Equation" "Figure"))
+
+(defvar org-export-odt-get-category-from-label nil
+ "Should category of label be inferred from label itself.
+When this option is non-nil, a label is parsed in to two
+component parts delimited by a \":\" (colon) as shown here -
+#+LABEL:[CATEGORY-HANDLE:]EXTRA. The CATEGORY-HANDLE is mapped
+to a CATEGORY-NAME and LABEL-STYLE using
+`org-odt-category-map-alist'. (If no such map is provided and
+CATEGORY-NAME is set to CATEGORY-HANDLE and LABEL-STYLE is set to
+\"category-and-value\"). If CATEGORY-NAME so obtained is listed
+under `org-export-odt-user-categories' then the user specified
+styles are used. Otherwise styles as determined by the internal
+CATEGORY-HANDLE is used. See
+`org-odt-get-label-category-and-style' for details.")
+
+(defun org-odt-get-label-category-and-style (label default-category)
+ "See `org-export-odt-get-category-from-label'."
+ (let ((default-category-map
+ (assoc default-category org-odt-category-map-alist))
+ user-category user-category-map category)
+ (cond
+ ((not org-export-odt-get-category-from-label)
+ default-category-map)
+ ((not (setq user-category
+ (save-match-data
+ (and (string-match "\\`\\(.*\\):.+" label)
+ (match-string 1 label)))))
+ default-category-map)
+ (t
+ (setq user-category-map
+ (or (assoc user-category org-odt-category-map-alist)
+ (list nil user-category "category-and-value"))
+ category (nth 1 user-category-map))
+ (if (member category org-export-odt-user-categories)
+ user-category-map
+ default-category-map)))))
+
+(defun org-odt-add-label-definition (label default-category)
+ "Create an entry in `org-odt-entity-labels-alist' and return it."
+ (setq label (substring-no-properties label))
+ (let* ((label-props (org-odt-get-label-category-and-style
+ label default-category))
+ (category (nth 1 label-props))
+ (counter category)
+ (label-style (nth 2 label-props))
+ (sequence-var (intern (mapconcat
+ 'downcase
+ (org-split-string counter) "-")))
+ (seqno (1+ (or (plist-get org-odt-entity-counts-plist sequence-var)
+ 0)))
+ (label-props (list label category seqno label-style)))
+ (setq org-odt-entity-counts-plist
+ (plist-put org-odt-entity-counts-plist sequence-var seqno))
+ (push label-props org-odt-entity-labels-alist)
+ label-props))
+
+(defun org-odt-format-label-definition (caption label category seqno label-style)
+ (assert label)
+ (format-spec
+ (cadr (assoc-string label-style org-odt-label-styles t))
+ `((?e . ,category)
+ (?n . ,(org-odt-format-tags
+ '("<text:sequence text:ref-name=\"%s\" text:name=\"%s\" text:formula=\"ooow:%s+1\" style:num-format=\"1\">" . "</text:sequence>")
+ (format "%d" seqno) label category category))
+ (?c . ,(or (and caption (concat ": " caption)) "")))))
+
+(defun org-odt-format-label-reference (label category seqno label-style)
+ (assert label)
+ (save-match-data
+ (let* ((fmt (cddr (assoc-string label-style org-odt-label-styles t)))
+ (fmt1 (car fmt))
+ (fmt2 (cadr fmt)))
+ (org-odt-format-tags
+ '("<text:sequence-ref text:reference-format=\"%s\" text:ref-name=\"%s\">"
+ . "</text:sequence-ref>")
+ (format-spec fmt2 `((?e . ,category)
+ (?n . ,(format "%d" seqno)))) fmt1 label))))
+
+(defun org-odt-fixup-label-references ()
+ (goto-char (point-min))
+ (while (re-search-forward
+ "<text:sequence-ref text:ref-name=\"\\([^\"]+\\)\">[ \t\n]*</text:sequence-ref>"
+ nil t)
+ (let* ((label (match-string 1))
+ (label-def (assoc label org-odt-entity-labels-alist))
+ (rpl (and label-def
+ (apply 'org-odt-format-label-reference label-def))))
+ (if rpl (replace-match rpl t t)
+ (org-lparse-warn
+ (format "Unable to resolve reference to label \"%s\"" label))))))
+
+(defun org-odt-format-entity-caption (label caption category)
+ (or (and label
+ (apply 'org-odt-format-label-definition
+ caption (org-odt-add-label-definition label category)))
+ caption ""))
+
+(defun org-odt-format-tags (tag text &rest args)
+ (let ((prefix (when org-lparse-encode-pending "@"))
+ (suffix (when org-lparse-encode-pending "@")))
+ (apply 'org-lparse-format-tags tag text prefix suffix args)))
+
+(defvar org-odt-manifest-file-entries nil)
+(defun org-odt-init-outfile (filename)
+ (unless (executable-find "zip")
+ ;; Not at all OSes ship with zip by default
+ (error "Executable \"zip\" needed for creating OpenDocument files"))
+
+ (let* ((outdir (make-temp-file
+ (format org-export-odt-tmpdir-prefix org-lparse-backend) t))
+ (content-file (expand-file-name "content.xml" outdir)))
+
+ ;; init conten.xml
+ (with-current-buffer (find-file-noselect content-file t))
+
+ ;; reset variables
+ (setq org-odt-manifest-file-entries nil
+ org-odt-embedded-images-count 0
+ org-odt-embedded-formulas-count 0
+ org-odt-entity-labels-alist nil
+ org-odt-entity-counts-plist nil)
+ content-file))
+
+(defcustom org-export-odt-prettify-xml nil
+ "Specify whether or not the xml output should be prettified.
+When this option is turned on, `indent-region' is run on all
+component xml buffers before they are saved. Turn this off for
+regular use. Turn this on if you need to examine the xml
+visually."
+ :group 'org-export-odt
+ :type 'boolean)
+
+(defvar hfy-user-sheet-assoc) ; bound during org-do-lparse
+(defun org-odt-save-as-outfile (target opt-plist)
+ ;; write meta file
+ (org-odt-update-meta-file opt-plist)
+
+ ;; write styles file
+ (when (equal org-lparse-backend 'odt)
+ (org-odt-update-styles-file opt-plist))
+
+ ;; create mimetype file
+ (let ((mimetype (org-odt-write-mimetype-file org-lparse-backend)))
+ (org-odt-create-manifest-file-entry mimetype "/" "1.2"))
+
+ ;; create a manifest entry for content.xml
+ (org-odt-create-manifest-file-entry "text/xml" "content.xml")
+
+ ;; write out the manifest entries before zipping
+ (org-odt-write-manifest-file)
+
+ (let ((xml-files '("mimetype" "META-INF/manifest.xml" "content.xml"
+ "meta.xml"))
+ (zipdir default-directory))
+ (when (equal org-lparse-backend 'odt)
+ (push "styles.xml" xml-files))
+ (message "Switching to directory %s" (expand-file-name zipdir))
+
+ ;; save all xml files
+ (mapc (lambda (file)
+ (with-current-buffer
+ (find-file-noselect (expand-file-name file) t)
+ ;; prettify output if needed
+ (when org-export-odt-prettify-xml
+ (indent-region (point-min) (point-max)))
+ (save-buffer 0)))
+ xml-files)
+
+ (let* ((target-name (file-name-nondirectory target))
+ (target-dir (file-name-directory target))
+ (cmds `(("zip" "-mX0" ,target-name "mimetype")
+ ("zip" "-rmTq" ,target-name "."))))
+ (when (file-exists-p target)
+ ;; FIXME: If the file is locked this throws a cryptic error
+ (delete-file target))
+
+ (let ((coding-system-for-write 'no-conversion) exitcode err-string)
+ (message "Creating odt file...")
+ (mapc
+ (lambda (cmd)
+ (message "Running %s" (mapconcat 'identity cmd " "))
+ (setq err-string
+ (with-output-to-string
+ (setq exitcode
+ (apply 'call-process (car cmd)
+ nil standard-output nil (cdr cmd)))))
+ (or (zerop exitcode)
+ (ignore (message "%s" err-string))
+ (error "Unable to create odt file (%S)" exitcode)))
+ cmds))
+
+ ;; move the file from outdir to target-dir
+ (rename-file target-name target-dir)
+
+ ;; kill all xml buffers
+ (mapc (lambda (file)
+ (kill-buffer
+ (find-file-noselect (expand-file-name file zipdir) t)))
+ xml-files)
+
+ (delete-directory zipdir)))
+ (message "Created %s" target)
+ (set-buffer (find-file-noselect target t)))
+
+(defconst org-odt-manifest-file-entry-tag
+ "
+<manifest:file-entry manifest:media-type=\"%s\" manifest:full-path=\"%s\"%s/>")
+
+(defun org-odt-create-manifest-file-entry (&rest args)
+ (push args org-odt-manifest-file-entries))
+
+(defun org-odt-write-manifest-file ()
+ (make-directory "META-INF")
+ (let ((manifest-file (expand-file-name "META-INF/manifest.xml")))
+ (with-current-buffer
+ (find-file-noselect manifest-file t)
+ (insert
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+ <manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\" manifest:version=\"1.2\">\n")
+ (mapc
+ (lambda (file-entry)
+ (let* ((version (nth 2 file-entry))
+ (extra (if version
+ (format " manifest:version=\"%s\"" version)
+ "")))
+ (insert
+ (format org-odt-manifest-file-entry-tag
+ (nth 0 file-entry) (nth 1 file-entry) extra))))
+ org-odt-manifest-file-entries)
+ (insert "\n</manifest:manifest>"))))
+
+(defun org-odt-update-meta-file (opt-plist)
+ (let ((date (org-odt-format-date (plist-get opt-plist :date)))
+ (author (or (plist-get opt-plist :author) ""))
+ (email (plist-get opt-plist :email))
+ (keywords (plist-get opt-plist :keywords))
+ (description (plist-get opt-plist :description))
+ (title (plist-get opt-plist :title)))
+ (write-region
+ (concat
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+ <office:document-meta
+ xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"
+ xmlns:xlink=\"http://www.w3.org/1999/xlink\"
+ xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
+ xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"
+ xmlns:ooo=\"http://openoffice.org/2004/office\"
+ office:version=\"1.2\">
+ <office:meta>" "\n"
+ (org-odt-format-author)
+ (org-odt-format-tags
+ '("\n<meta:initial-creator>" . "</meta:initial-creator>") author)
+ (org-odt-format-tags '("\n<dc:date>" . "</dc:date>") date)
+ (org-odt-format-tags
+ '("\n<meta:creation-date>" . "</meta:creation-date>") date)
+ (org-odt-format-tags '("\n<meta:generator>" . "</meta:generator>")
+ (when org-export-creator-info
+ (format "Org-%s/Emacs-%s"
+ org-version emacs-version)))
+ (org-odt-format-tags '("\n<meta:keyword>" . "</meta:keyword>") keywords)
+ (org-odt-format-tags '("\n<dc:subject>" . "</dc:subject>") description)
+ (org-odt-format-tags '("\n<dc:title>" . "</dc:title>") title)
+ "\n"
+ " </office:meta>" "</office:document-meta>")
+ nil (expand-file-name "meta.xml")))
+
+ ;; create a manifest entry for meta.xml
+ (org-odt-create-manifest-file-entry "text/xml" "meta.xml"))
+
+(defun org-odt-update-styles-file (opt-plist)
+ ;; write styles file
+ (let ((styles-file (plist-get opt-plist :odt-styles-file)))
+ (org-odt-copy-styles-file (and styles-file
+ (read (org-trim styles-file)))))
+
+ ;; Update styles.xml - take care of outline numbering
+ (with-current-buffer
+ (find-file-noselect (expand-file-name "styles.xml") t)
+ ;; Don't make automatic backup of styles.xml file. This setting
+ ;; prevents the backedup styles.xml file from being zipped in to
+ ;; odt file. This is more of a hackish fix. Better alternative
+ ;; would be to fix the zip command so that the output odt file
+ ;; includes only the needed files and excludes any auto-generated
+ ;; extra files like backups and auto-saves etc etc. Note that
+ ;; currently the zip command zips up the entire temp directory so
+ ;; that any auto-generated files created under the hood ends up in
+ ;; the resulting odt file.
+ (set (make-local-variable 'backup-inhibited) t)
+
+ ;; Import local setting of `org-export-with-section-numbers'
+ (org-lparse-bind-local-variables opt-plist)
+ (org-odt-configure-outline-numbering
+ (if org-export-with-section-numbers org-export-headline-levels 0)))
+
+ ;; Write custom stlyes for source blocks
+ (org-odt-insert-custom-styles-for-srcblocks
+ (mapconcat
+ (lambda (style)
+ (format " %s\n" (cddr style)))
+ hfy-user-sheet-assoc "")))
+
+(defun org-odt-write-mimetype-file (format)
+ ;; create mimetype file
+ (let ((mimetype
+ (case format
+ (odt "application/vnd.oasis.opendocument.text")
+ (odf "application/vnd.oasis.opendocument.formula")
+ (t (error "Unknown OpenDocument backend %S" org-lparse-backend)))))
+ (write-region mimetype nil (expand-file-name "mimetype"))
+ mimetype))
+
+(defun org-odt-finalize-outfile ()
+ (org-odt-delete-empty-paragraphs))
+
+(defun org-odt-delete-empty-paragraphs ()
+ (goto-char (point-min))
+ (let ((open "<text:p[^>]*>")
+ (close "</text:p>"))
+ (while (re-search-forward (format "%s[ \r\n\t]*%s" open close) nil t)
+ (replace-match ""))))
+
+(defcustom org-export-odt-convert-processes
+ '(("BasicODConverter"
+ ("soffice" "-norestore" "-invisible" "-headless"
+ "\"macro:///BasicODConverter.Main.Convert(%I,%f,%O)\""))
+ ("unoconv"
+ ("unoconv" "-f" "%f" "-o" "%d" "%i")))
+ "Specify a list of document converters and their usage.
+The converters in this list are offered as choices while
+customizing `org-export-odt-convert-process'.
+
+This variable is an alist where each element is of the
+form (CONVERTER-NAME CONVERTER-PROCESS). CONVERTER-NAME is name
+of the converter. CONVERTER-PROCESS specifies the command-line
+syntax of the converter and is of the form (CONVERTER-PROGRAM
+ARG1 ARG2 ...). CONVERTER-PROGRAM is the name of the executable.
+ARG1, ARG2 etc are command line options that are passed to
+CONVERTER-PROGRAM. Format specifiers can be used in the ARGs and
+they are interpreted as below:
+
+%i input file name in full
+%I input file name as a URL
+%f format of the output file
+%o output file name in full
+%O output file name as a URL
+%d output dir in full
+%D output dir as a URL."
+ :group 'org-export-odt
+ :type
+ '(choice
+ (const :tag "None" nil)
+ (alist :tag "Converters"
+ :key-type (string :tag "Converter Name")
+ :value-type (group (cons (string :tag "Executable")
+ (repeat (string :tag "Command line args")))))))
+
+(defcustom org-export-odt-convert-process nil
+ "Use this converter to convert from \"odt\" format to other formats.
+During customization, the list of converter names are populated
+from `org-export-odt-convert-processes'."
+ :group 'org-export-odt
+ :type '(choice :convert-widget
+ (lambda (w)
+ (apply 'widget-convert (widget-type w)
+ (eval (car (widget-get w :args)))))
+ `((const :tag "None" nil)
+ ,@(mapcar (lambda (c)
+ `(const :tag ,(car c) ,(car c)))
+ org-export-odt-convert-processes))))
+
+(defcustom org-export-odt-convert-capabilities
+ '(("Text"
+ ("odt" "ott" "doc" "rtf")
+ (("pdf" "pdf") ("odt" "odt") ("xhtml" "html") ("rtf" "rtf")
+ ("ott" "ott") ("doc" "doc") ("ooxml" "xml") ("html" "html")))
+ ("Web"
+ ("html" "xhtml") (("pdf" "pdf") ("odt" "txt") ("html" "html")))
+ ("Spreadsheet"
+ ("ods" "ots" "xls" "csv")
+ (("pdf" "pdf") ("ots" "ots") ("html" "html") ("csv" "csv")
+ ("ods" "ods") ("xls" "xls") ("xhtml" "xhtml") ("ooxml" "xml")))
+ ("Presentation"
+ ("odp" "otp" "ppt")
+ (("pdf" "pdf") ("swf" "swf") ("odp" "odp") ("xhtml" "xml")
+ ("otp" "otp") ("ppt" "ppt") ("odg" "odg") ("html" "html"))))
+ "Specify input and output formats of `org-export-odt-convert-process'.
+More correctly, specify the set of input and output formats that
+the user is actually interested in.
+
+This variable is an alist where each element is of the
+form (DOCUMENT-CLASS INPUT-FMT-LIST OUTPUT-FMT-ALIST).
+INPUT-FMT-LIST is a list of INPUT-FMTs. OUTPUT-FMT-ALIST is an
+alist where each element is of the form (OUTPUT-FMT
+OUTPUT-FILE-EXTENSION).
+
+The variable is interpreted as follows:
+`org-export-odt-convert-process' can take any document that is in
+INPUT-FMT-LIST and produce any document that is in the
+OUTPUT-FMT-LIST. A document converted to OUTPUT-FMT will have
+OUTPUT-FILE-EXTENSION as the file name extension. OUTPUT-FMT
+serves dual purposes:
+- It is used for populating completion candidates during
+ `org-export-odt-convert' commands.
+- It is used as the value of \"%f\" specifier in
+ `org-export-odt-convert-process'.
+
+DOCUMENT-CLASS is used to group a set of file formats in
+INPUT-FMT-LIST in to a single class.
+
+Note that this variable inherently captures how LibreOffice based
+converters work. LibreOffice maps documents of various formats
+to classes like Text, Web, Spreadsheet, Presentation etc and
+allow document of a given class (irrespective of it's source
+format) to be converted to any of the export formats associated
+with that class.
+
+See default setting of this variable for an typical
+configuration."
+ :group 'org-export-odt
+ :type
+ '(choice
+ (const :tag "None" nil)
+ (alist :key-type (string :tag "Document Class")
+ :value-type
+ (group (repeat :tag "Input formats" (string :tag "Input format"))
+ (alist :tag "Output formats"
+ :key-type (string :tag "Output format")
+ :value-type
+ (group (string :tag "Output file extension")))))))
+
+(declare-function org-create-math-formula "org"
+ (latex-frag &optional mathml-file))
+
+;;;###autoload
+(defun org-export-odt-convert (&optional in-file out-fmt prefix-arg)
+ "Convert IN-FILE to format OUT-FMT using a command line converter.
+IN-FILE is the file to be converted. If unspecified, it defaults
+to variable `buffer-file-name'. OUT-FMT is the desired output
+format. Use `org-export-odt-convert-process' as the converter.
+If PREFIX-ARG is non-nil then the newly converted file is opened
+using `org-open-file'."
+ (interactive
+ (append (org-lparse-convert-read-params) current-prefix-arg))
+ (org-lparse-do-convert in-file out-fmt prefix-arg))
+
+(defun org-odt-get (what &optional opt-plist)
+ (case what
+ (BACKEND 'odt)
+ (EXPORT-DIR (org-export-directory :html opt-plist))
+ (FILE-NAME-EXTENSION "odt")
+ (EXPORT-BUFFER-NAME "*Org ODT Export*")
+ (ENTITY-CONTROL org-odt-entity-control-callbacks-alist)
+ (ENTITY-FORMAT org-odt-entity-format-callbacks-alist)
+ (INIT-METHOD 'org-odt-init-outfile)
+ (FINAL-METHOD 'org-odt-finalize-outfile)
+ (SAVE-METHOD 'org-odt-save-as-outfile)
+ (CONVERT-METHOD
+ (and org-export-odt-convert-process
+ (cadr (assoc-string org-export-odt-convert-process
+ org-export-odt-convert-processes t))))
+ (CONVERT-CAPABILITIES
+ (and org-export-odt-convert-process
+ (cadr (assoc-string org-export-odt-convert-process
+ org-export-odt-convert-processes t))
+ org-export-odt-convert-capabilities))
+ (TOPLEVEL-HLEVEL 1)
+ (SPECIAL-STRING-REGEXPS org-export-odt-special-string-regexps)
+ (INLINE-IMAGES 'maybe)
+ (INLINE-IMAGE-EXTENSIONS '("png" "jpeg" "jpg" "gif" "svg"))
+ (PLAIN-TEXT-MAP '(("&" . "&amp;") ("<" . "&lt;") (">" . "&gt;")))
+ (TABLE-FIRST-COLUMN-AS-LABELS nil)
+ (FOOTNOTE-SEPARATOR (org-lparse-format 'FONTIFY "," 'superscript))
+ (CODING-SYSTEM-FOR-WRITE 'utf-8)
+ (CODING-SYSTEM-FOR-SAVE 'utf-8)
+ (t (error "Unknown property: %s" what))))
+
+(defvar org-lparse-latex-fragment-fallback) ; set by org-do-lparse
+(defun org-export-odt-do-preprocess-latex-fragments ()
+ "Convert LaTeX fragments to images."
+ (let* ((latex-frag-opt (plist-get org-lparse-opt-plist :LaTeX-fragments))
+ (latex-frag-opt ; massage the options
+ (or (and (member latex-frag-opt '(mathjax t))
+ (not (and (fboundp 'org-format-latex-mathml-available-p)
+ (org-format-latex-mathml-available-p)))
+ (prog1 org-lparse-latex-fragment-fallback
+ (org-lparse-warn
+ (concat
+ "LaTeX to MathML converter not available. "
+ (format "Using %S instead."
+ org-lparse-latex-fragment-fallback)))))
+ latex-frag-opt))
+ cache-dir display-msg)
+ (cond
+ ((eq latex-frag-opt 'dvipng)
+ (setq cache-dir "ltxpng/")
+ (setq display-msg "Creating LaTeX image %s"))
+ ((member latex-frag-opt '(mathjax t))
+ (setq latex-frag-opt 'mathml)
+ (setq cache-dir "ltxmathml/")
+ (setq display-msg "Creating MathML formula %s")))
+ (when (and org-current-export-file)
+ (org-format-latex
+ (concat cache-dir (file-name-sans-extension
+ (file-name-nondirectory org-current-export-file)))
+ org-current-export-dir nil display-msg
+ nil nil latex-frag-opt))))
+
+(defadvice org-format-latex-as-mathml
+ (after org-odt-protect-latex-fragment activate)
+ "Encode LaTeX fragment as XML.
+Do this when translation to MathML fails."
+ (when (or (not (> (length ad-return-value) 0))
+ (get-text-property 0 'org-protected ad-return-value))
+ (setq ad-return-value
+ (org-propertize (org-odt-encode-plain-text (ad-get-arg 0))
+ 'org-protected t))))
+
+(defun org-export-odt-preprocess-latex-fragments ()
+ (when (equal org-export-current-backend 'odt)
+ (org-export-odt-do-preprocess-latex-fragments)))
+
+(defun org-export-odt-preprocess-label-references ()
+ (goto-char (point-min))
+ (let (label label-components category value pretty-label)
+ (while (re-search-forward "\\\\ref{\\([^{}\n]+\\)}" nil t)
+ (org-if-unprotected-at (match-beginning 1)
+ (replace-match
+ (let ((org-lparse-encode-pending t)
+ (label (match-string 1)))
+ ;; markup generated below is mostly an eye-candy. At
+ ;; pre-processing stage, there is no information on which
+ ;; entity a label reference points to. The actual markup
+ ;; is generated as part of `org-odt-fixup-label-references'
+ ;; which gets called at the fag end of export. By this
+ ;; time we would have seen and collected all the label
+ ;; definitions in `org-odt-entity-labels-alist'.
+ (org-odt-format-tags
+ '("<text:sequence-ref text:ref-name=\"%s\">" .
+ "</text:sequence-ref>")
+ "" (org-add-props label '(org-protected t)))) t t)))))
+
+;; process latex fragments as part of
+;; `org-export-preprocess-after-blockquote-hook'. Note that this hook
+;; is the one that is closest and well before the call to
+;; `org-export-attach-captions-and-attributes' in
+;; `org-export-preprocess-stirng'. The above arrangement permits
+;; captions, labels and attributes to be attached to png images
+;; generated out of latex equations.
+(add-hook 'org-export-preprocess-after-blockquote-hook
+ 'org-export-odt-preprocess-latex-fragments)
+
+(defun org-export-odt-preprocess (parameters)
+ (org-export-odt-preprocess-label-references))
+
+(declare-function archive-zip-extract "arc-mode.el" (archive name))
+(defun org-odt-zip-extract-one (archive member &optional target)
+ (require 'arc-mode)
+ (let* ((target (or target default-directory))
+ (archive (expand-file-name archive))
+ (archive-zip-extract
+ (list "unzip" "-qq" "-o" "-d" target))
+ exit-code command-output)
+ (setq command-output
+ (with-temp-buffer
+ (setq exit-code (archive-zip-extract archive member))
+ (buffer-string)))
+ (unless (zerop exit-code)
+ (message command-output)
+ (error "Extraction failed"))))
+
+(defun org-odt-zip-extract (archive members &optional target)
+ (when (atom members) (setq members (list members)))
+ (mapc (lambda (member)
+ (org-odt-zip-extract-one archive member target))
+ members))
+
+(defun org-odt-copy-styles-file (&optional styles-file)
+ ;; Non-availability of styles.xml is not a critical error. For now
+ ;; throw an error purely for aesthetic reasons.
+ (setq styles-file (or styles-file
+ org-export-odt-styles-file
+ (expand-file-name "OrgOdtStyles.xml"
+ org-odt-styles-dir)
+ (error "org-odt: Missing styles file?")))
+ (cond
+ ((listp styles-file)
+ (let ((archive (nth 0 styles-file))
+ (members (nth 1 styles-file)))
+ (org-odt-zip-extract archive members)
+ (mapc
+ (lambda (member)
+ (when (org-file-image-p member)
+ (let* ((image-type (file-name-extension member))
+ (media-type (format "image/%s" image-type)))
+ (org-odt-create-manifest-file-entry media-type member))))
+ members)))
+ ((and (stringp styles-file) (file-exists-p styles-file))
+ (let ((styles-file-type (file-name-extension styles-file)))
+ (cond
+ ((string= styles-file-type "xml")
+ (copy-file styles-file "styles.xml" t))
+ ((member styles-file-type '("odt" "ott"))
+ (org-odt-zip-extract styles-file "styles.xml")))))
+ (t
+ (error (format "Invalid specification of styles.xml file: %S"
+ org-export-odt-styles-file))))
+
+ ;; create a manifest entry for styles.xml
+ (org-odt-create-manifest-file-entry "text/xml" "styles.xml"))
+
+(defvar org-export-odt-factory-settings
+ "d4328fb9d1b6cb211d4320ff546829f26700dc5e"
+ "SHA1 hash of OrgOdtStyles.xml.")
+
+(defun org-odt-configure-outline-numbering (level)
+ "Outline numbering is retained only upto LEVEL.
+To disable outline numbering pass a LEVEL of 0."
+ (goto-char (point-min))
+ (let ((regex
+ "<text:outline-level-style\\([^>]*\\)text:level=\"\\([^\"]*\\)\"\\([^>]*\\)>")
+ (replacement
+ "<text:outline-level-style\\1text:level=\"\\2\" style:num-format=\"\">"))
+ (while (re-search-forward regex nil t)
+ (when (> (string-to-number (match-string 2)) level)
+ (replace-match replacement t nil))))
+ (save-buffer 0))
+
+;;;###autoload
+(defun org-export-as-odf (latex-frag &optional odf-file)
+ "Export LATEX-FRAG as OpenDocument formula file ODF-FILE.
+Use `org-create-math-formula' to convert LATEX-FRAG first to
+MathML. When invoked as an interactive command, use
+`org-latex-regexps' to infer LATEX-FRAG from currently active
+region. If no LaTeX fragments are found, prompt for it. Push
+MathML source to kill ring, if `org-export-copy-to-kill-ring' is
+non-nil."
+ (interactive
+ `(,(let (frag)
+ (setq frag (and (setq frag (and (region-active-p)
+ (buffer-substring (region-beginning)
+ (region-end))))
+ (loop for e in org-latex-regexps
+ thereis (when (string-match (nth 1 e) frag)
+ (match-string (nth 2 e) frag)))))
+ (read-string "LaTeX Fragment: " frag nil frag))
+ ,(let ((odf-filename (expand-file-name
+ (concat
+ (file-name-sans-extension
+ (or (file-name-nondirectory buffer-file-name)))
+ "." "odf")
+ (file-name-directory buffer-file-name))))
+ (message "default val is %s" odf-filename)
+ (read-file-name "ODF filename: " nil odf-filename nil
+ (file-name-nondirectory odf-filename)))))
+ (let* ((org-lparse-backend 'odf)
+ org-lparse-opt-plist
+ (filename (or odf-file
+ (expand-file-name
+ (concat
+ (file-name-sans-extension
+ (or (file-name-nondirectory buffer-file-name)))
+ "." "odf")
+ (file-name-directory buffer-file-name))))
+ (buffer (find-file-noselect (org-odt-init-outfile filename)))
+ (coding-system-for-write 'utf-8)
+ (save-buffer-coding-system 'utf-8))
+ (set-buffer buffer)
+ (set-buffer-file-coding-system coding-system-for-write)
+ (let ((mathml (org-create-math-formula latex-frag)))
+ (unless mathml (error "No Math formula created"))
+ (insert mathml)
+ (or (org-export-push-to-kill-ring
+ (upcase (symbol-name org-lparse-backend)))
+ (message "Exporting... done")))
+ (org-odt-save-as-outfile filename nil)))
+
+;;;###autoload
+(defun org-export-as-odf-and-open ()
+ "Export LaTeX fragment as OpenDocument formula and immediately open it.
+Use `org-export-as-odf' to read LaTeX fragment and OpenDocument
+formula file."
+ (interactive)
+ (org-lparse-and-open
+ nil nil nil (call-interactively 'org-export-as-odf)))
+
+(provide 'org-odt)
+
+;;; org-odt.el ends here