소스 검색

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
Nicolas Goaziou 11 년 전
부모
커밋
b8781c4c85
3개의 변경된 파일76개의 추가작업 그리고 8개의 파일을 삭제
  1. 5 3
      doc/org.texi
  2. 29 4
      lisp/ox.el
  3. 42 1
      testing/lisp/test-ox.el

+ 5 - 3
doc/org.texi

@@ -9953,9 +9953,11 @@ include your @file{.emacs} file, you could use:
 @noindent
 @noindent
 The optional second and third parameter are the markup (e.g., @samp{quote},
 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
 @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,
 Contents of the included file will belong to the same structure (headline,
 item) containing the @code{INCLUDE} keyword.  In particular, headlines within
 item) containing the @code{INCLUDE} keyword.  In particular, headlines within

+ 29 - 4
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
 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."
-  (let ((case-fold-search t))
+  (let ((case-fold-search t)
+	(file-prefix (make-hash-table :test #'equal))
+	(current-prefix 0))
     (goto-char (point-min))
     (goto-char (point-min))
     (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))))
@@ -3349,13 +3351,16 @@ paths."
 		 (with-temp-buffer
 		 (with-temp-buffer
 		   (let ((org-inhibit-startup t)) (org-mode))
 		   (let ((org-inhibit-startup t)) (org-mode))
 		   (insert
 		   (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
 		   (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)))))))))))))
 		   (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.
   "Prepare the contents of FILE for inclusion and return them 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
@@ -3369,7 +3374,12 @@ 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
+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
   (with-temp-buffer
     (insert-file-contents file)
     (insert-file-contents file)
     (when lines
     (when lines
@@ -3428,6 +3438,21 @@ file should have."
 	       (org-map-entries
 	       (org-map-entries
 		(lambda () (if (< offset 0) (delete-char (abs offset))
 		(lambda () (if (< offset 0) (delete-char (abs offset))
 			(insert (make-string 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))))
     (org-element-normalize-string (buffer-string))))
 
 
 (defun org-export-execute-babel-code ()
 (defun org-export-execute-babel-code ()

+ 42 - 1
testing/lisp/test-ox.el

@@ -850,7 +850,48 @@ body\n")))
        org-test-dir)
        org-test-dir)
     (org-export-expand-include-keyword)
     (org-export-expand-include-keyword)
     (should (equal (buffer-string)
     (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 ()
 (ert-deftest test-org-export/expand-macro ()
   "Test macro expansion in an Org buffer."
   "Test macro expansion in an Org buffer."