浏览代码

org-element: Add :format property to link objects

* lisp/org-element.el (org-element-link-parser): Add :format property.
  Tiny refactoring.

* testing/lisp/test-org-element.el (test-org-element/link-interpreter):
  Add one test.  Update some others.

Since the link format is lost during parsing, the interpreter can hardly
handle nested links.  Indeed, in that case, the inner link is
interpreted as a bracket link, thus breaking the outer link.

Another option could be to guess a safe format for the link to be
interpreted. E.g.,

- any inner link could default to angle format unless it contains ">",
  it which case it would become a plain link;

- other links would have the bracket format, unless they contain "][" or
  "]]".

In any case, defining :format is less error prone and is similar to what
entities and sub/super-scripts have for curly brackets.

Reported-by: Thibault Marin <thibault.marin@gmx.com>
<http://permalink.gmane.org/gmane.emacs.orgmode/109623>
Nicolas Goaziou 8 年之前
父节点
当前提交
05794b13b3
共有 2 个文件被更改,包括 53 次插入29 次删除
  1. 45 24
      lisp/org-element.el
  2. 8 5
      testing/lisp/test-org-element.el

+ 45 - 24
lisp/org-element.el

@@ -3059,7 +3059,7 @@ Assume point is at the beginning of the line break."
   "Parse link at point, if any.
 
 When at a link, return a list whose car is `link' and cdr a plist
-with `:type', `:path', `:raw-link', `:application',
+with `:type', `:path', `:format', `:raw-link', `:application',
 `:search-option', `:begin', `:end', `:contents-begin',
 `:contents-end' and `:post-blank' as keywords.  Otherwise, return
 nil.
