Browse Source

org-element: Add `clock' and `planning' element types

* contrib/lisp/org-element.el (org-element-babel-call-parser): Fix
  property name.
(org-element-babel-call-interpreter, org-element--element-block-re):
Fix docstring.
(org-element-clock-parser, org-element-clock-interpreter,
org-element-planning-parser, org-element-planning-interpreter): New
functions.
(org-element-time-stamp-parser): Move planning keywords out of the
object: no more `:appt-type' property
(org-element-time-stamp-interpreter,
org-element-time-stamp-successor): Apply changes to previous function.
(org-element-paragraph-separate): Time keywords also end paragraphs.
(org-element-all-elements): Register new elements types.
(org-element-current-element): Recognize new elements.
(org-element-parse-elements): Fix comments.
* testing/lisp/test-org-element.el: Add tests.
Nicolas Goaziou 13 years ago
parent
commit
5cd9c01757
2 changed files with 201 additions and 88 deletions
  1. 171 86
      contrib/lisp/org-element.el
  2. 30 2
      testing/lisp/test-org-element.el

+ 171 - 86
contrib/lisp/org-element.el

@@ -30,23 +30,25 @@
 ;; to at least one element.
 ;; to at least one element.
 
 
 ;; An element always starts and ends at the beginning of a line.  With
 ;; An element always starts and ends at the beginning of a line.  With
-;; a few exceptions (namely `headline', `item', `section', `keyword',
-;; `babel-call' and `property-drawer' types), it can also accept
-;; a fixed set of keywords as attributes.  Those are called
-;; "affiliated keywords" to distinguish them from other keywords,
-;; which are full-fledged elements.
+;; a few exceptions (namely `babel-call', `clock', `headline', `item',
+;; `keyword', `planning', `property-drawer' and `section' types), it
+;; can also accept a fixed set of keywords as attributes.  Those are
+;; called "affiliated keywords" to distinguish them from other
+;; keywords, which are full-fledged elements.  All affiliated keywords
+;; are referenced in `org-element-affiliated-keywords'.
 ;;
 ;;
 ;; Element containing other elements (and only elements) are called
 ;; Element containing other elements (and only elements) are called
 ;; greater elements.  Concerned types are: `center-block', `drawer',
 ;; greater elements.  Concerned types are: `center-block', `drawer',
 ;; `dynamic-block', `footnote-definition', `headline', `inlinetask',
 ;; `dynamic-block', `footnote-definition', `headline', `inlinetask',
 ;; `item', `plain-list', `quote-block', `section' and `special-block'.
 ;; `item', `plain-list', `quote-block', `section' and `special-block'.
 ;;
 ;;
-;; Other element types are: `babel-call', `comment', `comment-block',
-;; `example-block', `export-block', `fixed-width', `horizontal-rule',
-;; `keyword', `latex-environment', `paragraph', `property-drawer',
-;; `quote-section', `src-block', `table', `table-cell', `table-row'
-;; and `verse-block'.  Among them, `paragraph', `table-cell' and
-;; `verse-block' types can contain Org objects and plain text.
+;; Other element types are: `babel-call', `clock', `comment',
+;; `comment-block', `example-block', `export-block', `fixed-width',
+;; `horizontal-rule', `keyword', `latex-environment', `paragraph',
+;; `planning', `property-drawer', `quote-section', `src-block',
+;; `table', `table-cell', `table-row' and `verse-block'.  Among them,
+;; `paragraph', `table-cell' and `verse-block' types can contain Org
+;; objects and plain text.
 ;;
 ;;
 ;; Objects are related to document's contents.  Some of them are
 ;; Objects are related to document's contents.  Some of them are
 ;; recursive.  Associated types are of the following: `bold', `code',
 ;; recursive.  Associated types are of the following: `bold', `code',
@@ -912,31 +914,73 @@ keywords."
   (save-excursion
   (save-excursion
     (let ((info (progn (looking-at org-babel-block-lob-one-liner-regexp)
     (let ((info (progn (looking-at org-babel-block-lob-one-liner-regexp)
 		       (org-babel-lob-get-info)))
 		       (org-babel-lob-get-info)))
-	  (beg (point-at-bol))
+	  (begin (point-at-bol))
 	  (pos-before-blank (progn (forward-line) (point)))
 	  (pos-before-blank (progn (forward-line) (point)))
 	  (end (progn (org-skip-whitespace)
 	  (end (progn (org-skip-whitespace)
 		      (if (eobp) (point) (point-at-bol)))))
 		      (if (eobp) (point) (point-at-bol)))))
       `(babel-call
       `(babel-call
-	(:beg ,beg
-	      :end ,end
-	      :info ,info
-	      :post-blank ,(count-lines pos-before-blank end))))))
+	(:begin ,begin
+		:end ,end
+		:info ,info
+		:post-blank ,(count-lines pos-before-blank end))))))
 
 
-(defun org-element-babel-call-interpreter (inline-babel-call contents)
-  "Interpret INLINE-BABEL-CALL object as Org syntax.
+(defun org-element-babel-call-interpreter (babel-call contents)
+  "Interpret BABEL-CALL element as Org syntax.
 CONTENTS is nil."
 CONTENTS is nil."
-  (let* ((babel-info (org-element-property :info inline-babel-call))
-	 (main-source (car babel-info))
+  (let* ((babel-info (org-element-property :info babel-call))
+	 (main (car babel-info))
 	 (post-options (nth 1 babel-info)))
 	 (post-options (nth 1 babel-info)))
     (concat "#+CALL: "
     (concat "#+CALL: "
-	    (if (string-match "\\[\\(\\[.*?\\]\\)\\]" main-source)
-		;; Remove redundant square brackets.
-		(replace-match
-		 (match-string 1 main-source) nil nil main-source)
-	      main-source)
+	    (if (not (string-match "\\[\\(\\[.*?\\]\\)\\]" main)) main
+	      ;; Remove redundant square brackets.
+	      (replace-match (match-string 1 main) nil nil main))
 	    (and post-options (format "[%s]" post-options)))))
 	    (and post-options (format "[%s]" post-options)))))
 
 
 
 
