From b8781c4c85f0d51cecb32e5192b5049e8f241939 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Wed, 26 Mar 2014 15:34:59 +0100 Subject: [PATCH] ox: Make footnotes file specific when including Org files * lisp/ox.el (org-export-expand-include-keyword, org-export--prepare-file-contents): Make footnotes file specific when including Org files. * doc/org.texi (Include files): Add documentation. * testing/lisp/test-ox.el (test-org-export/expand-include): Add tests. http://permalink.gmane.org/gmane.emacs.orgmode/83606 --- doc/org.texi | 8 +++++--- lisp/ox.el | 33 +++++++++++++++++++++++++++---- testing/lisp/test-ox.el | 43 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/doc/org.texi b/doc/org.texi index ddd8c2be3..9d86dadc2 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -9953,9 +9953,11 @@ include your @file{.emacs} file, you could use: @noindent The optional second and third parameter are the markup (e.g., @samp{quote}, @samp{example}, or @samp{src}), and, if the markup is @samp{src}, the -language for formatting the contents. The markup is optional; if it is not -given, the text will be assumed to be in Org mode format and will be -processed normally. +language for formatting the contents. + +If no markup is given, the text will be assumed to be in Org mode format and +will be processed normally. However, footnote labels (@pxref{Footnotes}) in +the file will be made local to that file. Contents of the included file will belong to the same structure (headline, item) containing the @code{INCLUDE} keyword. In particular, headlines within diff --git a/lisp/ox.el b/lisp/ox.el index fc0e98739..41f85277c 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -3279,7 +3279,9 @@ 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." - (let ((case-fold-search t)) + (let ((case-fold-search t) + (file-prefix (make-hash-table :test #'equal)) + (current-prefix 0)) (goto-char (point-min)) (while (re-search-forward "^[ \t]*#\\+INCLUDE:" nil t) (let ((element (save-match-data (org-element-at-point)))) @@ -3349,13 +3351,16 @@ paths." (with-temp-buffer (let ((org-inhibit-startup t)) (org-mode)) (insert - (org-export--prepare-file-contents file lines ind minlevel)) + (org-export--prepare-file-contents + file lines ind minlevel + (or (gethash file file-prefix) + (puthash file (incf current-prefix) file-prefix)))) (org-export-expand-include-keyword (cons (list file lines) included) (file-name-directory file)) (buffer-string))))))))))))) -(defun org-export--prepare-file-contents (file &optional lines ind minlevel) +(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. When optional argument LINES is a string specifying a range of @@ -3369,7 +3374,12 @@ 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." +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-temp-buffer (insert-file-contents file) (when lines @@ -3428,6 +3438,21 @@ file should have." (org-map-entries (lambda () (if (< offset 0) (delete-char (abs offset)) (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. + (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)))))))) (org-element-normalize-string (buffer-string)))) (defun org-export-execute-babel-code () diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el index c3c63b51e..d1fee1b42 100644 --- a/testing/lisp/test-ox.el +++ b/testing/lisp/test-ox.el @@ -850,7 +850,48 @@ body\n"))) org-test-dir) (org-export-expand-include-keyword) (should (equal (buffer-string) - "#+BEGIN_SRC emacs-lisp\n(+ 2 1)\n#+END_SRC\n")))) + "#+BEGIN_SRC emacs-lisp\n(+ 2 1)\n#+END_SRC\n"))) + ;; Footnotes labels are local to each included file. + (should + (= 6 + (length + (delete-dups + (let ((contents " +Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote]. +\[fn:1] Footnote 1 +\[fn:test] Footnote \"test\"")) + (org-test-with-temp-text-in-file contents + (let ((file1 (buffer-file-name))) + (org-test-with-temp-text-in-file contents + (let ((file2 (buffer-file-name))) + (org-test-with-temp-text + (format "#+INCLUDE: \"%s\"\n#+INCLUDE: \"%s\"" + file1 file2) + (org-export-expand-include-keyword) + (let (unique-labels) + (org-element-map (org-element-parse-buffer) + 'footnote-reference + (lambda (ref) + (org-element-property :label ref)))))))))))))) + ;; Footnotes labels are not local to each include keyword. + (should + (= 3 + (length + (delete-dups + (let ((contents " +Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote]. +\[fn:1] Footnote 1 +\[fn:test] Footnote \"test\"")) + (org-test-with-temp-text-in-file contents + (let ((file (buffer-file-name))) + (org-test-with-temp-text + (format "#+INCLUDE: \"%s\"\n#+INCLUDE: \"%s\"" file file) + (org-export-expand-include-keyword) + (let (unique-labels) + (org-element-map (org-element-parse-buffer) + 'footnote-reference + (lambda (ref) + (org-element-property :label ref))))))))))))) (ert-deftest test-org-export/expand-macro () "Test macro expansion in an Org buffer."