@@ -3067,20 +3067,22 @@ nil.
 Assume point is at the beginning of the link."
   (catch 'no-object
     (let ((begin (point))
-	  end contents-begin contents-end link-end post-blank path type
+	  end contents-begin contents-end link-end post-blank path type format
 	  raw-link search-option application)
       (cond
        ;; Type 1: Text targeted from a radio target.
        ((and org-target-link-regexp
 	     (save-excursion (or (bolp) (backward-char))
 			     (looking-at org-target-link-regexp)))
-	(setq type "radio"
-	      link-end (match-end 1)
-	      path (match-string-no-properties 1)
-	      contents-begin (match-beginning 1)
-	      contents-end (match-end 1)))
+	(setq type "radio")
+	(setq format 'plain)
+	(setq link-end (match-end 1))
+	(setq path (match-string-no-properties 1))
+	(setq contents-begin (match-beginning 1))
+	(setq contents-end (match-end 1)))
        ;; Type 2: Standard link, i.e. [[http://orgmode.org][homepage]]
        ((looking-at org-bracket-link-regexp)
+	(setq format 'bracket)
 	(setq contents-begin (match-beginning 3))
 	(setq contents-end (match-end 3))
 	(setq link-end (match-end 0))
@@ -3114,7 +3116,8 @@ Assume point is at the beginning of the link."
 	  (setq path (substring raw-link (match-end 0))))
 	 ;; Id type: PATH is the id.
 	 ((string-match "\\`id:\\([-a-f0-9]+\\)\\'" raw-link)
-	  (setq type "id" path (match-string 1 raw-link)))
+	  (setq type "id")
+	  (setq path (match-string 1 raw-link)))
 	 ;; Code-ref type: PATH is the name of the reference.
 	 ((and (string-match-p "\\`(" raw-link)
 	       (string-match-p ")\\'" raw-link))
@@ -3132,14 +3135,16 @@ Assume point is at the beginning of the link."
 	  (setq path raw-link))))
        ;; Type 3: Plain link, e.g., http://orgmode.org
        ((looking-at org-plain-link-re)
-	(setq raw-link (match-string-no-properties 0)
-	      type (match-string-no-properties 1)
-	      link-end (match-end 0)
-	      path (match-string-no-properties 2)))
+	(setq format 'plain)
+	(setq raw-link (match-string-no-properties 0))
+	(setq type (match-string-no-properties 1))
+	(setq link-end (match-end 0))
+	(setq path (match-string-no-properties 2)))
        ;; Type 4: Angular link, e.g., <http://orgmode.org>.  Unlike to
        ;; bracket links, follow RFC 3986 and remove any extra
        ;; whitespace in URI.
        ((looking-at org-angle-link-re)
+	(setq format 'angle)
 	(setq type (match-string-no-properties 1))
 	(setq link-end (match-end 0))
 	(setq raw-link
@@ -3171,6 +3176,7 @@ Assume point is at the beginning of the link."
       (list 'link
 	    (list :type type
 		  :path path
+		  :format format
 		  :raw-link (or raw-link path)
 		  :application application
 		  :search-option search-option
@@ -3186,18 +3192,33 @@ CONTENTS is the contents of the object, or nil."
   (let ((type (org-element-property :type link))
 	(path (org-element-property :path link)))
     (if (string= type "radio") path
-      (format "[[%s]%s]"
-	      (cond ((string= type "coderef") (format "(%s)" path))
-		    ((string= type "custom-id") (concat "#" path))
-		    ((string= type "file")
-		     (let ((app (org-element-property :application link))
-			   (opt (org-element-property :search-option link)))
-		       (concat type (and app (concat "+" app)) ":"
-			       path
-			       (and opt (concat "::" opt)))))
-		    ((string= type "fuzzy") path)
-		    (t (concat type ":" path)))
-	      (if contents (format "[%s]" contents) "")))))
+      (let ((fmt (pcase (org-element-property :format link)
+		   ;; Links with contents and internal links have to
+		   ;; use bracket syntax.  Ignore `:format' in these
+		   ;; cases.  This is also the default syntax when the
+		   ;; property is not defined, e.g., when the object
+		   ;; was crafted by the user.
+		   ((guard contents) (format "[[%%s][%s]]" contents))
+		   ((or `bracket
+			`nil
+			(guard (member type '("coderef" "custom-id" "fuzzy"))))
+		    "[[%s]]")
+		   ;; Otherwise, just obey to `:format'.
+		   (`angle "<%s>")
+		   (`plain "%s")
+		   (f (error "Wrong `:format' value: %s" f)))))
+	(format fmt
+		(pcase type
+		  ("coderef" (format "(%s)" path))
+		  ("custom-id" (concat "#" path))
+		  ("file"
+		   (let ((app (org-element-property :application link))
+			 (opt (org-element-property :search-option link)))
+		     (concat type (and app (concat "+" app)) ":"
+			     path
+			     (and opt (concat "::" opt)))))
+		  ("fuzzy" path)
+		  (_ (concat type ":" path))))))))
 
 
 ;;;; Macro

+ 8 - 5
testing/lisp/test-org-element.el

@@ -3001,10 +3001,13 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
   ;; Links without description.
   (should (equal (org-test-parse-and-interpret "[[http://orgmode.org]]")
 		 "[[http://orgmode.org]]\n"))
-  ;; Links with a description.
+  ;; Links with a description, even one containing a link.
   (should (equal (org-test-parse-and-interpret
 		  "[[http://orgmode.org][Org mode]]")
 		 "[[http://orgmode.org][Org mode]]\n"))
+  (should (equal (org-test-parse-and-interpret
+		  "[[http://orgmode.org][http://orgmode.org]]")
+		 "[[http://orgmode.org][http://orgmode.org]]\n"))
   ;; File links.
   (should
    (equal (org-test-parse-and-interpret "[[file+emacs:todo.org]]")
@@ -3018,12 +3021,12 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
   (should (equal (org-test-parse-and-interpret "[[#id]]") "[[#id]]\n"))
   ;; Code-ref links.
   (should (equal (org-test-parse-and-interpret "[[(ref)]]") "[[(ref)]]\n"))
-  ;; Normalize plain links.
+  ;; Plain links.
   (should (equal (org-test-parse-and-interpret "http://orgmode.org")
-		 "[[http://orgmode.org]]\n"))
-  ;; Normalize angular links.
+		 "http://orgmode.org\n"))
+  ;; Angular links.
   (should (equal (org-test-parse-and-interpret "<http://orgmode.org>")
-		 "[[http://orgmode.org]]\n")))
+		 "<http://orgmode.org>\n")))
 
 (ert-deftest test-org-element/macro-interpreter ()
   "Test macro interpreter."