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)
|
(car key)
|
||||||
(if (org-string-nw-p val) (format " %s" val) ""))))))))
|
(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.
|
"Expand every include keyword in buffer.
|
||||||
Optional argument INCLUDED is a list of included file names along
|
Optional argument INCLUDED is a list of included file names along
|
||||||
with their line restriction, when appropriate. It is used to
|
with their line restriction, when appropriate. It is used to
|
||||||
avoid infinite recursion. Optional argument DIR is the current
|
avoid infinite recursion. Optional argument DIR is the current
|
||||||
working directory. It is used to properly resolve relative
|
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)
|
(let ((case-fold-search t)
|
||||||
(file-prefix (make-hash-table :test #'equal))
|
(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))
|
(goto-char (point-min))
|
||||||
|
;; Expand INCLUDE keywords.
|
||||||
(while (re-search-forward "^[ \t]*#\\+INCLUDE:" nil t)
|
(while (re-search-forward "^[ \t]*#\\+INCLUDE:" nil t)
|
||||||
(let ((element (save-match-data (org-element-at-point))))
|
(let ((element (save-match-data (org-element-at-point))))
|
||||||
(when (eq (org-element-type element) 'keyword)
|
(when (eq (org-element-type element) 'keyword)
|
||||||
|
@ -3155,15 +3158,23 @@ paths."
|
||||||
file location only-contents lines)
|
file location only-contents lines)
|
||||||
lines)))
|
lines)))
|
||||||
(org-mode)
|
(org-mode)
|
||||||
(insert
|
(insert (org-export--prepare-file-contents
|
||||||
(org-export--prepare-file-contents
|
file lines ind minlevel
|
||||||
file lines ind minlevel
|
(or (gethash file file-prefix)
|
||||||
(or (gethash file file-prefix)
|
(puthash file (incf current-prefix) file-prefix))
|
||||||
(puthash file (incf current-prefix) file-prefix)))))
|
footnotes)))
|
||||||
(org-export-expand-include-keyword
|
(org-export-expand-include-keyword
|
||||||
(cons (list file lines) included)
|
(cons (list file lines) included)
|
||||||
(file-name-directory file))
|
(file-name-directory file)
|
||||||
(buffer-string)))))))))))))
|
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)
|
(defun org-export--inclusion-absolute-lines (file location only-contents lines)
|
||||||
"Resolve absolute lines for an included file with file-link.
|
"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))
|
(while (< (point) end) (incf counter) (forward-line))
|
||||||
counter))))))))
|
counter))))))))
|
||||||
|
|
||||||
(defun org-export--prepare-file-contents (file &optional lines ind minlevel id)
|
(defun org-export--update-footnote-label (ref-begin digit-label id)
|
||||||
"Prepare the contents of FILE for inclusion and return them as a string.
|
"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
|
When optional argument LINES is a string specifying a range of
|
||||||
lines, include only those lines.
|
lines, include only those lines.
|
||||||
|
@ -3242,11 +3271,14 @@ headline encountered.
|
||||||
Optional argument MINLEVEL, when non-nil, is an integer
|
Optional argument MINLEVEL, when non-nil, is an integer
|
||||||
specifying the level that any top-level headline in the included
|
specifying the level that any top-level headline in the included
|
||||||
file should have.
|
file should have.
|
||||||
|
|
||||||
Optional argument ID is an integer that will be inserted before
|
Optional argument ID is an integer that will be inserted before
|
||||||
each footnote definition and reference if FILE is an Org file.
|
each footnote definition and reference if FILE is an Org file.
|
||||||
This is useful to avoid conflicts when more than one 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
|
(with-temp-buffer
|
||||||
(insert-file-contents file)
|
(insert-file-contents file)
|
||||||
(when lines
|
(when lines
|
||||||
|
@ -3307,20 +3339,40 @@ with footnotes is included in a document."
|
||||||
(insert (make-string offset ?*)))))))))))
|
(insert (make-string offset ?*)))))))))))
|
||||||
;; Append ID to all footnote references and definitions, so they
|
;; Append ID to all footnote references and definitions, so they
|
||||||
;; become file specific and cannot collide with footnotes in other
|
;; become file specific and cannot collide with footnotes in other
|
||||||
;; included files.
|
;; included files. Further, collect relevant footnotes outside of
|
||||||
|
;; LINES.
|
||||||
(when id
|
(when id
|
||||||
(goto-char (point-min))
|
(let ((marker-min (point-min-marker))
|
||||||
(while (re-search-forward org-footnote-re nil t)
|
(marker-max (point-max-marker)))
|
||||||
(let ((reference (org-element-context)))
|
(goto-char (point-min))
|
||||||
(when (memq (org-element-type reference)
|
(while (re-search-forward org-footnote-re nil t)
|
||||||
'(footnote-reference footnote-definition))
|
(let ((reference (org-element-context)))
|
||||||
(goto-char (org-element-property :begin reference))
|
(when (eq (org-element-type reference) 'footnote-reference)
|
||||||
(forward-char)
|
(let* ((label (org-element-property :label reference))
|
||||||
(let ((label (org-element-property :label reference)))
|
(digit-label (and label (org-string-match-p "\\`[0-9]+\\'" label))))
|
||||||
(cond ((not label))
|
;; Update the footnote-reference at point and collect
|
||||||
((org-string-match-p "\\`[0-9]+\\'" label)
|
;; the new label, which is only used for footnotes
|
||||||
(insert (format "fn:%d-" id)))
|
;; outsides LINES.
|
||||||
(t (forward-char 3) (insert (format "%d-" id)))))))))
|
(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))))
|
(org-element-normalize-string (buffer-string))))
|
||||||
|
|
||||||
(defun org-export-execute-babel-code ()
|
(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)))))))))))))
|
(org-element-property :label ref)))))))))))))
|
||||||
;; Footnotes labels are not local to each include keyword.
|
;; Footnotes labels are not local to each include keyword.
|
||||||
(should
|
(should
|
||||||
(= 3
|
(= 4
|
||||||
(length
|
(length
|
||||||
(delete-dups
|
(delete-dups
|
||||||
(let ((contents "
|
(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
|
\[fn:1] Footnote 1
|
||||||
|
\[2] Footnote 2
|
||||||
\[fn:test] Footnote \"test\""))
|
\[fn:test] Footnote \"test\""))
|
||||||
(org-test-with-temp-text-in-file contents
|
(org-test-with-temp-text-in-file contents
|
||||||
(let ((file (buffer-file-name)))
|
(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)
|
(org-element-map (org-element-parse-buffer)
|
||||||
'footnote-reference
|
'footnote-reference
|
||||||
(lambda (ref) (org-element-property :label ref)))))))))))
|
(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.
|
;; If only-contents is non-nil only include contents of element.
|
||||||
(should
|
(should
|
||||||
(equal
|
(equal
|
||||||
|
|
Loading…
Reference in a new issue