+;;;; Clock
+
+(defun org-element-clock-parser ()
+  "Parse a clock.
+
+Return a list whose CAR is `clock' and CDR is a plist containing
+`:status', `:value', `:time', `:begin', `:end' and `:post-blank'
+as keywords."
+  (save-excursion
+    (let* ((case-fold-search nil)
+	   (begin (point))
+	   (value (progn (search-forward org-clock-string (line-end-position) t)
+			 (org-skip-whitespace)
+			 (looking-at "\\[.*\\]")
+			 (org-match-string-no-properties 0)))
+	   (time (and (progn (goto-char (match-end 0))
+			     (looking-at " +=> +\\(\\S-+\\)[ \t]*$"))
+		      (org-match-string-no-properties 1)))
+	   (status (if time 'closed 'running))
+	   (post-blank (let ((before-blank (progn (forward-line) (point))))
+			 (org-skip-whitespace)
+			 (unless (eobp) (beginning-of-line))
+			 (count-lines before-blank (point))))
+	   (end (point)))
+      `(clock (:status ,status
+		       :value ,value
+		       :time ,time
+		       :begin ,begin
+		       :end ,end
+		       :post-blank ,post-blank)))))
+
+(defun org-element-clock-interpreter (clock contents)
+  "Interpret CLOCK element as Org syntax.
+CONTENTS is nil."
+  (concat org-clock-string " "
+	  (org-element-property :value clock)
+	  (let ((time (org-element-property :time clock)))
+	    (and time
+		 (concat " => "
+			 (apply 'format
+				"%2s:%02s"
+				(org-split-string time ":")))))))
+
+
 ;;;; Comment
 ;;;; Comment
 
 
 (defun org-element-comment-parser ()
 (defun org-element-comment-parser ()
@@ -1323,6 +1367,56 @@ CONTENTS is the contents of the element."
   contents)
   contents)
 
 
 
 
