diff --git a/doc/org.texi b/doc/org.texi index 27ec715ae..232c176b5 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -1922,16 +1922,9 @@ The Org homepage[fn:1] now looks a lot better than it used to. @end example Org mode extends the number-based syntax to @emph{named} footnotes and -optional inline definition. Using plain numbers as markers (as -@file{footnote.el} does) is supported for backward compatibility, but not -encouraged because of possible conflicts with @LaTeX{} snippets (@pxref{Embedded -@LaTeX{}}). Here are the valid references: +optional inline definition. Here are the valid references: @table @code -@item [1] -A plain numeric footnote marker. Compatible with @file{footnote.el}, but not -recommended because something like @samp{[1]} could easily be part of a code -snippet. @item [fn:name] A named footnote reference, where @code{name} is a unique label word, or, for simplicity of automatic creation, a number. diff --git a/lisp/org-element.el b/lisp/org-element.el index 1a01e618d..d6695c8d0 100644 --- a/lisp/org-element.el +++ b/lisp/org-element.el @@ -151,7 +151,7 @@ specially in `org-element--object-lex'.") ;; Headlines, inlinetasks. org-outline-regexp "\\|" ;; Footnote definitions. - "\\[\\(?:[0-9]+\\|fn:[-_[:word:]]+\\)\\]" "\\|" + "\\[fn:[-_[:word:]]+\\]" "\\|" ;; Diary sexps. "%%(" "\\|" "[ \t]*\\(?:" @@ -199,7 +199,12 @@ specially in `org-element--object-lex'.") ;; Objects starting with "[": regular link, ;; footnote reference, statistics cookie, ;; timestamp (inactive). - "\\[\\(?:fn:\\|\\(?:[0-9]\\|\\(?:%\\|/[0-9]*\\)\\]\\)\\|\\[\\)" + (concat "\\[\\(?:" + "fn:" "\\|" + "\\[" "\\|" + "[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" "\\|" + "[0-9]*\\(?:%\\|/[0-9]*\\)\\]" + "\\)") ;; Objects starting with "@": export snippets. "@@" ;; Objects starting with "{": macro. @@ -836,7 +841,7 @@ Assume point is at the beginning of the footnote definition." (defun org-element-footnote-definition-interpreter (footnote-definition contents) "Interpret FOOTNOTE-DEFINITION element as Org syntax. CONTENTS is the contents of the footnote-definition." - (concat (format "[%s]" (org-element-property :label footnote-definition)) + (concat (format "[fn:%s]" (org-element-property :label footnote-definition)) " " contents)) @@ -2767,14 +2772,10 @@ When at a footnote reference, return a list whose car is (when closing (save-excursion (let* ((begin (point)) - (label - (or (org-match-string-no-properties 2) - (org-match-string-no-properties 3) - (and (match-string 1) - (concat "fn:" (org-match-string-no-properties 1))))) - (type (if (or (not label) (match-string 1)) 'inline 'standard)) + (label (match-string-no-properties 1)) (inner-begin (match-end 0)) (inner-end (1- closing)) + (type (if (match-end 2) 'inline 'standard)) (post-blank (progn (goto-char closing) (skip-chars-forward " \t"))) (end (point))) @@ -2790,9 +2791,9 @@ When at a footnote reference, return a list whose car is (defun org-element-footnote-reference-interpreter (footnote-reference contents) "Interpret FOOTNOTE-REFERENCE object as Org syntax. CONTENTS is its definition, when inline, or nil." - (format "[%s]" - (concat (or (org-element-property :label footnote-reference) "fn:") - (and contents (concat ":" contents))))) + (format "[fn:%s%s]" + (or (org-element-property :label footnote-reference) "") + (if contents (concat ":" contents) ""))) ;;;; Inline Babel Call diff --git a/lisp/ox.el b/lisp/ox.el index 82833e3c1..119b6b41b 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -3351,7 +3351,7 @@ storing and resolving footnotes. It is created automatically." (unless included (org-with-wide-buffer (goto-char (point-max)) - (maphash (lambda (k v) (insert (format "\n[%s] %s\n" k v))) + (maphash (lambda (k v) (insert (format "\n[fn:%s] %s\n" k v))) footnotes))))))))))) (defun org-export--inclusion-absolute-lines (file location only-contents lines) @@ -3472,7 +3472,7 @@ the included document." (unless (eq major-mode 'org-mode) (let ((org-inhibit-startup t)) (org-mode))) (goto-char (point-min)) - (let ((ind-str (make-string ind ? ))) + (let ((ind-str (make-string ind ?\s))) (while (not (or (eobp) (looking-at org-outline-regexp-bol))) ;; Do not move footnote definitions out of column 0. (unless (and (looking-at org-footnote-definition-re) @@ -3508,17 +3508,14 @@ the included document." (marker-max (point-max-marker)) (get-new-label (lambda (label) - ;; Generate new label from LABEL. If LABEL is akin to - ;; [1] convert it to [fn:--ID-1]. Otherwise add "-ID-" - ;; after "fn:". - (if (org-string-match-p "\\`[0-9]+\\'" label) - (format "fn:--%d-%s" id label) - (format "fn:-%d-%s" id (substring label 3))))) + ;; Generate new label from LABEL by prefixing it with + ;; "-ID-". + (format "-%d-%s" id label))) (set-new-label (lambda (f old new) ;; Replace OLD label with NEW in footnote F. (save-excursion - (goto-char (1+ (org-element-property :begin f))) + (goto-char (+ (org-element-property :begin f) 4)) (looking-at (regexp-quote old)) (replace-match new)))) (seen-alist)) diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el index 1abdd9da0..12cf2dd74 100644 --- a/testing/lisp/test-org-element.el +++ b/testing/lisp/test-org-element.el @@ -938,11 +938,6 @@ Some other text (org-test-with-temp-text "Text[fn:label]" (org-element-map (org-element-parse-buffer) 'footnote-reference 'identity))) - ;; Parse a normalized reference. - (should - (org-test-with-temp-text "Text[1]" - (org-element-map - (org-element-parse-buffer) 'footnote-reference 'identity))) ;; Parse an inline reference. (should (org-test-with-temp-text "Text[fn:test:def]" @@ -2893,17 +2888,15 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu (ert-deftest test-org-element/footnote-reference-interpreter () "Test footnote reference interpreter." - ;; 1. Regular reference. + ;; Regular reference. (should (equal (org-test-parse-and-interpret "Text[fn:1]") "Text[fn:1]\n")) - ;; 2. Normalized reference. - (should (equal (org-test-parse-and-interpret "Text[1]") "Text[1]\n")) - ;; 3. Named reference. + ;; Named reference. (should (equal (org-test-parse-and-interpret "Text[fn:label]") "Text[fn:label]\n")) - ;; 4. Inline reference. + ;; Inline reference. (should (equal (org-test-parse-and-interpret "Text[fn:label:def]") "Text[fn:label:def]\n")) - ;; 5. Anonymous reference. + ;; Anonymous reference. (should (equal (org-test-parse-and-interpret "Text[fn::def]") "Text[fn::def]\n"))) diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el index 74a94f363..b0518deb5 100644 --- a/testing/lisp/test-ox.el +++ b/testing/lisp/test-ox.el @@ -1054,7 +1054,7 @@ text (length (delete-dups (let ((contents " -Footnotes[fn:1], [fn:test], [fn:test] and [fn:inline:anonymous footnote]. +Footnotes[fn:1], [fn:test], [fn:test] and [fn:inline:inline footnote]. \[fn:1] Footnote 1 \[fn:test] Footnote \"test\"")) (org-test-with-temp-text-in-file contents @@ -1070,13 +1070,12 @@ Footnotes[fn:1], [fn:test], [fn:test] and [fn:inline:anonymous footnote]. (lambda (r) (org-element-property :label r))))))))))))) ;; Footnotes labels are not local to each include keyword. (should - (= 4 + (= 3 (length (delete-dups (let ((contents " -Footnotes[fn:1], [fn:test], [2] and [fn:inline:anonymous footnote]. +Footnotes[fn:1], [fn:test] and [fn:inline:inline 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))) @@ -1089,26 +1088,24 @@ Footnotes[fn:1], [fn:test], [2] and [fn:inline:anonymous footnote]. ;; Footnotes are supported by :lines-like elements and unnecessary ;; footnotes are dropped. (should - (= 4 + (= 3 (length (delete-dups (let ((contents " * foo Footnotes[fn:1] * bar -Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote] +Footnotes[fn:2], foot[fn:test] and [fn:inline:inline 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) + (format "#+INCLUDE: \"%s::*bar\"\n" file) (org-export-expand-include-keyword) (org-element-map (org-element-parse-buffer) 'footnote-definition @@ -1119,7 +1116,8 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote "body\n" (org-test-with-temp-text (concat - (format "#+INCLUDE: \"%s/examples/include.org::*Heading\" " org-test-dir) + (format "#+INCLUDE: \"%s/examples/include.org::*Heading\" " + org-test-dir) ":only-contents t") (org-export-expand-include-keyword) (buffer-string)))) @@ -1135,27 +1133,32 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote (equal "| 1 |\n" (org-test-with-temp-text - (format "#+INCLUDE: \"%s/examples/include.org::tbl\" :only-contents t" org-test-dir) + (format "#+INCLUDE: \"%s/examples/include.org::tbl\" :only-contents t" + org-test-dir) (org-export-expand-include-keyword) (buffer-string)))) ;; Including non-existing elements should result in an error. (should-error (org-test-with-temp-text - (format "#+INCLUDE: \"%s/examples/include.org::*non-existing heading\"" org-test-dir) + (format "#+INCLUDE: \"%s/examples/include.org::*non-existing heading\"" + org-test-dir) (org-export-expand-include-keyword))) ;; Lines work relatively to an included element. (should (equal "2\n3\n" (org-test-with-temp-text - (format "#+INCLUDE: \"%s/examples/include.org::#ah\" :only-contents t :lines \"2-3\"" org-test-dir) + (format "#+INCLUDE: \"%s/examples/include.org::#ah\" :only-contents t \ +:lines \"2-3\"" + org-test-dir) (org-export-expand-include-keyword) (buffer-string)))) ;; Properties should be dropped from headlines. (should (equal (org-test-with-temp-text - (format "#+INCLUDE: \"%s/examples/include.org::#ht\" :only-contents t" org-test-dir) + (format "#+INCLUDE: \"%s/examples/include.org::#ht\" :only-contents t" + org-test-dir) (org-export-expand-include-keyword) (buffer-string)) (org-test-with-temp-text @@ -1167,7 +1170,8 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote (equal ":LOGBOOK:\ndrawer\n:END:\ncontent\n" (org-test-with-temp-text - (format "#+INCLUDE: \"%s/examples/include.org::#dh\" :only-contents t" org-test-dir) + (format "#+INCLUDE: \"%s/examples/include.org::#dh\" :only-contents t" + org-test-dir) (org-export-expand-include-keyword) (buffer-string)))) ;; Adjacent INCLUDE-keywords should have the same :minlevel if unspecified. @@ -1175,8 +1179,10 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote (cl-every (lambda (level) (zerop (1- level))) (org-test-with-temp-text (concat - (format "#+INCLUDE: \"%s/examples/include.org::#ah\"\n" org-test-dir) - (format "#+INCLUDE: \"%s/examples/include.org::*Heading\"" org-test-dir)) + (format "#+INCLUDE: \"%s/examples/include.org::#ah\"\n" + org-test-dir) + (format "#+INCLUDE: \"%s/examples/include.org::*Heading\"" + org-test-dir)) (org-export-expand-include-keyword) (org-element-map (org-element-parse-buffer) 'headline (lambda (head) (org-element-property :level head)))))) @@ -1184,30 +1190,37 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote (should-not (equal (org-test-with-temp-text - (format "#+INCLUDE: \"%s/examples/include2.org\" src emacs-lisp" org-test-dir) + (format "#+INCLUDE: \"%s/examples/include2.org\" src emacs-lisp" + org-test-dir) (org-export-expand-include-keyword) (buffer-string)) (org-test-with-temp-text - (format "#+INCLUDE: \"%s/examples/include2.org\" src emacs-lisp :minlevel 1" org-test-dir) + (format + "#+INCLUDE: \"%s/examples/include2.org\" src emacs-lisp :minlevel 1" + org-test-dir) (org-export-expand-include-keyword) (buffer-string)))) ;; INCLUDE assigns the relative :minlevel conditional on narrowing. (should (org-test-with-temp-text-in-file - (format "* h1\n#+INCLUDE: \"%s/examples/include.org::#ah\"" org-test-dir) + (format "* h1\n#+INCLUDE: \"%s/examples/include.org::#ah\"" + org-test-dir) (narrow-to-region (point) (point-max)) (org-export-expand-include-keyword) (eq 1 (org-current-level)))) ;; If :minlevel is present do not alter it. (should (org-test-with-temp-text - (format "* h1\n#+INCLUDE: \"%s/examples/include.org::#ah\" :minlevel 3" org-test-dir) + (format + "* h1\n#+INCLUDE: \"%s/examples/include.org::#ah\" :minlevel 3" + org-test-dir) (narrow-to-region (point) (point-max)) (org-export-expand-include-keyword) (eq 3 (org-current-level))))) (ert-deftest test-org-export/expand-macro () "Test macro expansion in an Org buffer." + (require 'ox-org) ;; Standard macro expansion. (should (equal "#+MACRO: macro1 value\nvalue\n" @@ -1873,7 +1886,7 @@ Para2" ;; Test nested footnotes order. (should (equal - '((1 . "fn:1") (2 . "fn:2") (3 . "fn:3") (3 . "fn:3") (4)) + '((1 . "1") (2 . "2") (3 . "3") (3 . "3") (4)) (org-test-with-parsed-data "Text[fn:1:A[fn:2]] [fn:3].\n\n[fn:2] B [fn:3] [fn::D].\n\n[fn:3] C." (org-element-map tree 'footnote-reference @@ -1904,7 +1917,7 @@ Para2" ;; then footnote definitions. (should (equal - '(("fn:1" . 1) ("fn:2" . 2) ("fn:3" . 3) ("fn:3" . 3)) + '(("1" . 1) ("2" . 2) ("3" . 3) ("3" . 3)) (org-test-with-parsed-data "Text[fn:1][fn:2][fn:3]\n\n[fn:1] Def[fn:3]\n[fn:2] Def\n[fn:3] Def" (org-element-map tree 'footnote-reference @@ -1914,7 +1927,7 @@ Para2" info)))) (should (equal - '(("fn:1" . 1) ("fn:2" . 3) ("fn:3" . 2) ("fn:3" . 2)) + '(("1" . 1) ("2" . 3) ("3" . 2) ("3" . 2)) (org-test-with-parsed-data "Text[fn:1][fn:2][fn:3]\n\n[fn:1] Def[fn:3]\n[fn:2] Def\n[fn:3] Def" (org-element-map tree 'footnote-reference @@ -1936,7 +1949,7 @@ Para2" ;; Limit number to provided DATA, when non-nil. (should (equal - '((1 "fn:2")) + '((1 "2")) (org-test-with-parsed-data "Text[fn:1]\n* H\nText[fn:2]\n\n[fn:1] D1\n[fn:2] D2" (mapcar #'butlast @@ -1944,14 +1957,14 @@ Para2" info (org-element-map tree 'headline #'identity info t)))))) (should (equal - '((1 "fn:1") (2 "fn:2")) + '((1 "1") (2 "2")) (org-test-with-parsed-data "Text[fn:1]\n* H\nText[fn:2]\n\n[fn:1] D1\n[fn:2] D2" (mapcar #'butlast (org-export-collect-footnote-definitions info))))) ;; With optional argument BODY-FIRST, first check body, then ;; footnote definitions. (should - (equal '("fn:1" "fn:3" "fn:2" nil) + (equal '("1" "3" "2" nil) (org-test-with-parsed-data "Text[fn:1:A[fn:2]] [fn:3]. \[fn:2] B [fn:3] [fn::D]. @@ -1960,7 +1973,7 @@ Para2" (mapcar (lambda (e) (nth 1 e)) (org-export-collect-footnote-definitions info nil t))))) (should-not - (equal '("fn:1" "fn:3" "fn:2" nil) + (equal '("1" "3" "2" nil) (org-test-with-parsed-data "Text[fn:1:A[fn:2]] [fn:3]. \[fn:2] B [fn:3] [fn::D]. @@ -1976,9 +1989,9 @@ Para2" ;; Read every type of footnote. (should (equal - '((1 . "A\n") (2 . "B") (3 . "C") (4 . "D")) + '((1 . "A\n") (2 . "C") (3 . "D")) (org-test-with-parsed-data - "Text[fn:1] [1] [fn:label:C] [fn::D]\n\n[fn:1] A\n\n[1] B" + "Text[fn:1] [fn:label:C] [fn::D]\n\n[fn:1] A\n" (org-element-map tree 'footnote-reference (lambda (ref) (let ((def (org-export-get-footnote-definition ref info))) @@ -1990,7 +2003,7 @@ Para2" ;; Test nested footnote in invisible definitions. (should (= 2 - (org-test-with-temp-text "Text[1]\n\n[1] B [2]\n\n[2] C." + (org-test-with-temp-text "Text[fn:1]\n\n[fn:1] B [fn:2]\n\n[fn:2] C." (narrow-to-region (point) (line-end-position)) (catch 'exit (org-export-as @@ -2497,7 +2510,10 @@ Para2" (org-export-resolve-fuzzy-link link info) info)) info t)))) ;; Link to a target in a footnote should return footnote's number. (org-test-with-parsed-data " -Paragraph[1][2][fn:lbl3:C<>][[test]][[target]]\n[1] A\n\n[2] <>B" +Paragraph[fn:1][fn:2][fn:lbl3:C<>][[test]][[target]] +\[fn:1] A + +\[fn:2] <>B" (should (equal '(2 3) (org-element-map tree 'link