From fa145361ec10a503fa004c7073edc40b27e11268 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Sat, 21 Feb 2015 09:34:15 +0100 Subject: [PATCH] ox: Allow to narrow scope in footnotes API * lisp/ox.el (org-export--footnote-reference-map, org-export-collect-footnote-definitions, org-export-footnote-first-reference-p, org-export-get-footnote-number): Allow to specify scope, through a new optional argument. * lisp/ox-odt.el (org-odt-footnote-reference): Apply API change. * testing/lisp/test-ox.el (test-org-export/footnote-first-reference-p, test-org-export/get-footnote-number, test-org-export/collect-footnote-definitions): Update tests. --- lisp/ox-odt.el | 5 +-- lisp/ox.el | 30 +++++++++++------ testing/lisp/test-ox.el | 72 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 88 insertions(+), 19 deletions(-) diff --git a/lisp/ox-odt.el b/lisp/ox-odt.el index 4a94de11f..f96027104 100644 --- a/lisp/ox-odt.el +++ b/lisp/ox-odt.el @@ -1740,9 +1740,10 @@ CONTENTS is nil. INFO is a plist holding contextual information." (format "%s" "OrgSuperscript" ","))) ;; Transcode footnote reference. - (let ((n (org-export-get-footnote-number footnote-reference info t))) + (let ((n (org-export-get-footnote-number footnote-reference info nil t))) (cond - ((not (org-export-footnote-first-reference-p footnote-reference info t)) + ((not + (org-export-footnote-first-reference-p footnote-reference info nil t)) (funcall --format-footnote-reference n)) ;; Inline definitions are secondary strings. ;; Non-inline footnotes definitions are full Org data. diff --git a/lisp/ox.el b/lisp/ox.el index f2f220b63..ee7e1972a 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -3556,8 +3556,9 @@ definition can be found, raise an error." (org-element-contents footnote-reference)) (error "Definition not found for footnote %s" label)))) -(defun org-export--footnote-reference-map (function info &optional body-first) - "Apply FUNCTION on every footnote reference in parse tree. +(defun org-export--footnote-reference-map + (function data info &optional body-first) + "Apply FUNCTION on every footnote reference in DATA. INFO is a plist containing export state. By default, as soon as a new footnote reference is encountered, FUNCTION is called onto its definition. However, if BODY-FIRST is non-nil, this step is @@ -3597,15 +3598,18 @@ delayed until the end of the process." ;; definitions of inline references. (if delayp '(footnote-definition footnote-reference) 'footnote-definition))))) - (funcall search-ref (plist-get info :parse-tree) body-first) + (funcall search-ref data body-first) (funcall search-ref (nreverse definitions) nil))) -(defun org-export-collect-footnote-definitions (info &optional body-first) +(defun org-export-collect-footnote-definitions (info &optional data body-first) "Return an alist between footnote numbers, labels and definitions. INFO is the current export state, as a plist. -Definitions are sorted by order of references. As soon as a new +Definitions are collected throughout the whole parse tree, or +DATA when non-nil. + +Sorting is done by order of references. As soon as a new reference is encountered, other references are searched within its definition. However, if BODY-FIRST is non-nil, this step is delayed after the whole tree is checked. This alters results @@ -3623,16 +3627,19 @@ for inlined footnotes. Unreferenced definitions are ignored." (incf n) (push (list n l d) alist)) (when l (push l labels)))) - info body-first) + (or data (plist-get info :parse-tree)) info body-first) (nreverse alist))) (defun org-export-footnote-first-reference-p - (footnote-reference info &optional body-first) + (footnote-reference info &optional data body-first) "Non-nil when a footnote reference is the first one for its label. FOOTNOTE-REFERENCE is the footnote reference being considered. INFO is a plist containing current export state. +Search is done throughout the whole parse tree, or DATA when +non-nil. + By default, as soon as a new footnote reference is encountered, other references are searched within its definition. However, if BODY-FIRST is non-nil, this step is delayed after the whole tree @@ -3647,14 +3654,17 @@ footnote definitions." (let ((l (org-element-property :label f))) (when (and l label (string= label l)) (throw 'exit (eq footnote-reference f))))) - info body-first))))) + (or data (plist-get info :parse-tree)) info body-first))))) -(defun org-export-get-footnote-number (footnote info &optional body-first) +(defun org-export-get-footnote-number (footnote info &optional data body-first) "Return number associated to a footnote. FOOTNOTE is either a footnote reference or a footnote definition. INFO is the plist containing export state. +Number is unique throughout the whole parse tree, or DATA, when +non-nil. + By default, as soon as a new footnote reference is encountered, counting process moves into its definition. However, if BODY-FIRST is non-nil, this step is delayed until the end of the @@ -3675,7 +3685,7 @@ process, leading to a different order when footnotes are nested." ;; wasn't encountered yet. ((not l) (incf count)) ((not (member l seen)) (push l seen) (incf count))))) - info body-first)))) + (or data (plist-get info :parse-tree)) info body-first)))) ;;;; For Headlines diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el index 5afe11d32..1ea51201a 100644 --- a/testing/lisp/test-ox.el +++ b/testing/lisp/test-ox.el @@ -1557,6 +1557,30 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote (paragraph . (lambda (p c i) c)))) nil nil nil '(:with-footnotes t)) (nreverse result))))) + ;; Limit check to DATA, when non-nil. + (should + (equal + '(nil t) + (org-test-with-parsed-data "Text[fn:1]\n* H\nText[fn:1]\n\n[fn:1] D1" + (let (result) + (org-element-map tree 'footnote-reference + (lambda (ref) + (push + (org-export-footnote-first-reference-p + ref info (org-element-map tree 'headline #'identity info t)) + result)) + info) + (nreverse result))))) + (should + (equal + '(t nil) + (org-test-with-parsed-data "Text[fn:1]\n* H\nText[fn:1]\n\n[fn:1] D1" + (let (result) + (org-element-map tree 'footnote-reference + (lambda (ref) + (push (org-export-footnote-first-reference-p ref info) result)) + info) + (nreverse result))))) ;; If optional argument BODY-FIRST is non-nil, first find footnote ;; in the main body of the document. Otherwise, enter footnote ;; definitions when they are encountered. @@ -1593,7 +1617,7 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote `(,(cons 'footnote-reference (lambda (f c i) (when (org-element-lineage f '(drawer)) - (push (org-export-footnote-first-reference-p f info t) + (push (org-export-footnote-first-reference-p f info nil t) result)) "")) (drawer . (lambda (d c i) c)) @@ -1631,8 +1655,27 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote (cons (org-export-get-footnote-number ref info) (org-element-property :label ref))) info)))) - ;; With a non-nil optional argument, first check body, then footnote - ;; definitions. + ;; Limit number to provided DATA, when non-nil. + (should + (equal + '(1) + (org-test-with-parsed-data + "Text[fn:1]\n* H\nText[fn:2]\n\n[fn:1] D1\n[fn:2] D2" + (org-element-map tree 'footnote-reference + (lambda (ref) + (org-export-get-footnote-number + ref info (org-element-map tree 'headline #'identity info t))) + info)))) + (should + (equal + '(1 2) + (org-test-with-parsed-data + "Text[fn:1]\n* H\nText[fn:2]\n\n[fn:1] D1\n[fn:2]" + (org-element-map tree 'footnote-reference + (lambda (ref) (org-export-get-footnote-number ref info)) + info)))) + ;; With a non-nil BODY-FIRST optional argument, first check body, + ;; then footnote definitions. (should (equal '(("fn:1" . 1) ("fn:2" . 2) ("fn:3" . 3) ("fn:3" . 3)) @@ -1641,7 +1684,7 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote (org-element-map tree 'footnote-reference (lambda (ref) (cons (org-element-property :label ref) - (org-export-get-footnote-number ref info t))) + (org-export-get-footnote-number ref info nil t))) info)))) (should (equal @@ -1664,8 +1707,23 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote \[fn:3] C." (length (org-export-collect-footnote-definitions info))))) - ;; With optional argument, first check body, then footnote - ;; definitions. + ;; Limit number to provided DATA, when non-nil. + (should + (equal + '((1 "fn: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 (org-element-map tree 'headline #'identity info t)))))) + (should + (equal + '((1 "fn:1") (2 "fn: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) (org-test-with-parsed-data "Text[fn:1:A[fn:2]] [fn:3]. @@ -1674,7 +1732,7 @@ Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote \[fn:3] C." (mapcar (lambda (e) (nth 1 e)) - (org-export-collect-footnote-definitions info t))))) + (org-export-collect-footnote-definitions info nil t))))) (should-not (equal '("fn:1" "fn:3" "fn:2" nil) (org-test-with-parsed-data "Text[fn:1:A[fn:2]] [fn:3].