diff --git a/contrib/lisp/org-export.el b/contrib/lisp/org-export.el index c38eae317..f5a809d60 100644 --- a/contrib/lisp/org-export.el +++ b/contrib/lisp/org-export.el @@ -652,7 +652,6 @@ standard mode." ;; 1. Environment options are collected once at the very beginning of ;; the process, out of the original buffer and configuration. -;; Associated to the parse tree, they make an Org closure. ;; Collecting them is handled by `org-export-get-environment' ;; function. ;; @@ -891,6 +890,7 @@ standard mode." ;; increasing precedence order: ;; ;; - Global variables, +;; - Buffer's attributes, ;; - Options keyword symbols, ;; - Buffer keywords, ;; - Subtree properties. @@ -901,14 +901,11 @@ standard mode." ;; the different sources. ;; The internal functions doing the retrieval are: -;; `org-export-parse-option-keyword' , -;; `org-export-get-subtree-options' , -;; `org-export-get-inbuffer-options' and -;; `org-export-get-global-options'. -;; -;; Some properties, which do not rely on the previous sources but -;; still depend on the original buffer, are taken care of with -;; `org-export-initial-options'. +;; `org-export-get-global-options', +;; `org-export-get-buffer-attributes', +;; `org-export-parse-option-keyword', +;; `org-export-get-subtree-options' and +;; `org-export-get-inbuffer-options' ;; Also, `org-export-confirm-letbind' and `org-export-install-letbind' ;; take care of the part relative to "#+BIND:" keywords. @@ -931,13 +928,8 @@ inferior to file-local settings." (let ((options (org-combine-plists ;; ... from global variables... (org-export-get-global-options backend) - ;; ... from buffer's name (default title)... - `(:title - ,(or (let ((file (buffer-file-name (buffer-base-buffer)))) - (and file - (file-name-sans-extension - (file-name-nondirectory file)))) - (buffer-name (buffer-base-buffer)))) + ;; ... from buffer's attributes... + (org-export-get-buffer-attributes) ;; ... from an external property list... ext-plist ;; ... from in-buffer settings... @@ -947,8 +939,6 @@ inferior to file-local settings." (org-remove-double-quotes buffer-file-name))) ;; ... and from subtree, when appropriate. (and subtreep (org-export-get-subtree-options))))) - ;; Add initial options. - (setq options (append (org-export-initial-options) options)) ;; Return plist. options)) @@ -1140,6 +1130,32 @@ Assume buffer is in Org mode. Narrowing, if any, is ignored." ;; 3. Return final value. plist))) +(defun org-export-get-buffer-attributes () + "Return properties related to buffer attributes, as a plist." + (let ((visited-file (buffer-file-name (buffer-base-buffer)))) + (list + ;; Store full path of input file name, or nil. For internal use. + :input-file visited-file + :title (or (and visited-file + (file-name-sans-extension + (file-name-nondirectory visited-file))) + (buffer-name (buffer-base-buffer))) + :macro-modification-time + (and visited-file + (file-exists-p visited-file) + (concat "(eval (format-time-string \"$1\" '" + (prin1-to-string (nth 5 (file-attributes visited-file))) + "))")) + ;; Store input file name as a macro. + :macro-input-file (and visited-file (file-name-nondirectory visited-file)) + ;; `:macro-date', `:macro-time' and `:macro-property' could as + ;; well be initialized as tree properties, since they don't + ;; depend on buffer properties. Though, it may be more logical + ;; to keep them close to other ":macro-" properties. + :macro-date "(eval (format-time-string \"$1\"))" + :macro-time "(eval (format-time-string \"$1\"))" + :macro-property "(eval (org-entry-get nil \"$1\" 'selective))"))) + (defun org-export-get-global-options (&optional backend) "Return global export options as a plist. @@ -1159,50 +1175,46 @@ process." ;; Return value. plist)) -(defun org-export-initial-options () - "Return a plist with properties related to input buffer." - (let ((visited-file (buffer-file-name (buffer-base-buffer)))) - (list - ;; Store full path of input file name, or nil. For internal use. - :input-file visited-file - ;; `:macro-date', `:macro-time' and `:macro-property' could as well - ;; be initialized as tree properties, since they don't depend on - ;; initial environment. Though, it may be more logical to keep - ;; them close to other ":macro-" properties. - :macro-date "(eval (format-time-string \"$1\"))" - :macro-time "(eval (format-time-string \"$1\"))" - :macro-property "(eval (org-entry-get nil \"$1\" 'selective))" - :macro-modification-time - (and visited-file - (file-exists-p visited-file) - (concat "(eval (format-time-string \"$1\" '" - (prin1-to-string (nth 5 (file-attributes visited-file))) - "))")) - ;; Store input file name as a macro. - :macro-input-file (and visited-file (file-name-nondirectory visited-file)) - ;; Footnotes definitions must be collected in the original buffer, - ;; as there's no insurance that they will still be in the parse - ;; tree, due to some narrowing. - :footnote-definition-alist - (let (alist) - (org-with-wide-buffer - (goto-char (point-min)) - (while (re-search-forward org-footnote-definition-re nil t) - (let ((def (org-footnote-at-definition-p))) - (when def - (org-skip-whitespace) - (push (cons (car def) - (save-restriction - (narrow-to-region (point) (nth 2 def)) - ;; Like `org-element-parse-buffer', but - ;; makes sure the definition doesn't start - ;; with a section element. - (nconc - (list 'org-data nil) - (org-element-parse-elements - (point-min) (point-max) nil nil nil nil nil)))) - alist)))) - alist))))) +(defun org-export-store-footnote-definitions (info) + "Collect and store footnote definitions from current buffer in INFO. + +INFO is a plist containing export options. + +Footnotes definitions are stored as a alist whose CAR is +footnote's label, as a string, and CDR the contents, as a parse +tree. This alist will be consed to the value of +`:footnote-definition-alist' in INFO, if any. + +The new plist is returned; use + + \(setq info (org-export-store-footnote-definitions info)) + +to be sure to use the new value. INFO is modified by side +effects." + ;; Footnotes definitions must be collected in the original buffer, + ;; as there's no insurance that they will still be in the parse + ;; tree, due to some narrowing. + (plist-put + info :footnote-definition-alist + (let ((alist (plist-get info :footnote-definition-alist))) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward org-footnote-definition-re nil t) + (let ((def (org-footnote-at-definition-p))) + (when def + (org-skip-whitespace) + (push (cons (car def) + (save-restriction + (narrow-to-region (point) (nth 2 def)) + ;; Like `org-element-parse-buffer', but makes + ;; sure the definition doesn't start with + ;; a section element. + (nconc + (list 'org-data nil) + (org-element-parse-elements + (point-min) (point-max) nil nil nil nil nil)))) + alist)))) + alist)))) (defvar org-export-allow-BIND-local nil) (defun org-export-confirm-letbind () @@ -2078,54 +2090,60 @@ to be expanded and Babel code to be executed. Return code as a string." (save-excursion (save-restriction - (let (info tree) - ;; Narrow buffer to an appropriate region or subtree for - ;; parsing. If parsing subtree, be sure to remove main - ;; headline too. - (cond ((org-region-active-p) - (narrow-to-region (region-beginning) (region-end))) - (subtreep - (org-narrow-to-subtree) - (goto-char (point-min)) - (forward-line) - (narrow-to-region (point) (point-max)))) - ;; 1. Get export environment and tree. Environment is - ;; relative to the buffer being parsed, which isn't always - ;; the original one, depending on the NOEXPAND value. + ;; Narrow buffer to an appropriate region or subtree for + ;; parsing. If parsing subtree, be sure to remove main headline + ;; too. + (cond ((org-region-active-p) + (narrow-to-region (region-beginning) (region-end))) + (subtreep + (org-narrow-to-subtree) + (goto-char (point-min)) + (forward-line) + (narrow-to-region (point) (point-max)))) + ;; 1. Get export environment from original buffer. Store + ;; original footnotes definitions in communication channel as + ;; they might not be accessible anymore in a narrowed parse + ;; tree. Also install user's and developer's filters. + (let ((info (org-export-install-filters + backend + (org-export-store-footnote-definitions + (org-export-get-environment backend subtreep ext-plist)))) + tree) + ;; 2. Get parse tree. (if noexpand ;; If NOEXPAND is non-nil, simply parse current visible - ;; part of buffer and retrieve environment from original - ;; buffer. - (setq info (org-export-get-environment backend subtreep ext-plist) - tree (org-element-parse-buffer nil visible-only)) + ;; part of buffer. + (setq tree (org-element-parse-buffer nil visible-only)) ;; Otherwise, buffer isn't parsed directly. Instead, ;; a temporary copy is created, where include keywords are - ;; expanded and code blocks are evaluated. Environment is - ;; retrieved from that buffer. Moreover, save original file - ;; name or buffer in order to properly resolve babel block - ;; expansion when body is outside scope. + ;; expanded and code blocks are evaluated. (let ((buf (or (buffer-file-name (buffer-base-buffer)) (current-buffer)))) (org-export-with-current-buffer-copy (org-export-expand-include-keyword) + ;; Setting `org-current-export-file' is required by Org + ;; Babel to properly resolve noweb references. (let ((org-current-export-file buf)) (org-export-blocks-preprocess)) - (setq info (org-export-get-environment backend subtreep ext-plist) - tree (org-element-parse-buffer nil visible-only))))) - ;; 2. Install user's and developer's filters in communication - ;; channel. Then call parse-tree filters to get the final - ;; tree. - (setq info (org-export-install-filters backend info)) + (setq tree (org-element-parse-buffer nil visible-only) + ;; Footnote definitions must be stored again, since + ;; buffer's expansion might have modified + ;; boundaries of footnote definitions contained in + ;; the parse tree. This way, definitions in + ;; `footnote-definition-alist' are bound to + ;; coincide with those in the parse tree. + info (org-export-store-footnote-definitions info))))) + ;; 3. Call parse-tree filters to get the final tree. (setq tree (org-export-filter-apply-functions (plist-get info :filter-parse-tree) tree backend info)) - ;; 3. Now tree is complete, compute its properties and add + ;; 4. Now tree is complete, compute its properties and add ;; them to communication channel. (setq info (org-combine-plists info (org-export-collect-tree-properties tree info backend))) - ;; 4. Eventually transcode TREE. Wrap the resulting string + ;; 5. Eventually transcode TREE. Wrap the resulting string ;; into a template, if required. Eventually call ;; final-output filter. (let* ((body (org-element-normalize-string diff --git a/testing/lisp/test-org-export.el b/testing/lisp/test-org-export.el index 2bfb7fec2..4f06c36f6 100644 --- a/testing/lisp/test-org-export.el +++ b/testing/lisp/test-org-export.el @@ -338,10 +338,8 @@ body\n"))) (org-test-with-temp-text "Text[fn:1] [1] [fn:label:C] [fn::D]\n\n[fn:1] A\n\n[1] B" (let* ((tree (org-element-parse-buffer)) - (info (org-combine-plists - (org-export-initial-options) '(:with-footnotes t)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (info (org-export-store-footnote-definitions + `(:parse-tree ,tree :with-footnotes t)))) (should (equal '((1 . "A") (2 . "B") (3 . "C") (4 . "D")) @@ -358,10 +356,8 @@ body\n"))) (org-test-with-temp-text "Text[fn:1:A[fn:2]] [fn:3].\n\n[fn:2] B [fn:3] [fn::D].\n\n[fn:3] C." (let* ((tree (org-element-parse-buffer)) - (info (org-combine-plists - (org-export-initial-options) '(:with-footnotes t)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (info (org-export-store-footnote-definitions + `(:parse-tree ,tree :with-footnotes t)))) (should (equal '((1 . "fn:1") (2 . "fn:2") (3 . "fn:3") (4)) @@ -377,10 +373,8 @@ body\n"))) ;; Hide definitions. (narrow-to-region (point) (point-at-eol)) (let* ((tree (org-element-parse-buffer)) - (info (org-combine-plists - (org-export-initial-options) '(:with-footnotes t)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (info (org-export-store-footnote-definitions + `(:parse-tree ,tree :with-footnotes t)))) ;; Both footnotes should be seen. (should (= (length (org-export-collect-footnote-definitions tree info)) 2)))) @@ -390,13 +384,23 @@ body\n"))) \[fn:2] B [fn:3] [fn::D]. \[fn:3] C." - (let ((tree (org-element-parse-buffer)) - (info (org-combine-plists - (org-export-initial-options) '(:with-footnotes t)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (let* ((tree (org-element-parse-buffer)) + (info (org-export-store-footnote-definitions + `(:parse-tree ,tree :with-footnotes t)))) (should (= (length (org-export-collect-footnote-definitions tree info)) - 4)))))) + 4)))) + ;; 5. Test export of footnotes defined outside parsing scope. + (org-test-with-temp-text "[fn:1] Out of scope +* Title +Paragraph[fn:1]" + (org-test-with-backend "test" + (flet ((org-test-footnote-reference + (fn-ref contents info) + (org-element-interpret-data + (org-export-get-footnote-definition fn-ref info)))) + (forward-line) + (should (equal "ParagraphOut of scope\n" + (org-export-as 'test 'subtree)))))))) @@ -408,9 +412,7 @@ body\n"))) (org-test-with-temp-text "Paragraph.\n#+TARGET: Test\n[[Test]]" (let* ((tree (org-element-parse-buffer)) - (info (org-combine-plists (org-export-initial-options)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (info (org-export-collect-tree-properties tree nil 'test))) (should-not (org-element-map tree 'link @@ -421,9 +423,7 @@ body\n"))) (org-test-with-temp-text "Paragraph.\n* Head1\n* Head2\n* Head3\n[[Head2]]" (let* ((tree (org-element-parse-buffer)) - (info (org-combine-plists (org-export-initial-options)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (info (org-export-collect-tree-properties tree nil 'test))) (should ;; Note: Headline's number is in fact a list of numbers. (equal '(2) @@ -436,9 +436,7 @@ body\n"))) (org-test-with-temp-text "- Item1\n - Item11\n - <>Item12\n- Item2\n\n\n[[test]]" (let* ((tree (org-element-parse-buffer)) - (info (org-combine-plists (org-export-initial-options)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (info (org-export-collect-tree-properties tree nil 'test))) (should ;; Note: Item's number is in fact a list of numbers. (equal '(1 2) @@ -452,9 +450,7 @@ body\n"))) (org-test-with-temp-text "Paragraph[1][2][fn:lbl3:C<>][[test]][[target]]\n[1] A\n\n[2] <>B" (let* ((tree (org-element-parse-buffer)) - (info (org-combine-plists (org-export-initial-options)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (info (org-export-collect-tree-properties tree nil 'test))) (should (equal '(2 3) (org-element-map @@ -467,9 +463,7 @@ body\n"))) (org-test-with-temp-text "#+NAME: tbl1\n|1|2|\n#+NAME: tbl2\n|3|4|\n#+NAME: tbl3\n|5|6|\n[[tbl2]]" (let* ((tree (org-element-parse-buffer)) - (info (org-combine-plists (org-export-initial-options)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (info (org-export-collect-tree-properties tree nil 'test))) (should (= 2 (org-element-map @@ -482,9 +476,7 @@ body\n"))) (org-test-with-temp-text "* Head1\n* Head2\nParagraph<>\n* Head3\n[[target]]" (let* ((tree (org-element-parse-buffer)) - (info (org-combine-plists (org-export-initial-options)))) - (setq info (org-combine-plists - info (org-export-collect-tree-properties tree info 'test))) + (info (org-export-collect-tree-properties tree nil 'test))) (should (equal '(2) (org-element-map