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)
(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.
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
match for which FUN doesn't return nil, and return that value.
Nil values returned from FUN are ignored in the result."
;; Ensure TYPES is a list, even of one element.
Optional argument NO-RECURSION is a symbol or a list of symbols
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 no-recursion) (setq no-recursion (list no-recursion)))
;; Recursion depth is determined by --CATEGORY.
(let* ((--category
(cond
@ -3008,12 +3014,13 @@ Nil values returned from FUN are ignored in the result."
--blob))))
;; Now determine if a recursion into --BLOB is
;; possible. If so, do it.
(when (or (memq --type org-element-recursive-objects)
(and (memq --type org-element-all-elements)
(not (eq --category 'elements)))
(and (memq --type org-element-greater-elements)
(not (eq --category 'greater-elements))))
(funcall --walk-tree --blob)))))
(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)
(not (eq --category 'elements)))
(memq --type org-element-recursive-objects))
(funcall --walk-tree --blob))))))
(org-element-contents --data))))))
(catch 'first-match
(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.
Definitions are sorted by order of references. They either
appear as Org data \(transcoded with `org-export-data'\) or as
a secondary string for inlined footnotes \(transcoded with
`org-export-secondary-string'\). Unreferenced definitions are
appear as Org data (transcoded with `org-export-data') or as
a secondary string for inlined footnotes (transcoded with
`org-export-secondary-string'). Unreferenced definitions are
ignored."
(let (num-alist
(collect-fn
@ -2481,10 +2481,11 @@ ignored."
;; Also search in definition for nested footnotes.
(when (eq (org-element-property :type fn) 'standard)
(funcall collect-fn def)))))
info)
;; Return final value.
(reverse num-alist)))))
(funcall collect-fn (plist-get info :parse-tree))))
;; Don't enter footnote definitions since it will happen
;; when their first reference is found.
info nil 'footnote-definition)))))
(funcall collect-fn (plist-get info :parse-tree))
(reverse num-alist)))
(defun org-export-footnote-first-reference-p (footnote-reference info)
"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)
(funcall search-refs
(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)))
footnote-reference)))))
@ -2564,7 +2567,9 @@ INFO is the plist used as a communication channel."
(funcall search-ref
(org-export-get-footnote-definition fn info))
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)))))

View File

@ -363,7 +363,20 @@ body\n")))
info (org-export-collect-tree-properties tree info 'test)))
;; Both footnotes should be seen.
(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 ()
"Test fuzz link export specifications."