+;;;; Planning
+
+(defun org-element-planning-parser ()
+  "Parse a planning.
+
+Return a list whose CAR is `planning' and CDR is a plist
+containing `:closed', `:deadline', `:scheduled', `:begin', `:end'
+and `:post-blank' keywords."
+  (save-excursion
+    (let* ((case-fold-search nil)
+	   (begin (point))
+	   (post-blank (let ((before-blank (progn (forward-line) (point))))
+			 (org-skip-whitespace)
+			 (unless (eobp) (beginning-of-line))
+			 (count-lines before-blank (point))))
+	   (end (point))
+	   closed deadline scheduled)
+      (goto-char begin)
+      (while (re-search-forward org-keyword-time-not-clock-regexp
+				(line-end-position) t)
+	(goto-char (match-end 1))
+	(org-skip-whitespace)
+	(let ((time (buffer-substring-no-properties (point) (match-end 0)))
+	      (keyword (match-string 1)))
+	  (cond ((equal keyword org-closed-string) (setq closed time))
+		((equal keyword org-deadline-string) (setq deadline time))
+		(t (setq scheduled time)))))
+      `(planning
+	(:closed ,closed
+		 :deadline ,deadline
+		 :scheduled ,scheduled
+		 :begin ,begin
+		 :end ,end
+		 :post-blank ,post-blank)))))
+
+(defun org-element-planning-interpreter (planning contents)
+  "Interpret PLANNING element as Org syntax.
+CONTENTS is nil."
+  (mapconcat
+   'identity
+   (delq nil
+	 (list (let ((closed (org-element-property :closed planning)))
+		 (when closed (concat org-closed-string " " closed)))
+	       (let ((deadline (org-element-property :deadline planning)))
+		 (when deadline (concat org-deadline-string " " deadline)))
+	       (let ((scheduled (org-element-property :scheduled planning)))
+		 (when scheduled (concat org-scheduled-string " " scheduled)))))
+   " "))
+
+
 ;;;; Property Drawer
 ;;;; Property Drawer
 
 
 (defun org-element-property-drawer-parser ()
 (defun org-element-property-drawer-parser ()
@@ -2590,55 +2684,37 @@ beginning position."
   "Parse time stamp at point.
   "Parse time stamp at point.
 
 
 Return a list whose CAR is `time-stamp', and CDR a plist with
 Return a list whose CAR is `time-stamp', and CDR a plist with
-`:appt-type', `:type', `:begin', `:end', `:value' and
-`:post-blank' keywords.
+`:type', `:begin', `:end', `:value' and `:post-blank' keywords.
 
 
 Assume point is at the beginning of the time-stamp."
 Assume point is at the beginning of the time-stamp."
   (save-excursion
   (save-excursion
-    (let* ((appt-type (cond
-		       ((looking-at (concat org-deadline-string " +"))
-			(goto-char (match-end 0))
-			'deadline)
-		       ((looking-at (concat org-scheduled-string " +"))
-			(goto-char (match-end 0))
-			'scheduled)
-		       ((looking-at (concat org-closed-string " +"))
-			(goto-char (match-end 0))
-			'closed)))
-	   (begin (and appt-type (match-beginning 0)))
+    (let* ((begin (point))
 	   (type (cond
 	   (type (cond
 		  ((looking-at org-tsr-regexp)
 		  ((looking-at org-tsr-regexp)
 		   (if (match-string 2) 'active-range 'active))
 		   (if (match-string 2) 'active-range 'active))
 		  ((looking-at org-tsr-regexp-both)
 		  ((looking-at org-tsr-regexp-both)
 		   (if (match-string 2) 'inactive-range 'inactive))
 		   (if (match-string 2) 'inactive-range 'inactive))
-		  ((looking-at (concat
-				"\\(<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[dwmy]>\\)"
-				"\\|"
-				"\\(<%%\\(([^>\n]+)\\)>\\)"))
+		  ((looking-at
+		    (concat
+		     "\\(<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[dwmy]>\\)"
+		     "\\|"
+		     "\\(<%%\\(([^>\n]+)\\)>\\)"))
 		   'diary)))
 		   'diary)))
-	   (begin (or begin (match-beginning 0)))
-	   (value (buffer-substring-no-properties
-		   (match-beginning 0) (match-end 0)))
+	   (value (org-match-string-no-properties 0))
 	   (post-blank (progn (goto-char (match-end 0))
 	   (post-blank (progn (goto-char (match-end 0))
 			      (skip-chars-forward " \t")))
 			      (skip-chars-forward " \t")))
 	   (end (point)))
 	   (end (point)))
       `(time-stamp
       `(time-stamp
-	(:appt-type ,appt-type
-		    :type ,type
-		    :value ,value
-		    :begin ,begin
-		    :end ,end
-		    :post-blank ,post-blank)))))
+	(:type ,type
+	       :value ,value
+	       :begin ,begin
+	       :end ,end
+	       :post-blank ,post-blank)))))
 
 
 (defun org-element-time-stamp-interpreter (time-stamp contents)
 (defun org-element-time-stamp-interpreter (time-stamp contents)
   "Interpret TIME-STAMP object as Org syntax.
   "Interpret TIME-STAMP object as Org syntax.
 CONTENTS is nil."
 CONTENTS is nil."
-  (concat
-   (case (org-element-property :appt-type time-stamp)
-     (closed (concat org-closed-string " "))
-     (deadline (concat org-deadline-string " "))
-     (scheduled (concat org-scheduled-string " ")))
-   (org-element-property :value time-stamp)))
+  (org-element-property :value time-stamp))
 
 
 (defun org-element-time-stamp-successor (limit)
 (defun org-element-time-stamp-successor (limit)
   "Search for the next time-stamp object.
   "Search for the next time-stamp object.
@@ -2649,9 +2725,7 @@ Return value is a cons cell whose CAR is `time-stamp' and CDR is
 beginning position."
 beginning position."
   (save-excursion
   (save-excursion
     (when (re-search-forward
     (when (re-search-forward
-	   (concat "\\(?:" org-scheduled-string " +\\|"
-		   org-deadline-string " +\\|" org-closed-string " +\\)?"
-		   org-ts-regexp-both
+	   (concat org-ts-regexp-both
 		   "\\|"
 		   "\\|"
 		   "\\(?:<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[dwmy]>\\)"
 		   "\\(?:<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[dwmy]>\\)"
 		   "\\|"
 		   "\\|"
@@ -2735,7 +2809,7 @@ CONTENTS is nil."
 	  ;; Headlines and inlinetasks.
 	  ;; Headlines and inlinetasks.
 	  org-outline-regexp-bol "\\|"
 	  org-outline-regexp-bol "\\|"
 	  ;; Comments, blocks (any type), keywords and babel calls.
 	  ;; Comments, blocks (any type), keywords and babel calls.
-	  "^[ \t]*#\\+" "\\|" "^#\\( \\|$\\)" "\\|"
+	  "^[ \t]*#\\+" "\\|" "^#\\(?: \\|$\\)" "\\|"
 	  ;; Lists.
 	  ;; Lists.
 	  (org-item-beginning-re) "\\|"
 	  (org-item-beginning-re) "\\|"
 	  ;; Fixed-width, drawers (any type) and tables.
 	  ;; Fixed-width, drawers (any type) and tables.
@@ -2745,16 +2819,22 @@ CONTENTS is nil."
 	  ;; Horizontal rules.
 	  ;; Horizontal rules.
 	  "^[ \t]*-\\{5,\\}[ \t]*$" "\\|"
 	  "^[ \t]*-\\{5,\\}[ \t]*$" "\\|"
 	  ;; LaTeX environments.
 	  ;; LaTeX environments.
-	  "^[ \t]*\\\\\\(begin\\|end\\)")
+	  "^[ \t]*\\\\\\(begin\\|end\\)"
+	  ;; Planning and Clock lines.
+	  "^[ \t]*\\(?:"
+	  org-clock-string "\\|"
+	  org-closed-string "\\|"
+	  org-deadline-string "\\|"
+	  org-scheduled-string "\\)")
   "Regexp to separate paragraphs in an Org buffer.")
   "Regexp to separate paragraphs in an Org buffer.")
 
 
 (defconst org-element-all-elements
 (defconst org-element-all-elements
-  '(center-block comment comment-block drawer dynamic-block example-block
+  '(center-block clock comment comment-block drawer dynamic-block example-block
 		 export-block fixed-width footnote-definition headline
 		 export-block fixed-width footnote-definition headline
 		 horizontal-rule inlinetask item keyword latex-environment
 		 horizontal-rule inlinetask item keyword latex-environment
-		 babel-call paragraph plain-list property-drawer quote-block
-		 quote-section section special-block src-block table table-row
-		 verse-block)
+		 babel-call paragraph plain-list planning property-drawer
+		 quote-block quote-section section special-block src-block table
+		 table-row verse-block)
   "Complete list of element types.")
   "Complete list of element types.")
 
 
 (defconst org-element-greater-elements
 (defconst org-element-greater-elements
@@ -2944,11 +3024,17 @@ element or object type."
 
 
 
 
 ;;; Parsing Element Starting At Point
 ;;; Parsing Element Starting At Point
-
+;;
 ;; `org-element-current-element' is the core function of this section.
 ;; `org-element-current-element' is the core function of this section.
 ;; It returns the Lisp representation of the element starting at
 ;; It returns the Lisp representation of the element starting at
 ;; point.  It uses `org-element--element-block-re' for quick access to
 ;; point.  It uses `org-element--element-block-re' for quick access to
 ;; a common regexp.
 ;; a common regexp.
+;;
+;; `org-element-current-element' makes use of special modes.  They are
+;; activated for fixed element chaining (i.e. `plain-list' > `item')
+;; or fixed conditional element chaining (i.e. `section' >
+;; `planning'). Special modes are: `section', `quote-section', `item'
+;; and `table-row'.
 
 
 (defconst org-element--element-block-re
 (defconst org-element--element-block-re
   (format "[ \t]*#\\+BEGIN_\\(%s\\)\\(?: \\|$\\)"
   (format "[ \t]*#\\+BEGIN_\\(%s\\)\\(?: \\|$\\)"
@@ -2956,8 +3042,7 @@ element or object type."
            'regexp-quote
            'regexp-quote
            (mapcar 'car org-element-non-recursive-block-alist) "\\|"))
            (mapcar 'car org-element-non-recursive-block-alist) "\\|"))
   "Regexp matching the beginning of a non-recursive block type.
   "Regexp matching the beginning of a non-recursive block type.
-Used internally by `org-element-current-element'.  Do not modify
-it directly, set `org-element-recursive-block-alist' instead.")
+Used internally by `org-element-current-element'.")
 
 
 (defun org-element-current-element (&optional granularity special structure)
 (defun org-element-current-element (&optional granularity special structure)
   "Parse the element starting at point.
   "Parse the element starting at point.
@@ -2974,13 +3059,8 @@ recursion.  Allowed values are `headline', `greater-element',
 nil), secondary values will not be parsed, since they only
 nil), secondary values will not be parsed, since they only
 contain objects.
 contain objects.
 
 
-Optional argument SPECIAL, when non-nil, can be either `item',
-`section', `quote-section' or `table-row'.  `item' allows to
-parse item wise instead of plain-list wise, using STRUCTURE as
-the current list structure.  `section' (resp. `quote-section')
-will try to parse a section (resp. a quote section) before
-anything else, whereas `table-row' will look for rows within
-a table.
+Optional argument SPECIAL, when non-nil, can be either `section',
+`quote-section', `table-row' and `item'.
 
 
 If STRUCTURE isn't provided but SPECIAL is set to `item', it will
 If STRUCTURE isn't provided but SPECIAL is set to `item', it will
 be computed.
 be computed.
@@ -3013,8 +3093,13 @@ it is quicker than its counterpart, albeit more restrictive."
        ;; Headline.
        ;; Headline.
        ((org-with-limited-levels (org-at-heading-p))
        ((org-with-limited-levels (org-at-heading-p))
         (org-element-headline-parser raw-secondary-p))
         (org-element-headline-parser raw-secondary-p))
-       ;; Section (must be checked after headline)
+       ;; Section (must be checked after headline).
        ((eq special 'section) (org-element-section-parser))
        ((eq special 'section) (org-element-section-parser))
+       ;; Planning and Clock.
+       ((and (looking-at org-planning-or-clock-line-re))
+	(if (equal (match-string 1) org-clock-string)
+	    (org-element-clock-parser)
+	  (org-element-planning-parser)))
        ;; Non-recursive block.
        ;; Non-recursive block.
        ((when (looking-at org-element--element-block-re)
        ((when (looking-at org-element--element-block-re)
           (let ((type (upcase (match-string 1))))
           (let ((type (upcase (match-string 1))))
@@ -3254,8 +3339,8 @@ Assume buffer is in Org mode."
     (nconc (list 'org-data nil)
     (nconc (list 'org-data nil)
 	   (org-element-parse-elements
 	   (org-element-parse-elements
 	    (point-at-bol) (point-max)
 	    (point-at-bol) (point-max)
-	    ;; Start is section mode so text before the first headline
-	    ;; belongs to a section.
+	    ;; Start in `section' mode so text before the first
+	    ;; headline belongs to a section.
 	    'section nil granularity visible-only nil))))
 	    'section nil granularity visible-only nil))))
 
 
 (defun org-element-parse-secondary-string (string restriction)
 (defun org-element-parse-secondary-string (string restriction)
@@ -3378,10 +3463,10 @@ Nil values returned from FUN do not appear in the results."
   "Parse elements between BEG and END positions.
   "Parse elements between BEG and END positions.
 
 
 SPECIAL prioritize some elements over the others.  It can be set
 SPECIAL prioritize some elements over the others.  It can be set
-to `quote-section', `section' `item' or `table-row', which will
-focus search, respectively, on quote sections, sections, items
-and table-rows.  Moreover, when value is `item', STRUCTURE will
-be used as the current list structure.
+to `quote-section', `section' `item' or `table-row'.
+
+When value is `item', STRUCTURE will be used as the current list
+structure.
 
 
 GRANULARITY determines the depth of the recursion.  See
 GRANULARITY determines the depth of the recursion.  See
 `org-element-parse-buffer' for more information.
 `org-element-parse-buffer' for more information.
@@ -3425,13 +3510,13 @@ Elements are accumulated into ACC."
 		      (eq type 'headline)))
 		      (eq type 'headline)))
 	     (org-element-parse-elements
 	     (org-element-parse-elements
 	      cbeg (org-element-property :contents-end element)
 	      cbeg (org-element-property :contents-end element)
-	      ;; Possibly move to a special mode.
+	      ;; Possibly switch to a special mode.
 	      (case type
 	      (case type
 		(headline
 		(headline
 		 (if (org-element-property :quotedp element) 'quote-section
 		 (if (org-element-property :quotedp element) 'quote-section
 		   'section))
 		   'section))
-		(table 'table-row)
-		(plain-list 'item))
+		(plain-list 'item)
+		(table 'table-row))
 	      (org-element-property :structure element)
 	      (org-element-property :structure element)
 	      granularity visible-only (nreverse element)))
 	      granularity visible-only (nreverse element)))
 	    ;; Case 3.  ELEMENT has contents.  Parse objects inside,
 	    ;; Case 3.  ELEMENT has contents.  Parse objects inside,

+ 30 - 2
testing/lisp/test-org-element.el

@@ -572,6 +572,21 @@ Paragraph \\alpha."
 		  "#+CALL: test[:results output]()[:results html]")
 		  "#+CALL: test[:results output]()[:results html]")
 		 "#+CALL: test[:results output]()[:results html]\n")))
 		 "#+CALL: test[:results output]()[:results html]\n")))
 
 
+(ert-deftest test-org-element/clock-interpreter ()
+  "Test clock interpreter."
+  ;; Running clock.
+  (should
+   (equal (let ((org-clock-string "CLOCK:"))
+	    (org-test-parse-and-interpret "CLOCK: [2012-01-01 sun. 00:01]"))
+	  "CLOCK: [2012-01-01 sun. 00:01]\n"))
+  ;; Closed clock.
+  (should
+   (equal
+    (let ((org-clock-string "CLOCK:"))
+      (org-test-parse-and-interpret "
+CLOCK: [2012-01-01 sun. 00:01]--[2012-01-01 sun. 00:02] =>  0:01"))
+    "CLOCK: [2012-01-01 sun. 00:01]--[2012-01-01 sun. 00:02] =>  0:01\n")))
+
 (ert-deftest test-org-element/comment-interpreter ()
 (ert-deftest test-org-element/comment-interpreter ()
   "Test comment interpreter."
   "Test comment interpreter."
   ;; Regular comment.
   ;; Regular comment.
@@ -614,8 +629,21 @@ Paragraph \\alpha."
 (ert-deftest test-org-element/latex-environment-interpreter ()
 (ert-deftest test-org-element/latex-environment-interpreter ()
   "Test latex environment interpreter."
   "Test latex environment interpreter."
   (should (equal (org-test-parse-and-interpret
   (should (equal (org-test-parse-and-interpret
-		  "\begin{equation}\n1+1=2\n\end{equation}")
-		 "\begin{equation}\n1+1=2\n\end{equation}\n")))
+		  "\\begin{equation}\n1+1=2\n\\end{equation}")
+		 "\\begin{equation}\n1+1=2\n\\end{equation}\n")))
+
+(ert-deftest test-org-element/planning-interpreter ()
+  "Test planning interpreter."
+  (let ((org-closed-string "CLOSED:")
+	(org-deadline-string "DEADLINE:")
+	(org-scheduled-string "SCHEDULED:"))
+    (should
+     (equal
+      (org-test-parse-and-interpret
+       "* Headline
+CLOSED: <2012-01-01> DEADLINE: <2012-01-01> SCHEDULED: <2012-01-01>")
+      "* Headline
+CLOSED: <2012-01-01> DEADLINE: <2012-01-01> SCHEDULED: <2012-01-01>\n"))))
 
 
 (ert-deftest test-org-element/property-drawer-interpreter ()
 (ert-deftest test-org-element/property-drawer-interpreter ()
   "Test property drawer interpreter."
   "Test property drawer interpreter."