summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Goaziou <n.goaziou@gmail.com>2012-04-13 15:30:02 +0200
committerNicolas Goaziou <n.goaziou@gmail.com>2012-04-13 15:47:13 +0200
commit36711b05ba6957bd81118dfd7e81b72fd4f7d828 (patch)
treec402dcbcd78359c924d34531454dcee6a2a7ecb6
parent0c302d412c3198f4a64914d86fc910249144c624 (diff)
downloadorg-mode-36711b05ba6957bd81118dfd7e81b72fd4f7d828.tar.gz
org-export: Fix footnotes export outside parsing scope
* contrib/lisp/org-export.el (org-export-get-buffer-attributes): Function renamed from `org-export-initial-options'. (org-export-store-footnote-definitions): New function. (org-export-as): Store footnote definitions at the appropriate place. It happens twice if buffer gets expanded. * testing/lisp/test-org-export.el: Add test.
-rw-r--r--contrib/lisp/org-export.el202
-rw-r--r--testing/lisp/test-org-export.el64
2 files changed, 138 insertions, 128 deletions
diff --git a/contrib/lisp/org-export.el b/contrib/lisp/org-export.el
index c38eae3..f5a809d 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 2bfb7fe..4f06c36 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 - <<test>>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<<target>>][[test]][[target]]\n[1] A\n\n[2] <<test>>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<<target>>\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