From dcae2d1015f958dcb1ed3c92349ad0c2e18a1219 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Mon, 20 Feb 2012 22:24:38 +0100 Subject: [PATCH] Implement cross-references numbers * 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. --- EXPERIMENTAL/org-e-ascii.el | 80 ++++++++----------- EXPERIMENTAL/org-e-latex.el | 48 ++++------- contrib/lisp/org-element.el | 20 ++--- contrib/lisp/org-export.el | 185 ++++++++++++++++++++++++++---------------- lisp/org.el | 16 ++++ 5 files changed, 187 insertions(+), 162 deletions(-) diff --git a/EXPERIMENTAL/org-e-ascii.el b/EXPERIMENTAL/org-e-ascii.el index ef1ca1c..1361e23 100644 --- a/EXPERIMENTAL/org-e-ascii.el +++ b/EXPERIMENTAL/org-e-ascii.el @@ -826,28 +826,24 @@ channel." (org-element-get-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 (car 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. @@ -1392,29 +1388,23 @@ INFO is a plist holding contextual information." (org-element-get-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-get-property :caption el) - (org-element-get-property :name el))))))) ;; Do not apply a special syntax on fuzzy links pointing to ;; targets. - ((and (string= type "fuzzy") - (let ((path (org-element-get-property :path link))) - (loop for target in (plist-get info :target-list) - thereis (string= - (org-element-get-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 (car 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 @@ -1852,11 +1842,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 eec331a..75b5a9b 100644 --- a/EXPERIMENTAL/org-e-latex.el +++ b/EXPERIMENTAL/org-e-latex.el @@ -1266,8 +1266,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 @@ -1426,32 +1426,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 (car destination) - (target - (format "\\hyperref[%s]{%s}" - (org-export-solidify-link-text - (org-element-get-property :raw-value destination)) + ;; Fuzzy link points nowhere. + ('nil + (format "\\texttt{%s}" (or desc (org-export-secondary-string (org-element-get-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" @@ -1466,13 +1460,10 @@ INFO is a plist holding contextual information. See (org-export-secondary-string (org-element-get-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-get-property :raw-link link) - 'e-latex info))))))) + (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") @@ -1945,14 +1936,11 @@ 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-get-property :raw-value target)) - text)) +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "\\label{%s}" (org-element-get-property :value target))) ;;;; Time-stamp diff --git a/contrib/lisp/org-element.el b/contrib/lisp/org-element.el index 5b61942..554537e 100644 --- a/contrib/lisp/org-element.el +++ b/contrib/lisp/org-element.el @@ -1961,9 +1961,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))) @@ -2265,25 +2262,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) @@ -2470,7 +2463,7 @@ Sharing the same successor comes handy when, for example, the regexp matching one object can also match the other object.") (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 @@ -2540,8 +2533,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 34f290e..aa6ca52 100644 --- a/contrib/lisp/org-export.el +++ b/contrib/lisp/org-export.el @@ -1278,7 +1278,13 @@ Following tree properties are set: `(:parse-tree ,data :target-list - ,(org-element-map data 'target (lambda (target local) target) info) + ,(org-element-map + data '(keyword target) + (lambda (blob local) + (when (or (eq (car blob) 'target) + (string= (downcase (org-element-get-property :key blob)) + "target")) + blob)) info) :headline-numbering ,(org-export-collect-headline-numbering data info) :back-end ,backend) info)) @@ -2579,8 +2585,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 @@ -2591,39 +2600,53 @@ Return value can be an object, an element, or nil: Assume LINK type is \"fuzzy\"." (let ((path (org-element-get-property :path link))) - ;; Link points to a target: return it. - (or (loop for target in (plist-get info :target-list) - when (string= (org-element-get-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 - (function - ;; Return first headline whose `:raw-value' property - ;; is NAME in parse tree DATA, or nil. - (lambda (name data) - (org-element-map - data 'headline - (lambda (headline local) - (when (string= - (org-element-get-property :raw-value headline) - name) - headline)) - info 'first-match))))) - ;; Search among headlines sharing an ancestor with link, - ;; from closest to farthest. - (or (catch 'exit - (mapc - (lambda (parent) - (when (eq (car parent) 'headline) - (let ((foundp (funcall find-headline path parent))) - (when foundp (throw 'exit foundp))))) - (plist-get info :genealogy)) nil) - ;; No match with a common ancestor: try the full parse-tree. - (funcall find-headline path (plist-get info :parse-tree))))))) + (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-get-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 local) + (when (string= (org-element-get-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. + (lambda (name data) + (org-element-map + data 'headline + (lambda (headline local) + (when (string= + (org-element-get-property :raw-value headline) + name) + headline)) + info 'first-match))))) + ;; Search among headlines sharing an ancestor with link, from + ;; closest to farthest. + (or (catch 'exit + (mapc + (lambda (parent) + (when (eq (car parent) 'headline) + (let ((foundp (funcall find-headline path parent))) + (when foundp (throw 'exit foundp))))) + (plist-get info :genealogy)) nil) + ;; No match with a common ancestor: try the full + ;; 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. @@ -2641,20 +2664,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-get-property :path link))) - (org-element-map - (plist-get info :parse-tree) org-element-all-elements - (lambda (el local) - (when (string= (org-element-get-property :name el) name) el)) - info 'first-match))) - (defun org-export-resolve-coderef (ref info) "Resolve a code reference REF. @@ -2745,27 +2754,61 @@ 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 (car element)) - (lambda (el local) - (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 (car element) 'keyword) + ;; Ordinal of a target object is the ordinal of the closest table, + ;; item, or headline containing the object. + (when (eq (car element) 'target) + (setq element + (loop for parent in (org-export-get-genealogy element info) + when + (memq + (car parent) + '(footnote-definition footnote-reference headline item table)) + return parent))) + (case (car 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-get-property :structure element))) + (org-list-get-item-number + (org-element-get-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 (car element)) + (lambda (el local) + (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..6708801 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. + (goto-char pos)) ((and (string-match "^(\\(.*\\))$" s0) (save-excursion (goto-char (point-min)) -- 1.7.9.1