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:
rasmus 2014-12-09 12:40:52 +01:00 committed by Rasmus
parent 6dcaafff3e
commit 7509d6d721
2 changed files with 109 additions and 29 deletions

View File

@ -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 ()

View File

@ -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