org-export: Avoid duplicates in `org-export-collect-footnote-definitions'

* contrib/lisp/org-element.el (org-element-map): New optional argument
  to avoid recursion into certain recursive types.
* contrib/lisp/org-export.el (org-export-footnote-first-reference-p,
  org-export-get-footnote-number,
  org-export-collect-footnote-definitions): Use new argument from
  `org-element-map'.
* testing/lisp/test-org-export.el: Add test.

The new argument allows to force entering footnotes definitions at
a certain time (when their first reference is found) but not a second
time when they are encountered in the parse tree.

Thanks to Jambunathan for reporting this.
This commit is contained in:
Nicolas Goaziou 2012-03-04 11:57:16 +01:00
parent 6fd14fe683
commit bf609d8844
3 changed files with 44 additions and 19 deletions

View File

@ -2935,7 +2935,7 @@ the current buffer."
(insert string) (insert string)
(org-element-parse-objects (point-min) (point-max) nil restriction))) (org-element-parse-objects (point-min) (point-max) nil restriction)))
(defun org-element-map (data types fun &optional info first-match) (defun org-element-map (data types fun &optional info first-match no-recursion)
"Map a function on selected elements or objects. "Map a function on selected elements or objects.
DATA is the parsed tree, as returned by, i.e, DATA is the parsed tree, as returned by, i.e,
@ -2951,9 +2951,15 @@ not exportable according to that property list will be skipped.
When optional argument FIRST-MATCH is non-nil, stop at the first When optional argument FIRST-MATCH is non-nil, stop at the first
match for which FUN doesn't return nil, and return that value. match for which FUN doesn't return nil, and return that value.
Nil values returned from FUN are ignored in the result." Optional argument NO-RECURSION is a symbol or a list of symbols
;; Ensure TYPES is a list, even of one element. representing elements or objects types. `org-element-map' won't
enter any recursive element or object whose type belongs to that
list. Though, FUN can still be applied on them.
Nil values returned from FUN do not appear in the results."
;; Ensure TYPES and NO-RECURSION are a list, even of one element.
(unless (listp types) (setq types (list types))) (unless (listp types) (setq types (list types)))
(unless (listp no-recursion) (setq no-recursion (list no-recursion)))
;; Recursion depth is determined by --CATEGORY. ;; Recursion depth is determined by --CATEGORY.
(let* ((--category (let* ((--category
(cond (cond
@ -3008,12 +3014,13 @@ Nil values returned from FUN are ignored in the result."
--blob)))) --blob))))
;; Now determine if a recursion into --BLOB is ;; Now determine if a recursion into --BLOB is
;; possible. If so, do it. ;; possible. If so, do it.
(when (or (memq --type org-element-recursive-objects) (unless (memq --type no-recursion)
(when (or (and (memq --type org-element-greater-elements)
(not (eq --category 'greater-elements)))
(and (memq --type org-element-all-elements) (and (memq --type org-element-all-elements)
(not (eq --category 'elements))) (not (eq --category 'elements)))
(and (memq --type org-element-greater-elements) (memq --type org-element-recursive-objects))
(not (eq --category 'greater-elements)))) (funcall --walk-tree --blob))))))
(funcall --walk-tree --blob)))))
(org-element-contents --data)))))) (org-element-contents --data))))))
(catch 'first-match (catch 'first-match
(funcall --walk-tree data) (funcall --walk-tree data)

View File

@ -2459,9 +2459,9 @@ DATA is the parse tree from which definitions are collected.
INFO is the plist used as a communication channel. INFO is the plist used as a communication channel.
Definitions are sorted by order of references. They either Definitions are sorted by order of references. They either
appear as Org data \(transcoded with `org-export-data'\) or as appear as Org data (transcoded with `org-export-data') or as
a secondary string for inlined footnotes \(transcoded with a secondary string for inlined footnotes (transcoded with
`org-export-secondary-string'\). Unreferenced definitions are `org-export-secondary-string'). Unreferenced definitions are
ignored." ignored."
(let (num-alist (let (num-alist
(collect-fn (collect-fn
@ -2481,10 +2481,11 @@ ignored."
;; Also search in definition for nested footnotes. ;; Also search in definition for nested footnotes.
(when (eq (org-element-property :type fn) 'standard) (when (eq (org-element-property :type fn) 'standard)
(funcall collect-fn def))))) (funcall collect-fn def)))))
info) ;; Don't enter footnote definitions since it will happen
;; Return final value. ;; when their first reference is found.
(reverse num-alist))))) info nil 'footnote-definition)))))
(funcall collect-fn (plist-get info :parse-tree)))) (funcall collect-fn (plist-get info :parse-tree))
(reverse num-alist)))
(defun org-export-footnote-first-reference-p (footnote-reference info) (defun org-export-footnote-first-reference-p (footnote-reference info)
"Non-nil when a footnote reference is the first one for its label. "Non-nil when a footnote reference is the first one for its label.
@ -2512,7 +2513,9 @@ INFO is the plist used as a communication channel."
((eq (org-element-property :type fn) 'standard) ((eq (org-element-property :type fn) 'standard)
(funcall search-refs (funcall search-refs
(org-export-get-footnote-definition fn info))))) (org-export-get-footnote-definition fn info)))))
info 'first-match))))) ;; Don't enter footnote definitions since it will
;; happen when their first reference is found.
info 'first-match 'footnote-definition)))))
(equal (catch 'exit (funcall search-refs (plist-get info :parse-tree))) (equal (catch 'exit (funcall search-refs (plist-get info :parse-tree)))
footnote-reference))))) footnote-reference)))))
@ -2564,7 +2567,9 @@ INFO is the plist used as a communication channel."
(funcall search-ref (funcall search-ref
(org-export-get-footnote-definition fn info)) (org-export-get-footnote-definition fn info))
nil)))) nil))))
info 'first-match))))) ;; Don't enter footnote definitions since it will happen
;; when their first reference is found.
info 'first-match 'footnote-definition)))))
(catch 'exit (funcall search-ref (plist-get info :parse-tree))))) (catch 'exit (funcall search-ref (plist-get info :parse-tree)))))

View File

@ -363,7 +363,20 @@ body\n")))
info (org-export-collect-tree-properties tree info 'test))) info (org-export-collect-tree-properties tree info 'test)))
;; Both footnotes should be seen. ;; Both footnotes should be seen.
(should (should
(= (length (org-export-collect-footnote-definitions tree info)) 2)))))) (= (length (org-export-collect-footnote-definitions tree info)) 2))))
;; 4. Test footnotes definitions collection.
(org-test-with-temp-text "Text[fn:1:A[fn:2]] [fn:3].
\[fn:2] B [fn:3] [fn::D].
\[fn:3] C."
(let ((tree (org-element-parse-buffer))
(info (org-combine-plists
(org-export-initial-options) '(:with-footnotes t))))
(setq info (org-combine-plists
info (org-export-collect-tree-properties tree info 'test)))
(should (= (length (org-export-collect-footnote-definitions tree info))
4))))))
(ert-deftest test-org-export/fuzzy-links () (ert-deftest test-org-export/fuzzy-links ()
"Test fuzz link export specifications." "Test fuzz link export specifications."