Bladeren bron

org-element: Interpret pseudo elements and objects

* lisp/org-element.el (org-element-interpret-data): Change signature.
  One can now define pseudo elements and objects types.
(org-element--interpret-data-1): New function.
* testing/lisp/test-org-element.el: Add tests.
* testing/lisp/test-ox.el: Update tests.

A pseudo element or object is a new element or object type that is
created and treated locally within an export back-end.  The back-end
provides a translator for it and it is ignored when interpreted back
into Org syntax.
Nicolas Goaziou 11 jaren geleden
bovenliggende
commit
f101663489
3 gewijzigde bestanden met toevoegingen van 80 en 48 verwijderingen
  1. 57 37
      lisp/org-element.el
  2. 19 7
      testing/lisp/test-org-element.el
  3. 4 4
      testing/lisp/test-ox.el

+ 57 - 37
lisp/org-element.el

@@ -4387,71 +4387,91 @@ beginning position."
 ;; `org-element--interpret-affiliated-keywords'.
 
 ;;;###autoload
-(defun org-element-interpret-data (data &optional parent)
+(defun org-element-interpret-data (data &optional pseudo-objects)
   "Interpret DATA as Org syntax.
 
 DATA is a parse tree, an element, an object or a secondary string
 to interpret.
 
-Optional argument PARENT is used for recursive calls.  It contains
-the element or object containing data, or nil.
+Optional argument PSEUDO-OBJECTS is a list of symbols defining
+new types that should be treated as objects.  An unknown type not
+belonging to this list is seen as a pseudo-element instead.  Both
+pseudo-objects and pseudo-elements are transparent entities, i.e.
+only their contents are interpreted.
+
+Return Org syntax as a string."
+  (org-element--interpret-data-1 data nil pseudo-objects))
+
+(defun org-element--interpret-data-1 (data parent pseudo-objects)
+  "Interpret DATA as Org syntax.
+
+DATA is a parse tree, an element, an object or a secondary string
+to interpret.  PARENT is used for recursive calls.  It contains
+the element or object containing data, or nil.  PSEUDO-OBJECTS
+are list of symbols defining new element or object types.
+Unknown types that don't belong to this list are treated as
+pseudo-elements instead.
 
 Return Org syntax as a string."
   (let* ((type (org-element-type data))
+	 ;; Find interpreter for current object or element.  If it
+	 ;; doesn't exist (e.g. this is a pseudo object or element),
+	 ;; return contents, if any.
+	 (interpret
+	  (let ((fun (intern (format "org-element-%s-interpreter" type))))
+	    (if (fboundp fun) fun (lambda (data contents) contents))))
 	 (results
 	  (cond
 	   ;; Secondary string.
 	   ((not type)
 	    (mapconcat
-	     (lambda (obj) (org-element-interpret-data obj parent))
+	     (lambda (obj)
+	       (org-element--interpret-data-1 obj parent pseudo-objects))
 	     data ""))
 	   ;; Full Org document.
 	   ((eq type 'org-data)
 	    (mapconcat
-	     (lambda (obj) (org-element-interpret-data obj parent))
+	     (lambda (obj)
+	       (org-element--interpret-data-1 obj parent pseudo-objects))
 	     (org-element-contents data) ""))
 	   ;; Plain text: remove `:parent' text property from output.
 	   ((stringp data) (org-no-properties data))
