diff --git a/lisp/org.el b/lisp/org.el index b53d821ed..9b95733b6 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -18101,7 +18101,7 @@ share a good deal of logic." string tofile options buffer)) (declare-function org-export--get-global-options "org-export" (&optional backend)) -(declare-function org-export--get-inbuffer-options "org-export" (&optional backend files)) +(declare-function org-export--get-inbuffer-options "org-export" (&optional backend)) (defun org-create-formula--latex-header () "Return LaTeX header appropriate for previewing a LaTeX snippet." (org-latex-guess-inputenc @@ -18112,9 +18112,7 @@ share a good deal of logic." (plist-get (org-combine-plists (org-export--get-global-options 'latex) - (org-export--get-inbuffer-options - 'latex - (and buffer-file-name (org-remove-double-quotes buffer-file-name)))) + (org-export--get-inbuffer-options 'latex)) :latex-header-extra)))) ;; This function borrows from Ganesh Swami's latex2png.el diff --git a/lisp/ox.el b/lisp/ox.el index f5a2bc269..30299f201 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -1404,9 +1404,7 @@ inferior to file-local settings." ;; ... from an external property list... ext-plist ;; ... from in-buffer settings... - (org-export--get-inbuffer-options - backend - (and buffer-file-name (org-remove-double-quotes buffer-file-name))) + (org-export--get-inbuffer-options backend) ;; ... and from subtree, when appropriate. (and subtreep (org-export--get-subtree-options backend)) ;; Eventually add misc. properties. @@ -1531,106 +1529,112 @@ for export. Return options as a plist." ;; Return value. plist))) -(defun org-export--get-inbuffer-options (&optional backend files) +(defun org-export--get-inbuffer-options (&optional backend) "Return current buffer export options, as a plist. Optional argument BACKEND, when non-nil, is a symbol specifying which back-end specific options should also be read in the process. -Optional argument FILES is a list of setup files names read so -far, used to avoid circular dependencies. - Assume buffer is in Org mode. Narrowing, if any, is ignored." - (org-with-wide-buffer - (goto-char (point-min)) - (let ((case-fold-search t) plist) - ;; 1. Special keywords, as in `org-export-special-keywords'. - (let ((special-re - (format "^[ \t]*#\\+%s:" (regexp-opt org-export-special-keywords)))) - (while (re-search-forward special-re nil t) - (let ((element (org-element-at-point))) - (when (eq (org-element-type element) 'keyword) - (let* ((key (org-element-property :key element)) - (val (org-element-property :value element)) - (prop + (let* (plist + get-options ; For byte-compiler. + (case-fold-search t) + (options (append + ;; Priority is given to back-end specific options. + (and backend (org-export-backend-options backend)) + org-export-options-alist)) + (regexp (format "^[ \t]*#\\+%s:" + (regexp-opt (nconc (delq nil (mapcar 'cadr options)) + org-export-special-keywords)))) + (find-opt + (lambda (keyword) + ;; Return property name associated to KEYWORD. + (catch 'exit + (mapc (lambda (option) + (when (equal (nth 1 option) keyword) + (throw 'exit (car option)))) + options)))) + (get-options + (lambda (&optional files plist) + ;; Recursively read keywords in buffer. FILES is a list + ;; of files read so far. PLIST is the current property + ;; list obtained. + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (let ((element (org-element-at-point))) + (when (eq (org-element-type element) 'keyword) + (let ((key (org-element-property :key element)) + (val (org-element-property :value element))) (cond + ;; Options in `org-export-special-keywords'. ((equal key "SETUPFILE") - (let ((file - (expand-file-name - (org-remove-double-quotes (org-trim val))))) + (let ((file (expand-file-name + (org-remove-double-quotes (org-trim val))))) ;; Avoid circular dependencies. (unless (member file files) (with-temp-buffer (insert (org-file-contents file 'noerror)) (org-mode) - (org-export--get-inbuffer-options - backend (cons file files)))))) + (setq plist (funcall get-options + (cons file files) plist)))))) ((equal key "OPTIONS") - (org-export--parse-option-keyword val backend)) - ((equal key "FILETAGS") - (list :filetags - (org-uniquify - (append (org-split-string val ":") - (plist-get plist :filetags)))))))) - (setq plist (org-combine-plists plist prop))))))) - ;; 2. Standard options, as in `org-export-options-alist'. - (let* ((all (append - ;; Priority is given to back-end specific options. - (and backend (org-export-backend-options backend)) - org-export-options-alist))) - (dolist (option all) - (let ((prop (car option))) - (when (and (nth 1 option) (not (plist-member plist prop))) - (goto-char (point-min)) - (let ((opt-re (format "^[ \t]*#\\+%s:" (nth 1 option))) - (behaviour (nth 4 option))) - (while (re-search-forward opt-re nil t) - (let ((element (org-element-at-point))) - (when (eq (org-element-type element) 'keyword) - (let((key (org-element-property :key element)) - (val (org-element-property :value element))) (setq plist - (plist-put - plist (car option) - ;; Handle value depending on specified - ;; BEHAVIOUR. - (case behaviour - (space - (if (not (plist-get plist prop)) (org-trim val) - (concat (plist-get plist prop) - " " - (org-trim val)))) - (newline - (org-trim (concat (plist-get plist prop) - "\n" - (org-trim val)))) - (split `(,@(plist-get plist prop) - ,@(org-split-string val))) - ('t val) - (otherwise - (if (not (plist-member plist prop)) val - (plist-get plist prop)))))))))))))) - ;; Parse keywords specified in - ;; `org-element-document-properties'. - (mapc - (lambda (key) - ;; Find the property associated to the keyword. - (let* ((prop (catch 'found - (mapc (lambda (option) - (when (equal (nth 1 option) key) - (throw 'found (car option)))) - all))) - (value (and prop (plist-get plist prop)))) - (when (stringp value) - (setq plist - (plist-put - plist prop - (org-element-parse-secondary-string - value (org-element-restriction 'keyword))))))) - org-element-document-properties)) - ;; 3. Return final value. - plist))) + (org-combine-plists + plist + (org-export--parse-option-keyword val backend)))) + ((equal key "FILETAGS") + (setq plist + (org-combine-plists + plist + (list :filetags + (org-uniquify + (append (org-split-string val ":") + (plist-get plist :filetags))))))) + (t + ;; Options in `org-export-options-alist'. + (let* ((prop (funcall find-opt key)) + (behaviour (nth 4 (assq prop options)))) + (setq plist + (plist-put + plist prop + ;; Handle value depending on specified + ;; BEHAVIOUR. + (case behaviour + (space + (if (not (plist-get plist prop)) + (org-trim val) + (concat (plist-get plist prop) + " " + (org-trim val)))) + (newline + (org-trim (concat (plist-get plist prop) + "\n" + (org-trim val)))) + (split `(,@(plist-get plist prop) + ,@(org-split-string val))) + ('t val) + (otherwise + (if (not (plist-member plist prop)) val + (plist-get plist prop))))))))))))) + ;; Return final value. + plist)))) + ;; Read options in the current buffer. + (setq plist (funcall get-options buffer-file-name nil)) + ;; Parse keywords specified in `org-element-document-properties'. + (mapc (lambda (keyword) + ;; Find the property associated to the keyword. + (let* ((prop (funcall find-opt keyword)) + (value (and prop (plist-get plist prop)))) + (when (stringp value) + (setq plist + (plist-put plist prop + (org-element-parse-secondary-string + value (org-element-restriction 'keyword))))))) + org-element-document-properties) + ;; Return value. + plist)) (defun org-export--get-buffer-attributes () "Return properties related to buffer attributes, as a plist." diff --git a/testing/examples/setupfile.org b/testing/examples/setupfile.org new file mode 100644 index 000000000..5b243158c --- /dev/null +++ b/testing/examples/setupfile.org @@ -0,0 +1,4 @@ +#+DESCRIPTION: l2 +#+LANGUAGE: en +#+SELECT_TAGS: b +#+TITLE: b diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el index ffd61618c..c2ba86c86 100644 --- a/testing/lisp/test-ox.el +++ b/testing/lisp/test-ox.el @@ -122,26 +122,53 @@ already filled in `info'." (ert-deftest test-org-export/get-inbuffer-options () "Test reading all standard export keywords." + ;; Properties should follow buffer order. (should (equal - (org-test-with-temp-text "#+AUTHOR: Me, Myself and I -#+CREATOR: Idem -#+DATE: Today -#+DESCRIPTION: Testing -#+DESCRIPTION: with two lines -#+EMAIL: some@email.org -#+EXCLUDE_TAGS: noexport invisible -#+KEYWORDS: test -#+LANGUAGE: en -#+SELECT_TAGS: export -#+TITLE: Some title -#+TITLE: with spaces" + (org-test-with-temp-text "#+LANGUAGE: fr\n#+CREATOR: Me\n#+EMAIL: email" (org-export--get-inbuffer-options)) - '(:author - ("Me, Myself and I") :creator "Idem" :date ("Today") - :description "Testing\nwith two lines" :email "some@email.org" - :exclude-tags ("noexport" "invisible") :keywords "test" :language "en" - :select-tags ("export") :title ("Some title with spaces"))))) + '(:language "fr" :creator "Me" :email "email"))) + ;; Parse document keywords. + (should + (equal + (org-test-with-temp-text "#+AUTHOR: Me" + (org-export--get-inbuffer-options)) + '(:author ("Me")))) + ;; Test `space' behaviour. + (should + (equal + (org-test-with-temp-text "#+TITLE: Some title\n#+TITLE: with spaces" + (org-export--get-inbuffer-options)) + '(:title ("Some title with spaces")))) + ;; Test `newline' behaviour. + (should + (equal + (org-test-with-temp-text "#+DESCRIPTION: With\n#+DESCRIPTION: two lines" + (org-export--get-inbuffer-options)) + '(:description "With\ntwo lines"))) + ;; Test `split' behaviour. + (should + (equal + (org-test-with-temp-text "#+SELECT_TAGS: a\n#+SELECT_TAGS: b" + (org-export--get-inbuffer-options)) + '(:select-tags ("a" "b")))) + ;; Options set through SETUPFILE. + (should + (equal + (org-test-with-temp-text + (format "#+DESCRIPTION: l1 +#+LANGUAGE: es +#+SELECT_TAGS: a +#+TITLE: a +#+SETUPFILE: \"%s/examples/setupfile.org\" +#+DESCRIPTION: l3 +#+LANGUAGE: fr +#+SELECT_TAGS: c +#+TITLE: c" + org-test-dir) + (org-export--get-inbuffer-options)) + '(:description "l1\nl2\nl3":language "fr" :select-tags ("a" "b" "c") + :title ("a b c"))))) (ert-deftest test-org-export/get-subtree-options () "Test setting options from headline's properties."