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
This commit is contained in:
Nicolas Goaziou 2014-03-26 15:34:59 +01:00
parent e84c1d8442
commit b8781c4c85
3 changed files with 76 additions and 8 deletions

View File

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

View File

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

View File

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