-	   ;; Element/Object without contents.
-	   ((not (org-element-contents data))
-	    (funcall (intern (format "org-element-%s-interpreter" type))
-		     data nil))
-	   ;; Element/Object with contents.
+	   ;; Element or object without contents.
+	   ((not (org-element-contents data)) (funcall interpret data nil))
+	   ;; Element or object with contents.
 	   (t
-	    (let* ((greaterp (memq type org-element-greater-elements))
-		   (objectp (and (not greaterp)
-				 (memq type org-element-recursive-objects)))
-		   (contents
-		    (mapconcat
-		     (lambda (obj) (org-element-interpret-data obj data))
-		     (org-element-contents
-		      (if (or greaterp objectp) data
-			;; Elements directly containing objects must
-			;; have their indentation normalized first.
-			(org-element-normalize-contents
-			 data
-			 ;; When normalizing first paragraph of an
-			 ;; item or a footnote-definition, ignore
-			 ;; first line's indentation.
-			 (and (eq type 'paragraph)
-			      (equal data (car (org-element-contents parent)))
-			      (memq (org-element-type parent)
-				    '(footnote-definition item))))))
-		     "")))
-	      (funcall (intern (format "org-element-%s-interpreter" type))
-		       data
-		       (if greaterp (org-element-normalize-contents contents)
-			 contents)))))))
+	    (funcall interpret data
+		     ;; Recursively interpret contents.
+		     (mapconcat
+		      (lambda (obj)
+			(org-element--interpret-data-1 obj data pseudo-objects))
+		      (org-element-contents
+		       (if (not (memq type '(paragraph verse-block)))
+			   data
+			 ;; Fix indentation of elements containing
+			 ;; objects.  We ignore `table-row' elements
+			 ;; as they are one line long anyway.
+			 (org-element-normalize-contents
+			  data
+			  ;; When normalizing first paragraph of an
+			  ;; item or a footnote-definition, ignore
+			  ;; first line's indentation.
+			  (and (eq type 'paragraph)
+			       (equal data (car (org-element-contents parent)))
+			       (memq (org-element-type parent)
+				     '(footnote-definition item))))))
+		      ""))))))
     (if (memq type '(org-data plain-text nil)) results
       ;; Build white spaces.  If no `:post-blank' property is
       ;; specified, assume its value is 0.
       (let ((post-blank (or (org-element-property :post-blank data) 0)))
-	(if (memq type org-element-all-objects)
-	    (concat results (make-string post-blank 32))
+	(if (or (memq type org-element-all-objects)
+		(memq type pseudo-objects))
+	    (concat results (make-string post-blank ?\s))
 	  (concat
 	   (org-element--interpret-affiliated-keywords data)
 	   (org-element-normalize-string results)
-	   (make-string post-blank 10)))))))
+	   (make-string post-blank ?\n)))))))
 
 (defun org-element--interpret-affiliated-keywords (element)
   "Return ELEMENT's affiliated keywords as Org syntax.

+ 19 - 7
testing/lisp/test-org-element.el

@@ -1989,27 +1989,27 @@ Outside list"
 
 ;;; Test Interpreters.
 
-(ert-deftest test-org-element/affiliated-keywords-interpreter ()
-  "Test if affiliated keywords are correctly interpreted."
-  ;; Interpret simple keywords.
+(ert-deftest test-org-element/interpret-data ()
+  "Test `org-element-interpret-data' specifications."
+  ;; Interpret simple affiliated keywords.
   (should
    (equal
     (org-element-interpret-data
      '(org-data nil (paragraph (:name "para") "Paragraph")))
     "#+NAME: para\nParagraph\n"))
-  ;; Interpret multiple keywords.
+  ;; Interpret multiple affiliated keywords.
   (should
    (equal
     (org-element-interpret-data
      '(org-data nil (paragraph (:attr_ascii ("line2" "line1")) "Paragraph")))
     "#+ATTR_ASCII: line1\n#+ATTR_ASCII: line2\nParagraph\n"))
-  ;; Interpret parsed keywords.
+  ;; Interpret parsed affiliated keywords.
   (should
    (equal
     (org-element-interpret-data
      '(org-data nil (paragraph (:caption (("caption"))) "Paragraph")))
     "#+CAPTION: caption\nParagraph\n"))
-  ;; Interpret dual keywords.
+  ;; Interpret dual affiliated keywords.
   (should
    (equal
     (org-element-interpret-data
@@ -2021,7 +2021,19 @@ Outside list"
     (org-element-interpret-data
      '(org-data nil (paragraph
 		     (:caption ((("l2") "s2") (("l1") "s1"))) "Paragraph")))
-    "#+CAPTION[s1]: l1\n#+CAPTION[s2]: l2\nParagraph\n")))
+    "#+CAPTION[s1]: l1\n#+CAPTION[s2]: l2\nParagraph\n"))
+  ;; Pseudo objects and elements are transparent.
+  (should
+   (equal "A B\n"
+	  (org-element-interpret-data
+	   '(paragraph nil (pseudo-object (:post-blank 1) "A") "B")
+	   '(pseudo-object))))
+  (should
+   (equal "A\n\nB\n"
+	  (org-element-interpret-data
+	   '(center nil
+		    (pseudo-element (:post-blank 1) (paragraph nil "A"))
+		    (paragraph nil "B"))))))
 
 (ert-deftest test-org-element/center-block-interpreter ()
   "Test center block interpreter."

+ 4 - 4
testing/lisp/test-ox.el

@@ -281,7 +281,7 @@ Paragraph"
 	      :transcoders
 	      '((template . (lambda (text info)
 			      (org-element-interpret-data
-			       (plist-get info :title) info))))))
+			       (plist-get info :title)))))))
 	    (file-name-nondirectory
 	     (file-name-sans-extension (buffer-file-name)))))))
   ;; If no title is specified, and no file is associated to the
@@ -296,7 +296,7 @@ Paragraph"
 	      :transcoders
 	      '((template . (lambda (text info)
 			      (org-element-interpret-data
-			       (plist-get info :title) info))))))
+			       (plist-get info :title)))))))
 	    (buffer-name)))))
   ;; If a title is specified, use it.
   (should
@@ -309,7 +309,7 @@ Paragraph"
 	:transcoders
 	'((template . (lambda (text info)
 			(org-element-interpret-data
-			 (plist-get info :title) info)))))))))
+			 (plist-get info :title))))))))))
   ;; If an empty title is specified, do not set it.
   (should
    (equal
@@ -321,7 +321,7 @@ Paragraph"
 	:transcoders
 	'((template . (lambda (text info)
 			(org-element-interpret-data
-			 (plist-get info :title) info))))))))))
+			 (plist-get info :title)))))))))))
 
 (ert-deftest test-org-export/handle-options ()
   "Test if export options have an impact on output."