From mboxrd@z Thu Jan 1 00:00:00 1970 From: Nicolas Goaziou Subject: Re: [dev] Implement "ref" link types Date: Mon, 27 Feb 2012 20:38:46 +0100 Message-ID: <87399wrrqx.fsf@gmail.com> References: <87vcn2vgq7.fsf@gmail.com> <8739a62upy.fsf@gmail.com> <763A8736-3DB8-492D-95CB-2E2D90982BAE@gmail.com> <87y5rx22km.fsf@gmail.com> <87ty2l17o7.fsf@gmail.com> <87aa4csr86.wl%dmaus@ictsoc.de> <87pqd81r5j.fsf@gmail.com> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([208.118.235.92]:55412) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S26Rd-0006w9-40 for emacs-orgmode@gnu.org; Mon, 27 Feb 2012 14:41:18 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1S26RZ-0003ip-Ik for emacs-orgmode@gnu.org; Mon, 27 Feb 2012 14:41:12 -0500 Received: from mail-wi0-f169.google.com ([209.85.212.169]:58623) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S26RZ-0003ie-4E for emacs-orgmode@gnu.org; Mon, 27 Feb 2012 14:41:09 -0500 Received: by wibhj13 with SMTP id hj13so4052175wib.0 for ; Mon, 27 Feb 2012 11:41:08 -0800 (PST) In-Reply-To: <87pqd81r5j.fsf@gmail.com> (Nicolas Goaziou's message of "Tue, 21 Feb 2012 10:18:00 +0100") List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: David Maus Cc: Org Mode List , Carsten Dominik --=-=-= Content-Type: text/plain Hello, Here is a new version of the patch built on top of master, along with test cases. If there is no objection, I'll push it to master in a couple of days. I really think that's a great feature to have in Org. Regards, -- Nicolas Goaziou --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-Implement-numbered-cross-references.patch Content-Description: numbered cross references >From 2fdde87bb7f1241f3d24dbd8ae030a300fe8f0fc Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Mon, 20 Feb 2012 22:24:38 +0100 Subject: [PATCH] Implement numbered cross-references * lisp/org.el (org-link-search): Search for #+name affiliated keywords and invisible targets. * contrib/lisp/org-element.el (org-element-link-parser): Remove "ref" links relative part. (org-element-target-parser): Move property name from `:raw-value' to `:value'. (org-element-recursive-objects): Remove targets from tables. Cells are not parsed unless explicitely asked by back-end developer, too late. A target wouldn't be noticed in time. One solution could be to parse every table, but that's time consumming. (org-element-object-restrictions): Target are not recursive anymore. * contrib/lisp/org-export.el (org-export-resolve-fuzzy-link): Find elements with a matching "#+name: path" affiliated keyword. (org-export-get-ordinal): Make special cases for headlines, items, footnotes definitions and references. (org-export-resolve-ref-link): Removed function. * EXPERIMENTAL/org-e-latex.el (org-e-latex-link): Handle cross-reference numbers. (org-e-latex-target): Targets have no contents. * EXPERIMENTAL/org-e-ascii.el (org-e-ascii--describe-links): Ignore fuzzy links in link description at the end of the section. (org-e-ascii-link): Handle cross-reference numbers. * testing/contrib/lisp/test-org-export.el: Add tests. * testing/lisp/test-org.el: Add tests. --- EXPERIMENTAL/org-e-ascii.el | 80 ++++++++---------- EXPERIMENTAL/org-e-latex.el | 50 +++++------ contrib/lisp/org-element.el | 20 ++--- contrib/lisp/org-export.el | 141 ++++++++++++++++++++----------- lisp/org.el | 16 ++++ testing/contrib/lisp/test-org-export.el | 91 ++++++++++++++++++++ testing/lisp/test-org.el | 41 +++++++++ 7 files changed, 299 insertions(+), 140 deletions(-) diff --git a/EXPERIMENTAL/org-e-ascii.el b/EXPERIMENTAL/org-e-ascii.el index 0eb547b..c9cca4a 100644 --- a/EXPERIMENTAL/org-e-ascii.el +++ b/EXPERIMENTAL/org-e-ascii.el @@ -825,28 +825,24 @@ channel." (if (not desc) (org-element-property :raw-link link) (org-export-secondary-string desc 'e-ascii info))))) (cond - ;; Coderefs, radio links and ref links are ignored. - ((member type '("coderef" "radio" "ref")) nil) - ;; Id, custom-id and fuzzy links (with the exception of - ;; targets): Headlines refer to their numbering. - ((member type '("custom-id" "fuzzy" "id")) - (let ((destination (if (string= type "fuzzy") - (org-export-resolve-fuzzy-link link info) - (org-export-resolve-id-link link info)))) - (unless (eq (org-element-type destination) 'target) - (concat - (org-e-ascii--fill-string - (format - "[%s] %s" - anchor - (if (not destination) - (org-e-ascii--translate "Unknown reference" info) - (format - (org-e-ascii--translate "See section %s" info) - (mapconcat 'number-to-string - (org-export-get-headline-number destination info) - ".")))) - width info) "\n\n")))) + ;; Coderefs, radio links and fuzzy links are ignored. + ((member type '("coderef" "radio" "fuzzy")) nil) + ;; Id and custom-id links: Headlines refer to their numbering. + ((member type '("custom-id" "id")) + (let ((destination (org-export-resolve-id-link link info))) + (concat + (org-e-ascii--fill-string + (format + "[%s] %s" + anchor + (if (not destination) + (org-e-ascii--translate "Unknown reference" info) + (format + (org-e-ascii--translate "See section %s" info) + (mapconcat 'number-to-string + (org-export-get-headline-number destination info) + ".")))) + width info) "\n\n"))) ;; Do not add a link that cannot be resolved and doesn't have ;; any description: destination is already visible in the ;; paragraph. @@ -1390,29 +1386,23 @@ INFO is a plist holding contextual information." (org-element-property :path link) (cdr (assq 'radio-target org-element-object-restrictions))) 'e-ascii info)) - ;; Ref link: If there's no description (DESC, return link's - ;; destination sequence number among elements of same - ;; type. Otherwise, use DESC. - ((string= type "ref") - (if (org-string-nw-p desc) desc - (format "%d" - (org-export-get-ordinal - (org-export-resolve-ref-link link info) - info nil nil - (lambda (el) (or (org-element-property :caption el) - (org-element-property :name el))))))) ;; Do not apply a special syntax on fuzzy links pointing to ;; targets. - ((and (string= type "fuzzy") - (let ((path (org-element-property :path link))) - (loop for target in (plist-get info :target-list) - thereis (string= - (org-element-property :raw-value target) - path)))) - (if (org-string-nw-p desc) desc raw-link)) + ((string= type "fuzzy") + (let ((destination (org-export-resolve-fuzzy-link link info))) + ;; Ignore invisible "#+target: path". + (unless (eq (org-element-type destination) 'keyword) + (if (org-string-nw-p desc) desc + (when destination + (let ((number (org-export-get-ordinal destination info))) + (when number + (if (atom number) (number-to-string number) + (mapconcat 'number-to-string number "."))))))))) (t - (concat (format "[%s]" (if (org-string-nw-p desc) desc raw-link)) - (unless org-e-ascii-links-to-notes (format " (%s)" raw-link))))))) + (if (not (org-string-nw-p desc)) (format "[%s]" raw-link) + (concat + (format "[%s]" desc) + (unless org-e-ascii-links-to-notes (format " (%s)" raw-link)))))))) ;;;; Macro @@ -1850,11 +1840,7 @@ INFO is a plist used as a communication channel." ;;;; Target -(defun org-e-ascii-target (target contents info) - "Transcode a TARGET object from Org to ASCII. -CONTENTS is the contents of the target. INFO is a plist holding -contextual information." - contents) +;; Targets are invisible. ;;;; Time-stamp diff --git a/EXPERIMENTAL/org-e-latex.el b/EXPERIMENTAL/org-e-latex.el index 43bbde7..01b8ee2 100644 --- a/EXPERIMENTAL/org-e-latex.el +++ b/EXPERIMENTAL/org-e-latex.el @@ -1291,8 +1291,8 @@ CONTENTS is nil. INFO is a plist holding contextual information." (cond ((string= key "latex") value) ((string= key "index") (format "\\index{%s}" value)) - ((string= key "target") - (format "\\label{%s}" (org-export-solidify-link-text value))) + ;; Invisible targets. + ((string= key "target") nil) ((string= key "toc") (let ((value (downcase value))) (cond @@ -1451,32 +1451,26 @@ INFO is a plist holding contextual information. See (org-element-parse-secondary-string path (cdr (assq 'radio-target org-element-object-restrictions))) 'e-latex info))) - ;; Ref link: If no description is provided, reference label PATH - ;; and display table number. Otherwise move to label but display - ;; description instead. - ((string= type "ref") - (if (not desc) (format "\\ref{%s}" path) - (format "\\hyperref[%s]{%s}" path desc))) ;; Links pointing to an headline: Find destination and build ;; appropriate referencing command. ((member type '("custom-id" "fuzzy" "id")) (let ((destination (if (string= type "fuzzy") (org-export-resolve-fuzzy-link link info) (org-export-resolve-id-link link info)))) - ;; Fuzzy link points to a target. Do as above. (case (org-element-type destination) - (target - (format "\\hyperref[%s]{%s}" - (org-export-solidify-link-text - (org-element-property :raw-value destination)) + ;; Fuzzy link points nowhere. + ('nil + (format "\\texttt{%s}" (or desc (org-export-secondary-string (org-element-property :raw-link link) 'e-latex info)))) - ;; Fuzzy link points to an headline. If headlines are - ;; numbered and the link has no description, display - ;; headline's number. Otherwise, display description or - ;; headline's title. + ;; Fuzzy link points to an invisible target. + (keyword nil) + ;; LINK points to an headline. If headlines are numbered + ;; and the link has no description, display headline's + ;; number. Otherwise, display description or headline's + ;; title. (headline (let ((label (format "sec-%s" @@ -1491,13 +1485,11 @@ INFO is a plist holding contextual information. See (org-export-secondary-string (org-element-property :title destination) 'e-latex info)))))) - ;; Fuzzy link points nowhere. + ;; Fuzzy link points to a target. Do as above. (otherwise - (format "\\texttt{%s}" - (or desc - (org-export-secondary-string - (org-element-property :raw-link link) - 'e-latex info))))))) + (let ((path (org-export-solidify-link-text path))) + (if (not desc) (format "\\ref{%s}" path) + (format "\\hyperref[%s]{%s}" path desc))))))) ;; Coderef: replace link with the reference name or the ;; equivalent line number. ((string= type "coderef") @@ -1970,14 +1962,12 @@ CONTENTS is nil. INFO is a plist holding contextual information." ;;;; Target -(defun org-e-latex-target (target text info) +(defun org-e-latex-target (target contents info) "Transcode a TARGET object from Org to LaTeX. -TEXT is the text of the target. INFO is a plist holding -contextual information." - (format "\\label{%s}%s" - (org-export-solidify-link-text - (org-element-property :raw-value target)) - text)) +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "\\label{%s}" + (org-export-solidify-link-text (org-element-property :value target)))) ;;;; Time-stamp diff --git a/contrib/lisp/org-element.el b/contrib/lisp/org-element.el index 4e5e7fd..de5d4ea 100644 --- a/contrib/lisp/org-element.el +++ b/contrib/lisp/org-element.el @@ -1967,9 +1967,6 @@ Assume point is at the beginning of the link." ;; Explicit type (http, irc, bbdb...). See `org-link-types'. ((string-match org-link-re-with-space3 link) (setq type (match-string 1 link) path (match-string 2 link))) - ;; Ref type: PATH is the name of the target element. - ((string-match "^ref:\\(.*\\)" link) - (setq type "ref" path (org-trim (match-string 1 link)))) ;; Id type: PATH is the id. ((string-match "^id:\\([-a-f0-9]+\\)" link) (setq type "id" path (match-string 1 link))) @@ -2269,25 +2266,21 @@ CONTENTS is the contents of the object." "Parse target at point. Return a list whose car is `target' and cdr a plist with -`:begin', `:end', `:contents-begin', `:contents-end', `raw-value' -and `:post-blank' as keywords. +`:begin', `:end', `:contents-begin', `:contents-end', `value' and +`:post-blank' as keywords. Assume point is at the target." (save-excursion (looking-at org-target-regexp) (let ((begin (point)) - (contents-begin (match-beginning 1)) - (contents-end (match-end 1)) - (raw-value (org-match-string-no-properties 1)) + (value (org-match-string-no-properties 1)) (post-blank (progn (goto-char (match-end 0)) (skip-chars-forward " \t"))) (end (point))) `(target (:begin ,begin :end ,end - :contents-begin ,contents-begin - :contents-end ,contents-end - :raw-value ,raw-value + :value ,value :post-blank ,post-blank))))) (defun org-element-target-interpreter (target contents) @@ -2481,7 +2474,7 @@ regexp matching one object can also match the other object.") "Complete list of object types.") (defconst org-element-recursive-objects - '(emphasis link macro subscript superscript target radio-target) + '(emphasis link macro subscript superscript radio-target) "List of recursive object types.") (defconst org-element-non-recursive-block-alist @@ -2551,8 +2544,7 @@ This list is checked after translations have been applied. See (subscript entity export-snippet inline-babel-call inline-src-block latex-fragment sub/superscript text-markup) (superscript entity export-snippet inline-babel-call inline-src-block - latex-fragment sub/superscript text-markup) - (target entity export-snippet latex-fragment sub/superscript text-markup)) + latex-fragment sub/superscript text-markup)) "Alist of recursive objects restrictions. CAR is a recursive object type and CDR is a list of successors diff --git a/contrib/lisp/org-export.el b/contrib/lisp/org-export.el index b809758..e7cca50 100644 --- a/contrib/lisp/org-export.el +++ b/contrib/lisp/org-export.el @@ -1289,7 +1289,13 @@ Following tree properties are set: `(:parse-tree ,data :target-list - ,(org-element-map data 'target 'identity info) + ,(org-element-map + data '(keyword target) + (lambda (blob) + (when (or (eq (org-element-type blob) 'target) + (string= (upcase (org-element-property :key blob)) + "TARGET")) + blob)) info) :headline-numbering ,(org-export-collect-headline-numbering data info) :back-end ,backend) info)) @@ -2697,8 +2703,11 @@ INFO is a plist holding contextual information. Return value can be an object, an element, or nil: -- If LINK path exactly matches any target, return the target - object. +- If LINK path matches a target object (i.e. <>) or + element (i.e. \"#+target: path\"), return it. + +- If LINK path exactly matches the name affiliated keyword + \(i.e. #+name: path) of an element, return that element. - If LINK path exactly matches any headline name, return that element. If more than one headline share that name, priority @@ -2709,16 +2718,29 @@ Return value can be an object, an element, or nil: Assume LINK type is \"fuzzy\"." (let ((path (org-element-property :path link))) - ;; Link points to a target: return it. - (or (loop for target in (plist-get info :target-list) - when (string= (org-element-property :raw-value target) path) - return target) - ;; Link either points to an headline or nothing. Try to find - ;; the source, with priority given to headlines with the closest - ;; common ancestor. If such candidate is found, return its - ;; beginning position as an unique identifier, otherwise return - ;; nil. - (let ((find-headline + (cond + ;; First try to find a matching "<>" unless user specified + ;; he was looking for an headline (path starts with a * + ;; character). + ((and (not (eq (substring path 0 1) ?*)) + (loop for target in (plist-get info :target-list) + when (string= (org-element-property :value target) path) + return target))) + ;; Then try to find an element with a matching "#+name: path" + ;; affiliated keyword. + ((and (not (eq (substring path 0 1) ?*)) + (org-element-map + (plist-get info :parse-tree) org-element-all-elements + (lambda (el) + (when (string= (org-element-property :name el) path) el)) + info 'first-match))) + ;; Last case: link either points to an headline or to + ;; nothingness. Try to find the source, with priority given to + ;; headlines with the closest common ancestor. If such candidate + ;; is found, return its beginning position as an unique + ;; identifier, otherwise return nil. + (t + (let ((find-headline (function ;; Return first headline whose `:raw-value' property ;; is NAME in parse tree DATA, or nil. @@ -2741,7 +2763,7 @@ Assume LINK type is \"fuzzy\"." (when foundp (throw 'exit foundp))))) (org-export-get-genealogy link info)) nil) ;; No match with a common ancestor: try the full parse-tree. - (funcall find-headline path (plist-get info :parse-tree))))))) + (funcall find-headline path (plist-get info :parse-tree)))))))) (defun org-export-resolve-id-link (link info) "Return headline referenced as LINK destination. @@ -2759,20 +2781,6 @@ is either \"id\" or \"custom-id\"." headline)) info 'first-match))) -(defun org-export-resolve-ref-link (link info) - "Return element referenced as LINK destination. - -INFO is a plist used as a communication channel. - -Assume LINK type is \"ref\" and. Return value is the first -element whose `:name' property matches LINK's `:path', or nil." - (let ((name (org-element-property :path link))) - (org-element-map - (plist-get info :parse-tree) org-element-all-elements - (lambda (el) - (when (string= (org-element-property :name el) name) el)) - info 'first-match))) - (defun org-export-resolve-coderef (ref info) "Resolve a code reference REF. @@ -2863,27 +2871,62 @@ Optional argument PREDICATE is a function returning a non-nil value if the current element or object should be counted in. It accepts one argument: the element or object being considered. This argument allows to count only a certain type of objects, -like inline images, which are a subset of links \(in that case, -`org-export-inline-image-p' might be an useful predicate\)." - (let ((counter 0) - ;; Determine if search should apply to current section, in - ;; which case it should be retrieved first, or to full parse - ;; tree. As a special case, an element or object without - ;; a parent headline will also trigger a full search, - ;; notwithstanding WITHIN-SECTION value. - (data - (if (not within-section) (plist-get info :parse-tree) - (or (org-export-get-parent-headline element info) - (plist-get info :parse-tree))))) - ;; Increment counter until ELEMENT is found again. - (org-element-map - data (or types (org-element-type element)) - (lambda (el) - (cond - ((equal element el) (1+ counter)) - ((not predicate) (incf counter) nil) - ((funcall predicate el) (incf counter) nil))) - info 'first-match))) +like inline images, which are a subset of links (in that case, +`org-export-inline-image-p' might be an useful predicate). + +Return value is a list of numbers if ELEMENT is an headline or an +item. It is nil for keywords. It represents the footnote number +for footnote definitions and footnote references. If ELEMENT is +a target, return the same value as if ELEMENT was the closest +table, item or headline containing the target. In any other +case, return the sequence number of ELEMENT among elements or +objects of the same type." + ;; A target keyword, representing an invisible target, never has + ;; a sequence number. + (unless (eq (org-element-type element) 'keyword) + ;; Ordinal of a target object refer to the ordinal of the closest + ;; table, item, or headline containing the object. + (when (eq (org-element-type element) 'target) + (setq element + (loop for parent in (org-export-get-genealogy element info) + when + (memq + (org-element-type parent) + '(footnote-definition footnote-reference headline item + table)) + return parent))) + (case (org-element-type element) + ;; Special case 1: An headline returns its number as a list. + (headline (org-export-get-headline-number element info)) + ;; Special case 2: An item returns its number as a list. + (item (let ((struct (org-element-property :structure element))) + (org-list-get-item-number + (org-element-property :begin element) + struct + (org-list-prevs-alist struct) + (org-list-parents-alist struct)))) + ((footnote definition footnote-reference) + (org-export-get-footnote-number element info)) + (otherwise + (let ((counter 0) + ;; Determine if search should apply to current section, + ;; in which case it should be retrieved first, or to full + ;; parse tree. As a special case, an element or object + ;; without a parent headline will also trigger a full + ;; search, notwithstanding WITHIN-SECTION value. + (data + (if (not within-section) (plist-get info :parse-tree) + (or (org-export-get-parent-headline element info) + (plist-get info :parse-tree))))) + ;; Increment counter until ELEMENT is found again. + (org-element-map + data (or types (org-element-type element)) + (lambda (el) + (cond + ((equal element el) (1+ counter)) + ((not predicate) (incf counter) nil) + ((funcall predicate el) (incf counter) nil))) + info 'first-match)))))) ;;;; For Src-Blocks diff --git a/lisp/org.el b/lisp/org.el index a81f7fc..b8dd292 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -9896,6 +9896,22 @@ visibility around point, thus ignoring pos (match-beginning 0)))) ;; There is an exact target for this (goto-char pos)) + ((save-excursion + (goto-char (point-min)) + (and + (re-search-forward + (format "^[ \t]*#\\+TARGET: %s" (regexp-quote s0)) nil t) + (setq type 'dedicated pos (match-beginning 0)))) + ;; Found an invisible target. + (goto-char pos)) + ((save-excursion + (goto-char (point-min)) + (and + (re-search-forward + (format "^[ \t]*#\\+NAME: %s" (regexp-quote s0)) nil t) + (setq type 'dedicated pos (match-beginning 0)))) + ;; Found an element with a matching #+name affiliated keyword. + (goto-char pos)) ((and (string-match "^(\\(.*\\))$" s0) (save-excursion (goto-char (point-min)) diff --git a/testing/contrib/lisp/test-org-export.el b/testing/contrib/lisp/test-org-export.el index c9923d4..f791391 100644 --- a/testing/contrib/lisp/test-org-export.el +++ b/testing/contrib/lisp/test-org-export.el @@ -369,3 +369,94 @@ body\n"))) ;; Both footnotes should be seen. (should (= (length (org-export-collect-footnote-definitions tree info)) 2)))))) + +(ert-deftest test-org-export/fuzzy-links () + "Test fuzz link export specifications." + ;; 1. Links to invisible (keyword) targets should be ignored. + (org-test-with-temp-text + "Paragraph.\n#+TARGET: Test\n[[Test]]" + (let* ((tree (org-element-parse-buffer)) + (info (org-combine-plists (org-export-initial-options)))) + (setq info (org-combine-plists + info (org-export-collect-tree-properties tree info 'test))) + (should-not + (org-element-map + tree 'link + (lambda (link) + (org-export-get-ordinal + (org-export-resolve-fuzzy-link link info) info)) info)))) + ;; 2. Link to an headline should return headline's number. + (org-test-with-temp-text + "Paragraph.\n* Head1\n* Head2\n* Head3\n[[Head2]]" + (let* ((tree (org-element-parse-buffer)) + (info (org-combine-plists (org-export-initial-options)))) + (setq info (org-combine-plists + info (org-export-collect-tree-properties tree info 'test))) + (should + ;; Note: Headline's number is in fact a list of numbers. + (equal '(2) + (org-element-map + tree 'link + (lambda (link) + (org-export-get-ordinal + (org-export-resolve-fuzzy-link link info) info)) info t))))) + ;; 3. Link to a target in an item should return item's number. + (org-test-with-temp-text + "- Item1\n - Item11\n - <>Item12\n- Item2\n\n\n[[test]]" + (let* ((tree (org-element-parse-buffer)) + (info (org-combine-plists (org-export-initial-options)))) + (setq info (org-combine-plists + info (org-export-collect-tree-properties tree info 'test))) + (should + ;; Note: Item's number is in fact a list of numbers. + (equal '(1 2) + (org-element-map + tree 'link + (lambda (link) + (org-export-get-ordinal + (org-export-resolve-fuzzy-link link info) info)) info t))))) + ;; 4. Link to a target in a footnote should return footnote's + ;; number. + (org-test-with-temp-text + "Paragraph[1][2][fn:lbl3:C<>][[test]][[target]]\n[1] A\n\n[2] <>B" + (let* ((tree (org-element-parse-buffer)) + (info (org-combine-plists (org-export-initial-options)))) + (setq info (org-combine-plists + info (org-export-collect-tree-properties tree info 'test))) + (should + (equal '(2 3) + (org-element-map + tree 'link + (lambda (link) + (org-export-get-ordinal + (org-export-resolve-fuzzy-link link info) info)) info))))) + ;; 5. Link to a named element should return sequence number of that + ;; element. + (org-test-with-temp-text + "#+NAME: tbl1\n|1|2|\n#+NAME: tbl2\n|3|4|\n#+NAME: tbl3\n|5|6|\n[[tbl2]]" + (let* ((tree (org-element-parse-buffer)) + (info (org-combine-plists (org-export-initial-options)))) + (setq info (org-combine-plists + info (org-export-collect-tree-properties tree info 'test))) + (should + (= 2 + (org-element-map + tree 'link + (lambda (link) + (org-export-get-ordinal + (org-export-resolve-fuzzy-link link info) info)) info t))))) + ;; 6. Link to a target not within an item, a table, a footnote + ;; reference or definition should return section number. + (org-test-with-temp-text + "* Head1\n* Head2\nParagraph<>\n* Head3\n[[target]]" + (let* ((tree (org-element-parse-buffer)) + (info (org-combine-plists (org-export-initial-options)))) + (setq info (org-combine-plists + info (org-export-collect-tree-properties tree info 'test))) + (should + (equal '(2) + (org-element-map + tree 'link + (lambda (link) + (org-export-get-ordinal + (org-export-resolve-fuzzy-link link info) info)) info t)))))) diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index 4fc9ac9..3639367 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -98,6 +98,47 @@ http://article.gmane.org/gmane.emacs.orgmode/21459/" (org-babel-next-src-block) (should (equal '(2 1) (org-babel-execute-src-block))))) + + +;;; Links + +;;;; Fuzzy links + +;; Fuzzy links [[text]] encompass links to a target (<>), to +;; a target keyword (aka an invisible target: #+TARGET: text), to +;; a named element (#+name: text) and to headlines (* Text). + +(ert-deftest test-org-export/fuzzy-links () + "Test fuzzy links specifications." + ;; 1. Fuzzy link goes in priority to a matching target. + (org-test-with-temp-text + "#+TARGET: Test\n#+NAME: Test\n|a|b|\n<>\n* Test\n[[Test]]" + (goto-line 4) + (org-open-at-point) + (should (looking-at "<>"))) + ;; 2. Fuzzy link should then go to a matching target keyword. + (org-test-with-temp-text + "#+NAME: Test\n|a|b|\n#+TARGET: Test\n* Test\n[[Test]]" + (goto-line 4) + (org-open-at-point) + (should (looking-at "#\\+TARGET: Test"))) + ;; 3. Then fuzzy link points to an element with a given name. + (org-test-with-temp-text "Test\n#+NAME: Test\n|a|b|\n* Test\n[[Test]]" + (goto-line 5) + (org-open-at-point) + (should (looking-at "#\\+NAME: Test"))) + ;; 4. A target still lead to a matching headline otherwise. + (org-test-with-temp-text "* Head1\n* Head2\n*Head3\n[[Head2]]" + (goto-line 4) + (org-open-at-point) + (should (looking-at "\\* Head2"))) + ;; 5. With a leading star in link, enforce heading match. + (org-test-with-temp-text "#+TARGET: Test\n* Test\n<>\n[[*Test]]" + (goto-line 4) + (org-open-at-point) + (should (looking-at "\\* Test")))) + + (provide 'test-org) ;;; test-org.el ends here -- 1.7.9.2 --=-=-=--