forked from mirrors/org-mode
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.
This commit is contained in:
parent
0c302d412c
commit
36711b05ba
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue