diff --git a/contrib/lisp/org-e-latex.el b/contrib/lisp/org-e-latex.el index 67e9197ed..043b75cde 100644 --- a/contrib/lisp/org-e-latex.el +++ b/contrib/lisp/org-e-latex.el @@ -1598,6 +1598,10 @@ INFO is a plist holding contextual information. See (org-export-resolve-fuzzy-link link info) (org-export-resolve-id-link link info)))) (case (org-element-type destination) + ;; Id link points to an external file. + (plain-text + (if desc (format "\\href{file://%s}{%s}" destination desc) + (format "\\url{file://%s}" destination))) ;; Fuzzy link points nowhere. ('nil (format org-e-latex-link-with-unknown-path-format diff --git a/contrib/lisp/org-export.el b/contrib/lisp/org-export.el index b9294e59a..f81a9f665 100644 --- a/contrib/lisp/org-export.el +++ b/contrib/lisp/org-export.el @@ -1065,7 +1065,7 @@ inferior to file-local settings." (and buffer-file-name (org-remove-double-quotes buffer-file-name))) ;; ... and from subtree, when appropriate. (and subtreep (org-export-get-subtree-options)) - ;; Also install back-end symbol and its translation table. + ;; Eventually install back-end symbol and its translation table. `(:back-end ,backend :translate-alist @@ -1266,6 +1266,40 @@ Assume buffer is in Org mode. Narrowing, if any, is ignored." (file-name-sans-extension (file-name-nondirectory visited-file))) (buffer-name (buffer-base-buffer))) + :footnote-definition-alist + ;; Footnotes definitions must be collected in the original + ;; buffer, as there's no insurance that they will still be in the + ;; parse tree, due to possible narrowing. + (let (alist) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward org-footnote-definition-re nil t) + (let ((def (org-footnote-at-definition-p))) + (when def + (org-skip-whitespace) + (push (cons (car def) + (save-restriction + (narrow-to-region (point) (nth 2 def)) + ;; Like `org-element-parse-buffer', but + ;; makes sure the definition doesn't start + ;; with a section element. + (nconc + (list 'org-data nil) + (org-element-parse-elements + (point-min) (point-max) nil nil nil nil nil)))) + alist)))) + alist)) + :id-alist + ;; Collect id references. + (let (alist) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward + "\\[\\[id:\\(\\S-+?\\)\\]\\(?:\\[.*?\\]\\)?\\]" nil t) + (let* ((id (org-match-string-no-properties 1)) + (file (org-id-find-id-file id))) + (when file (push (cons id (file-relative-name file)) alist))))) + alist) :macro-modification-time (and visited-file (file-exists-p visited-file) @@ -1301,47 +1335,6 @@ process." ;; Return value. plist)) -(defun org-export-store-footnote-definitions (info) - "Collect and store footnote definitions from current buffer in INFO. - -INFO is a plist containing export options. - -Footnotes definitions are stored as a alist whose CAR is -footnote's label, as a string, and CDR the contents, as a parse -tree. This alist will be consed to the value of -`:footnote-definition-alist' in INFO, if any. - -The new plist is returned; use - - \(setq info (org-export-store-footnote-definitions info)) - -to be sure to use the new value. INFO is modified by side -effects." - ;; Footnotes definitions must be collected in the original buffer, - ;; as there's no insurance that they will still be in the parse - ;; tree, due to some narrowing. - (plist-put - info :footnote-definition-alist - (let ((alist (plist-get info :footnote-definition-alist))) - (org-with-wide-buffer - (goto-char (point-min)) - (while (re-search-forward org-footnote-definition-re nil t) - (let ((def (org-footnote-at-definition-p))) - (when def - (org-skip-whitespace) - (push (cons (car def) - (save-restriction - (narrow-to-region (point) (nth 2 def)) - ;; Like `org-element-parse-buffer', but - ;; makes sure the definition doesn't start - ;; with a section element. - (nconc - (list 'org-data nil) - (org-element-parse-elements - (point-min) (point-max) nil nil nil nil nil)))) - alist)))) - alist)))) - (defvar org-export-allow-BIND-local nil) (defun org-export-confirm-letbind () "Can we use #+BIND values during export? @@ -1405,7 +1398,9 @@ Following tree properties are set or updated: `:ignore-list' List of elements that should be ignored during export. -`:target-list' List of all targets in the parse tree." +`:target-list' List of all targets in the parse tree. + +Return updated plist." ;; Install the parse tree in the communication channel, in order to ;; use `org-export-get-genealogy' and al. (setq info (plist-put info :parse-tree data)) @@ -2267,8 +2262,7 @@ Return code as a string." ;; they might not be accessible anymore in a narrowed parse ;; tree. Also install user's and developer's filters. (let ((info (org-export-install-filters - (org-export-store-footnote-definitions - (org-export-get-environment backend subtreep ext-plist)))) + (org-export-get-environment backend subtreep ext-plist))) ;; 2. Get parse tree. Buffer isn't parsed directly. ;; Instead, a temporary copy is created, where include ;; keywords are expanded and code blocks are evaluated. @@ -3007,16 +3001,20 @@ Assume LINK type is \"fuzzy\"." INFO is a plist used as a communication channel. -Return value can be an headline element or nil. Assume LINK type -is either \"id\" or \"custom-id\"." +Return value can be the headline element matched in current parse +tree, a file name or nil. Assume LINK type is either \"id\" or +\"custom-id\"." (let ((id (org-element-property :path link))) - (org-element-map - (plist-get info :parse-tree) 'headline - (lambda (headline) - (when (or (string= (org-element-property :id headline) id) - (string= (org-element-property :custom-id headline) id)) - headline)) - info 'first-match))) + ;; First check if id is within the current parse tree. + (or (org-element-map + (plist-get info :parse-tree) 'headline + (lambda (headline) + (when (or (string= (org-element-property :id headline) id) + (string= (org-element-property :custom-id headline) id)) + headline)) + info 'first-match) + ;; Otherwise, look for external files. + (cdr (assoc id (plist-get info :id-alist)))))) (defun org-export-resolve-radio-link (link info) "Return radio-target object referenced as LINK destination. diff --git a/testing/lisp/test-org-export.el b/testing/lisp/test-org-export.el index 9aedbbab2..8ed47fb90 100644 --- a/testing/lisp/test-org-export.el +++ b/testing/lisp/test-org-export.el @@ -375,62 +375,56 @@ body\n"))) (ert-deftest test-org-export/footnotes () "Test footnotes specifications." - (let ((org-footnote-section nil)) + (let ((org-footnote-section nil) + (org-export-with-footnotes t)) ;; 1. Read every type of footnote. - (org-test-with-temp-text + (org-test-with-parsed-data "Text[fn:1] [1] [fn:label:C] [fn::D]\n\n[fn:1] A\n\n[1] B" - (let* ((tree (org-element-parse-buffer)) - (info (org-export-store-footnote-definitions - `(:parse-tree ,tree :with-footnotes t)))) - (should - (equal - '((1 . "A") (2 . "B") (3 . "C") (4 . "D")) - (org-element-map - tree 'footnote-reference - (lambda (ref) - (let ((def (org-export-get-footnote-definition ref info))) - (cons (org-export-get-footnote-number ref info) - (if (eq (org-element-property :type ref) 'inline) (car def) - (car (org-element-contents - (car (org-element-contents def)))))))) - info))))) + (should + (equal + '((1 . "A") (2 . "B") (3 . "C") (4 . "D")) + (org-element-map + tree 'footnote-reference + (lambda (ref) + (let ((def (org-export-get-footnote-definition ref info))) + (cons (org-export-get-footnote-number ref info) + (if (eq (org-element-property :type ref) 'inline) (car def) + (car (org-element-contents + (car (org-element-contents def)))))))) + info)))) ;; 2. Test nested footnotes order. - (org-test-with-temp-text + (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." - (let* ((tree (org-element-parse-buffer)) - (info (org-export-store-footnote-definitions - `(:parse-tree ,tree :with-footnotes t)))) - (should - (equal - '((1 . "fn:1") (2 . "fn:2") (3 . "fn:3") (4)) - (org-element-map - tree 'footnote-reference - (lambda (ref) - (when (org-export-footnote-first-reference-p ref info) - (cons (org-export-get-footnote-number ref info) - (org-element-property :label ref)))) - info))))) + (should + (equal + '((1 . "fn:1") (2 . "fn:2") (3 . "fn:3") (4)) + (org-element-map + tree 'footnote-reference + (lambda (ref) + (when (org-export-footnote-first-reference-p ref info) + (cons (org-export-get-footnote-number ref info) + (org-element-property :label ref)))) + info)))) ;; 3. Test nested footnote in invisible definitions. (org-test-with-temp-text "Text[1]\n\n[1] B [2]\n\n[2] C." ;; Hide definitions. (narrow-to-region (point) (point-at-eol)) (let* ((tree (org-element-parse-buffer)) - (info (org-export-store-footnote-definitions - `(:parse-tree ,tree :with-footnotes t)))) + (info (org-combine-plists + `(:parse-tree ,tree) + (org-export-collect-tree-properties + tree (org-export-get-environment))))) ;; Both footnotes should be seen. (should (= (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]. + (org-test-with-parsed-data "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-export-store-footnote-definitions - `(:parse-tree ,tree :with-footnotes t)))) - (should (= (length (org-export-collect-footnote-definitions tree info)) - 4)))) + (should (= (length (org-export-collect-footnote-definitions tree info)) + 4))) ;; 5. Test export of footnotes defined outside parsing scope. (org-test-with-temp-text "[fn:1] Out of scope * Title @@ -484,8 +478,73 @@ Paragraph[fn:1]" ;;; Links -(ert-deftest test-org-export/fuzzy-links () - "Test fuzzy link export specifications." +(ert-deftest test-org-export/resolve-coderef () + "Test `org-export-resolve-coderef' specifications." + (let ((org-coderef-label-format "(ref:%s)")) + ;; 1. A link to a "-n -k -r" block returns line number. + (org-test-with-parsed-data + "#+BEGIN_EXAMPLE -n -k -r\nText (ref:coderef)\n#+END_EXAMPLE" + (should (= (org-export-resolve-coderef "coderef" info) 1))) + (org-test-with-parsed-data + "#+BEGIN_SRC emacs-lisp -n -k -r\n(+ 1 1) (ref:coderef)\n#+END_SRC" + (should (= (org-export-resolve-coderef "coderef" info) 1))) + ;; 2. A link to a "-n -r" block returns line number. + (org-test-with-parsed-data + "#+BEGIN_EXAMPLE -n -r\nText (ref:coderef)\n#+END_EXAMPLE" + (should (= (org-export-resolve-coderef "coderef" info) 1))) + (org-test-with-parsed-data + "#+BEGIN_SRC emacs-lisp -n -r\n(+ 1 1) (ref:coderef)\n#+END_SRC" + (should (= (org-export-resolve-coderef "coderef" info) 1))) + ;; 3. A link to a "-n" block returns coderef. + (org-test-with-parsed-data + "#+BEGIN_SRC emacs-lisp -n\n(+ 1 1) (ref:coderef)\n#+END_SRC" + (should (equal (org-export-resolve-coderef "coderef" info) "coderef"))) + (org-test-with-parsed-data + "#+BEGIN_EXAMPLE -n\nText (ref:coderef)\n#+END_EXAMPLE" + (should (equal (org-export-resolve-coderef "coderef" info) "coderef"))) + ;; 4. A link to a "-r" block returns line number. + (org-test-with-parsed-data + "#+BEGIN_SRC emacs-lisp -r\n(+ 1 1) (ref:coderef)\n#+END_SRC" + (should (= (org-export-resolve-coderef "coderef" info) 1))) + (org-test-with-parsed-data + "#+BEGIN_EXAMPLE -r\nText (ref:coderef)\n#+END_EXAMPLE" + (should (= (org-export-resolve-coderef "coderef" info) 1))) + ;; 5. A link to a block without a switch returns coderef. + (org-test-with-parsed-data + "#+BEGIN_SRC emacs-lisp\n(+ 1 1) (ref:coderef)\n#+END_SRC" + (should (equal (org-export-resolve-coderef "coderef" info) "coderef"))) + (org-test-with-parsed-data + "#+BEGIN_EXAMPLE\nText (ref:coderef)\n#+END_EXAMPLE" + (should (equal (org-export-resolve-coderef "coderef" info) "coderef"))) + ;; 6. Correctly handle continued line numbers. A "+n" switch + ;; should resume numbering from previous block with numbered + ;; lines, ignoring blocks not numbering lines in the process. + ;; A "-n" switch resets count. + (org-test-with-parsed-data " +#+BEGIN_EXAMPLE -n +Text. +#+END_EXAMPLE + +#+BEGIN_SRC emacs-lisp +\(- 1 1) +#+END_SRC + +#+BEGIN_SRC emacs-lisp +n -r +\(+ 1 1) (ref:addition) +#+END_SRC + +#+BEGIN_EXAMPLE -n -r +Another text. (ref:text) +#+END_EXAMPLE" + (should (= (org-export-resolve-coderef "addition" info) 2)) + (should (= (org-export-resolve-coderef "text" info) 1))) + ;; 7. Recognize coderef with user-specified syntax. + (org-test-with-parsed-data + "#+BEGIN_EXAMPLE -l \"[ref:%s]\"\nText. [ref:text]\n#+END_EXAMPLE" + (should (equal (org-export-resolve-coderef "text" info) "text"))))) + +(ert-deftest test-org-export/resolve-fuzzy-link () + "Test `org-export-resolve-fuzzy-link' specifications." ;; 1. Links to invisible (keyword) targets should be ignored. (org-test-with-parsed-data "Paragraph.\n#+TARGET: Test\n[[Test]]" @@ -551,98 +610,44 @@ Paragraph[1][2][fn:lbl3:C<>][[test]][[target]]\n[1] A\n\n[2] <>B" (org-export-get-ordinal (org-export-resolve-fuzzy-link link info) info)) info t))))) -(ert-deftest test-org-export/resolve-coderef () - "Test `org-export-resolve-coderef' specifications." - (let ((org-coderef-label-format "(ref:%s)")) - ;; 1. A link to a "-n -k -r" block returns line number. - (org-test-with-temp-text - "#+BEGIN_EXAMPLE -n -k -r\nText (ref:coderef)\n#+END_EXAMPLE" - (let ((tree (org-element-parse-buffer))) - (should - (= (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) 1)))) - (org-test-with-temp-text - "#+BEGIN_SRC emacs-lisp -n -k -r\n(+ 1 1) (ref:coderef)\n#+END_SRC" - (let ((tree (org-element-parse-buffer))) - (should - (= (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) 1)))) - ;; 2. A link to a "-n -r" block returns line number. - (org-test-with-temp-text - "#+BEGIN_EXAMPLE -n -r\nText (ref:coderef)\n#+END_EXAMPLE" - (let ((tree (org-element-parse-buffer))) - (should - (= (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) 1)))) - (org-test-with-temp-text - "#+BEGIN_SRC emacs-lisp -n -r\n(+ 1 1) (ref:coderef)\n#+END_SRC" - (let ((tree (org-element-parse-buffer))) - (should - (= (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) 1)))) - ;; 3. A link to a "-n" block returns coderef. - (org-test-with-temp-text - "#+BEGIN_SRC emacs-lisp -n\n(+ 1 1) (ref:coderef)\n#+END_SRC" - (let ((tree (org-element-parse-buffer))) - (should - (equal (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) - "coderef")))) - (org-test-with-temp-text - "#+BEGIN_EXAMPLE -n\nText (ref:coderef)\n#+END_EXAMPLE" - (let ((tree (org-element-parse-buffer))) - (should - (equal (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) - "coderef")))) - ;; 4. A link to a "-r" block returns line number. - (org-test-with-temp-text - "#+BEGIN_SRC emacs-lisp -r\n(+ 1 1) (ref:coderef)\n#+END_SRC" - (let ((tree (org-element-parse-buffer))) - (should - (= (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) 1)))) - (org-test-with-temp-text - "#+BEGIN_EXAMPLE -r\nText (ref:coderef)\n#+END_EXAMPLE" - (let ((tree (org-element-parse-buffer))) - (should - (= (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) 1)))) - ;; 5. A link to a block without a switch returns coderef. - (org-test-with-temp-text - "#+BEGIN_SRC emacs-lisp\n(+ 1 1) (ref:coderef)\n#+END_SRC" - (let ((tree (org-element-parse-buffer))) - (should - (equal (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) - "coderef")))) - (org-test-with-temp-text - "#+BEGIN_EXAMPLE\nText (ref:coderef)\n#+END_EXAMPLE" - (let ((tree (org-element-parse-buffer))) - (should - (equal (org-export-resolve-coderef "coderef" `(:parse-tree ,tree)) - "coderef")))) - ;; 6. Correctly handle continued line numbers. A "+n" switch - ;; should resume numbering from previous block with numbered - ;; lines, ignoring blocks not numbering lines in the process. - ;; A "-n" switch resets count. - (org-test-with-temp-text " -#+BEGIN_EXAMPLE -n -Text. -#+END_EXAMPLE - -#+BEGIN_SRC emacs-lisp -\(- 1 1) -#+END_SRC - -#+BEGIN_SRC emacs-lisp +n -r -\(+ 1 1) (ref:addition) -#+END_SRC - -#+BEGIN_EXAMPLE -n -r -Another text. (ref:text) -#+END_EXAMPLE" - (let* ((tree (org-element-parse-buffer)) - (info `(:parse-tree ,tree))) - (should (= (org-export-resolve-coderef "addition" info) 2)) - (should (= (org-export-resolve-coderef "text" info) 1)))) - ;; 7. Recognize coderef with user-specified syntax. - (org-test-with-temp-text - "#+BEGIN_EXAMPLE -l \"[ref:%s]\"\nText. [ref:text]\n#+END_EXAMPLE" - (let ((tree (org-element-parse-buffer))) - (should (equal (org-export-resolve-coderef "text" `(:parse-tree ,tree)) - "text")))))) +(ert-deftest test-org-export/resolve-id-link () + "Test `org-export-resolve-id-link' specifications." + ;; 1. Regular test for custom-id link. + (org-test-with-parsed-data "* Headline1 +:PROPERTIES: +:CUSTOM-ID: test +:END: +* Headline 2 +\[[#test]]" + (should + (org-export-resolve-id-link + (org-element-map tree 'link 'identity info t) info))) + ;; 2. Failing test for custom-id link. + (org-test-with-parsed-data "* Headline1 +:PROPERTIES: +:CUSTOM-ID: test +:END: +* Headline 2 +\[[#no-match]]" + (should-not + (org-export-resolve-id-link + (org-element-map tree 'link 'identity info t) info))) + ;; 3. Test for internal id target. + (org-test-with-parsed-data "* Headline1 +:PROPERTIES: +:ID: aaaa +:END: +* Headline 2 +\[[id:aaaa]]" + (should + (org-export-resolve-id-link + (org-element-map tree 'link 'identity info t) info))) + ;; 4. Test for external id target. + (org-test-with-parsed-data "[[id:aaaa]]" + (should + (org-export-resolve-id-link + (org-element-map tree 'link 'identity info t) + (org-combine-plists info '(:id-alist (("aaaa" . "external-file")))))))) (ert-deftest test-org-export/resolve-radio-link () "Test `org-export-resolve-radio-link' specifications."