forked from mirrors/org-mode
ox.el: Fix footnote-bug in #+INCLUDE-keyword
* ox.el (org-export--prepare-file-contents): Preserve footnotes when using the LINES argument. New optional argument FOOTNOTES. (org-export-expand-include-keyword): New optional argument FOOTNOTES. * test-ox.el (test-org-export/expand-include): Add test for INCLUDE with :lines and footnotes.
This commit is contained in:
parent
6dcaafff3e
commit
7509d6d721
106
lisp/ox.el
106
lisp/ox.el
|
@ -3052,17 +3052,20 @@ locally for the subtree through node properties."
|
|||
(car key)
|
||||
(if (org-string-nw-p val) (format " %s" val) ""))))))))
|
||||
|
||||
(defun org-export-expand-include-keyword (&optional included dir)
|
||||
(defun org-export-expand-include-keyword (&optional included dir footnotes)
|
||||
"Expand every include keyword in buffer.
|
||||
Optional argument INCLUDED is a list of included file names along
|
||||
with their line restriction, when appropriate. It is used to
|
||||
avoid infinite recursion. Optional argument DIR is the current
|
||||
working directory. It is used to properly resolve relative
|
||||
paths."
|
||||
paths. Optional argument FOOTNOTES is a hash-table used for
|
||||
storing and resolving footnotes. It is created automatically."
|
||||
(let ((case-fold-search t)
|
||||
(file-prefix (make-hash-table :test #'equal))
|
||||
(current-prefix 0))
|
||||
(current-prefix 0)
|
||||
(footnotes (or footnotes (make-hash-table :test #'equal))))
|
||||
(goto-char (point-min))
|
||||
;; Expand INCLUDE keywords.
|
||||
(while (re-search-forward "^[ \t]*#\\+INCLUDE:" nil t)
|
||||
(let ((element (save-match-data (org-element-at-point))))
|
||||
(when (eq (org-element-type element) 'keyword)
|
||||
|
@ -3155,15 +3158,23 @@ paths."
|
|||
file location only-contents lines)
|
||||
lines)))
|
||||
(org-mode)
|
||||
(insert
|
||||
(org-export--prepare-file-contents
|
||||
file lines ind minlevel
|
||||
(or (gethash file file-prefix)
|
||||
(puthash file (incf current-prefix) file-prefix)))))
|
||||
(insert (org-export--prepare-file-contents
|
||||
file lines ind minlevel
|
||||
(or (gethash file file-prefix)
|
||||
(puthash file (incf current-prefix) file-prefix))
|
||||
footnotes)))
|
||||
(org-export-expand-include-keyword
|
||||
(cons (list file lines) included)
|
||||
(file-name-directory file))
|
||||
(buffer-string)))))))))))))
|
||||
(file-name-directory file)
|
||||
footnotes)
|
||||
(buffer-string)))))
|
||||
;; Expand footnotes after all files have been
|
||||
;; included. Footnotes are stored at end of buffer.
|
||||
(unless included
|
||||
(org-with-wide-buffer
|
||||
(goto-char (point-max))
|
||||
(maphash (lambda (ref def) (insert (format "\n[%s] %s\n" ref def)))
|
||||
footnotes)))))))))))
|
||||
|
||||
(defun org-export--inclusion-absolute-lines (file location only-contents lines)
|
||||
"Resolve absolute lines for an included file with file-link.
|
||||
|
@ -3227,8 +3238,26 @@ Return a string of lines to be included in the format expected by
|
|||
(while (< (point) end) (incf counter) (forward-line))
|
||||
counter))))))))
|
||||
|
||||
(defun org-export--prepare-file-contents (file &optional lines ind minlevel id)
|
||||
"Prepare the contents of FILE for inclusion and return them as a string.
|
||||
(defun org-export--update-footnote-label (ref-begin digit-label id)
|
||||
"Prefix footnote-label at point REF-BEGIN in buffer with ID.
|
||||
|
||||
REF-BEGIN corresponds to the property `:begin' of objects of type
|
||||
footnote-definition and footnote-reference.
|
||||
|
||||
If DIGIT-LABEL is non-nil the label is assumed to be of the form
|
||||
\[N] where N is one or more numbers.
|
||||
|
||||
Return the new label."
|
||||
(goto-char (1+ ref-begin))
|
||||
(buffer-substring (point)
|
||||
(progn
|
||||
(if digit-label (insert (format "fn:%d-" id))
|
||||
(forward-char 3)
|
||||
(insert (format "%d-" id)))
|
||||
(1- (search-forward "]")))))
|
||||
|
||||
(defun org-export--prepare-file-contents (file &optional lines ind minlevel id footnotes)
|
||||
"Prepare contents of FILE for inclusion and return it as a string.
|
||||
|
||||
When optional argument LINES is a string specifying a range of
|
||||
lines, include only those lines.
|
||||
|
@ -3242,11 +3271,14 @@ headline encountered.
|
|||
Optional argument MINLEVEL, when non-nil, is an integer
|
||||
specifying the level that any top-level headline in the included
|
||||
file should have.
|
||||
|
||||
Optional argument ID is an integer that will be inserted before
|
||||
each footnote definition and reference if FILE is an Org file.
|
||||
This is useful to avoid conflicts when more than one Org file
|
||||
with footnotes is included in a document."
|
||||
with footnotes is included in a document.
|
||||
|
||||
Optional argument FOOTNOTES is a hash-table to store footnotes in
|
||||
the included document.
|
||||
"
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(when lines
|
||||
|
@ -3307,20 +3339,40 @@ with footnotes is included in a document."
|
|||
(insert (make-string offset ?*)))))))))))
|
||||
;; Append ID to all footnote references and definitions, so they
|
||||
;; become file specific and cannot collide with footnotes in other
|
||||
;; included files.
|
||||
;; included files. Further, collect relevant footnotes outside of
|
||||
;; LINES.
|
||||
(when id
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward org-footnote-re nil t)
|
||||
(let ((reference (org-element-context)))
|
||||
(when (memq (org-element-type reference)
|
||||
'(footnote-reference footnote-definition))
|
||||
(goto-char (org-element-property :begin reference))
|
||||
(forward-char)
|
||||
(let ((label (org-element-property :label reference)))
|
||||
(cond ((not label))
|
||||
((org-string-match-p "\\`[0-9]+\\'" label)
|
||||
(insert (format "fn:%d-" id)))
|
||||
(t (forward-char 3) (insert (format "%d-" id)))))))))
|
||||
(let ((marker-min (point-min-marker))
|
||||
(marker-max (point-max-marker)))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward org-footnote-re nil t)
|
||||
(let ((reference (org-element-context)))
|
||||
(when (eq (org-element-type reference) 'footnote-reference)
|
||||
(let* ((label (org-element-property :label reference))
|
||||
(digit-label (and label (org-string-match-p "\\`[0-9]+\\'" label))))
|
||||
;; Update the footnote-reference at point and collect
|
||||
;; the new label, which is only used for footnotes
|
||||
;; outsides LINES.
|
||||
(when label
|
||||
;; If label is akin to [1] convert it to [fn:ID-1].
|
||||
;; Otherwise add "ID-" after "fn:".
|
||||
(let ((new-label (org-export--update-footnote-label
|
||||
(org-element-property :begin reference) digit-label id)))
|
||||
(unless (eq (org-element-property :type reference) 'inline)
|
||||
(org-with-wide-buffer
|
||||
(let* ((definition (org-footnote-get-definition label))
|
||||
(beginning (nth 1 definition)))
|
||||
(unless definition
|
||||
(error "Definition not found for footnote %s in file %s" label file))
|
||||
(if (or (< beginning marker-min) (> beginning marker-max))
|
||||
;; Store since footnote-definition is outside of LINES.
|
||||
(puthash new-label
|
||||
(org-element-normalize-string (nth 3 definition))
|
||||
footnotes)
|
||||
;; Update label of definition since it is included directly.
|
||||
(org-export--update-footnote-label beginning digit-label id)))))))))))
|
||||
(set-marker marker-min nil)
|
||||
(set-marker marker-max nil)))
|
||||
(org-element-normalize-string (buffer-string))))
|
||||
|
||||
(defun org-export-execute-babel-code ()
|
||||
|
|
|
@ -904,12 +904,13 @@ Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
|
|||
(org-element-property :label ref)))))))))))))
|
||||
;; Footnotes labels are not local to each include keyword.
|
||||
(should
|
||||
(= 3
|
||||
(= 4
|
||||
(length
|
||||
(delete-dups
|
||||
(let ((contents "
|
||||
Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
|
||||
Footnotes[fn:1], [fn:test], [2] and [fn:inline:anonymous footnote].
|
||||
\[fn:1] Footnote 1
|
||||
\[2] Footnote 2
|
||||
\[fn:test] Footnote \"test\""))
|
||||
(org-test-with-temp-text-in-file contents
|
||||
(let ((file (buffer-file-name)))
|
||||
|
@ -919,6 +920,33 @@ Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
|
|||
(org-element-map (org-element-parse-buffer)
|
||||
'footnote-reference
|
||||
(lambda (ref) (org-element-property :label ref)))))))))))
|
||||
;; Footnotes are supported by :lines-like elements and unnecessary
|
||||
;; footnotes are dropped.
|
||||
(should
|
||||
(= 4
|
||||
(length
|
||||
(delete-dups
|
||||
(let ((contents "
|
||||
* foo
|
||||
Footnotes[fn:1]
|
||||
* bar
|
||||
Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote]
|
||||
|
||||
\[fn:1] Footnote 1
|
||||
\[fn:2] Footnote 1
|
||||
* Footnotes
|
||||
\[fn:test] Footnote \"test\"
|
||||
\[3] Footnote 3
|
||||
"))
|
||||
(org-test-with-temp-text-in-file contents
|
||||
(let ((file (buffer-file-name)))
|
||||
(org-test-with-temp-text
|
||||
(format "#+INCLUDE: \"%s::*bar\"
|
||||
" file)
|
||||
(org-export-expand-include-keyword)
|
||||
(org-element-map (org-element-parse-buffer)
|
||||
'footnote-definition
|
||||
(lambda (ref) (org-element-property :label ref)))))))))))
|
||||
;; If only-contents is non-nil only include contents of element.
|
||||
(should
|
||||
(equal
|
||||
|
|
Loading…
Reference in New Issue