Browse Source

org-element: Fix top-level property-drawer parsing

* lisp/org-element.el (org-element-comment-parser): Top level comments
do not have affiliated keywords.
(org-element--current-element): Properly parse top-level comments and
top-level properties drawer.
(org-element--next-mode): Change signature.
(org-element--parse-elements): Apply signature change.
(org-element--parse-to): Apply signature change.  Look for top-level
comments or properties drawer.
* testing/lisp/test-org-element.el (test-org-element/property-drawer-parser):
Add comment.

Initially, `org-element--current-element' would defer to
`org-get-property-block' to validate a property-drawer, which is the
wrong way to look at the low-level Org Element library. Also, it would
allow affiliated keywords, which is not possible.
Nicolas Goaziou 6 years ago
parent
commit
af99544286
2 changed files with 45 additions and 35 deletions
  1. 43 35
      lisp/org-element.el
  2. 2 0
      testing/lisp/test-org-element.el

+ 43 - 35
lisp/org-element.el

@@ -1820,7 +1820,7 @@ containing `:begin', `:end', `:value', `:post-blank',
 
 
 Assume point is at comment beginning."
 Assume point is at comment beginning."
   (save-excursion
   (save-excursion
-    (let* ((begin (car affiliated))
+    (let* ((begin (or (car affiliated) (point)))
 	   (post-affiliated (point))
 	   (post-affiliated (point))
 	   (value (prog2 (looking-at "[ \t]*# ?")
 	   (value (prog2 (looking-at "[ \t]*# ?")
 		      (buffer-substring-no-properties
 		      (buffer-substring-no-properties
@@ -3823,12 +3823,6 @@ Assume point is at the first equal sign marker."
 ;; `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.
 ;; point.
-;;
-;; `org-element--current-element' makes use of special modes.  They
-;; are activated for fixed element chaining (e.g., `plain-list' >
-;; `item') or fixed conditional element chaining (e.g., `headline' >
-;; `section').  Special modes are: `first-section', `item',
-;; `node-property', `section' and `table-row'.
 
 
 (defun org-element--current-element (limit &optional granularity mode structure)
 (defun org-element--current-element (limit &optional granularity mode structure)
   "Parse the element starting at point.
   "Parse the element starting at point.
@@ -3848,8 +3842,9 @@ nil), secondary values will not be parsed, since they only
 contain objects.
 contain objects.
 
 
 Optional argument MODE, when non-nil, can be either
 Optional argument MODE, when non-nil, can be either
-`first-section', `section', `planning', `item', `node-property'
-and `table-row'.
+`first-section', `item', `node-property', `planning',
+`property-drawer', `section', `table-row', or `top-comment'.
+
 
 
 If STRUCTURE isn't provided but MODE is set to `item', it will be
 If STRUCTURE isn't provided but MODE is set to `item', it will be
 computed.
 computed.
@@ -3879,15 +3874,22 @@ element it has to parse."
 	(org-element-section-parser
 	(org-element-section-parser
 	 (or (save-excursion (org-with-limited-levels (outline-next-heading)))
 	 (or (save-excursion (org-with-limited-levels (outline-next-heading)))
 	     limit)))
 	     limit)))
+       ;; Top-level comments.  Those cannot have affiliated keywords.
+       ((and (eq mode 'top-comment) (looking-at "#\\(?: \\|$\\)"))
+	(org-element-comment-parser limit nil))
        ;; Planning.
        ;; Planning.
        ((and (eq mode 'planning)
        ((and (eq mode 'planning)
 	     (eq ?* (char-after (line-beginning-position 0)))
 	     (eq ?* (char-after (line-beginning-position 0)))
 	     (looking-at org-planning-line-re))
 	     (looking-at org-planning-line-re))
 	(org-element-planning-parser limit))
 	(org-element-planning-parser limit))
        ;; Property drawer.
        ;; Property drawer.
-       ((and (memq mode '(planning property-drawer))
-	     (eq ?* (char-after (line-beginning-position
-				 (if (eq mode 'planning) 0 -1))))
+       ((and (pcase mode
+	       (`planning (eq ?* (char-after (line-beginning-position 0))))
+	       ((or `property-drawer `top-comment)
+		(save-excursion
+		  (beginning-of-line 0)
+		  (not (looking-at "[[:blank:]]*$"))))
+	       (_ nil))
 	     (looking-at org-property-drawer-re))
 	     (looking-at org-property-drawer-re))
 	(org-element-property-drawer-parser limit))
 	(org-element-property-drawer-parser limit))
        ;; When not at bol, point is at the beginning of an item or
        ;; When not at bol, point is at the beginning of an item or
@@ -3910,9 +3912,6 @@ element it has to parse."
 	     ;; LaTeX Environment.
 	     ;; LaTeX Environment.
 	     ((looking-at org-element--latex-begin-environment)
 	     ((looking-at org-element--latex-begin-environment)
 	      (org-element-latex-environment-parser limit affiliated))
 	      (org-element-latex-environment-parser limit affiliated))
-	     ;; Property drawer (before first headline, else it's catched above).
-	     ((org-at-property-block-p)
-	      (org-element-property-drawer-parser limit))
 	     ;; Drawer.
 	     ;; Drawer.
 	     ((looking-at org-drawer-regexp)
 	     ((looking-at org-drawer-regexp)
 	      (org-element-drawer-parser limit affiliated))
 	      (org-element-drawer-parser limit affiliated))
@@ -4323,34 +4322,41 @@ looking into captions:
 ;; `org-element--object-lex' to find the next object in the current
 ;; `org-element--object-lex' to find the next object in the current
 ;; container.
 ;; container.
 
 
-(defsubst org-element--next-mode (type parentp)
-  "Return next special mode according to TYPE, or nil.
-TYPE is a symbol representing the type of an element or object
-containing next element if PARENTP is non-nil, or before it
-otherwise.  Modes can be either `first-section', `item',
-`node-property', `planning', `property-drawer', `section',
-`table-row' or nil."
-  (if parentp
+(defsubst org-element--next-mode (mode type parent?)
+  "Return next mode according to current one.
+
+MODE is a symbol representing the expectation about the next
+element or object.  Meaningful values are `first-section',
+`item', `node-property', `planning', `property-drawer',
+`section', `table-row', `top-comment', and nil.
+
+TYPE is the type of the current element or object.
+
+If PARENT? is non-nil, assume the next element or object will be
+located inside the current one.  "
+  (if parent?
       (pcase type
       (pcase type
 	(`headline 'section)
 	(`headline 'section)
+	(`first-section 'top-comment)
 	(`inlinetask 'planning)
 	(`inlinetask 'planning)
 	(`plain-list 'item)
 	(`plain-list 'item)
 	(`property-drawer 'node-property)
 	(`property-drawer 'node-property)
 	(`section 'planning)
 	(`section 'planning)
 	(`table 'table-row))
 	(`table 'table-row))
-    (pcase type
+    (pcase mode
       (`item 'item)
       (`item 'item)
       (`node-property 'node-property)
       (`node-property 'node-property)
-      (`planning 'property-drawer)
-      (`table-row 'table-row))))
+      ((and `planning (guard (eq type 'planning))) 'property-drawer)
+      (`table-row 'table-row)
+      ((and `top-comment (guard (eq type 'comment))) 'property-drawer))))
 
 
 (defun org-element--parse-elements
 (defun org-element--parse-elements
     (beg end mode structure granularity visible-only acc)
     (beg end mode structure granularity visible-only acc)
   "Parse elements between BEG and END positions.
   "Parse elements between BEG and END positions.
 
 
 MODE prioritizes some elements over the others.  It can be set to
 MODE prioritizes some elements over the others.  It can be set to
-`first-section', `section', `planning', `item', `node-property'
-or `table-row'.
+`first-section', `item', `node-property', `planning',
+`property-drawer', `section', `table-row', `top-comment', or nil.
 
 
 When value is `item', STRUCTURE will be used as the current list
 When value is `item', STRUCTURE will be used as the current list
 structure.
 structure.
@@ -4399,7 +4405,7 @@ Elements are accumulated into ACC."
 	    (org-element--parse-elements
 	    (org-element--parse-elements
 	     cbeg (org-element-property :contents-end element)
 	     cbeg (org-element-property :contents-end element)
 	     ;; Possibly switch to a special mode.
 	     ;; Possibly switch to a special mode.
-	     (org-element--next-mode type t)
+	     (org-element--next-mode mode type t)
 	     (and (memq type '(item plain-list))
 	     (and (memq type '(item plain-list))
 		  (org-element-property :structure element))
 		  (org-element-property :structure element))
 	     granularity visible-only element))
 	     granularity visible-only element))
@@ -4411,7 +4417,7 @@ Elements are accumulated into ACC."
 	     (org-element-restriction type))))
 	     (org-element-restriction type))))
 	  (push (org-element-put-property element :parent acc) elements)
 	  (push (org-element-put-property element :parent acc) elements)
 	  ;; Update mode.
 	  ;; Update mode.
-	  (setq mode (org-element--next-mode type nil))))
+	  (setq mode (org-element--next-mode mode type nil))))
       ;; Return result.
       ;; Return result.
       (apply #'org-element-set-contents acc (nreverse elements)))))
       (apply #'org-element-set-contents acc (nreverse elements)))))
 
 
@@ -5453,9 +5459,11 @@ the process stopped before finding the expected result."
         ;; element following headline above, or first element in
         ;; element following headline above, or first element in
         ;; buffer.
         ;; buffer.
         ((not cached)
         ((not cached)
-         (when (org-with-limited-levels (outline-previous-heading))
-           (setq mode 'planning)
-	   (forward-line))
+         (if (org-with-limited-levels (outline-previous-heading))
+             (progn
+	       (setq mode 'planning)
+	       (forward-line))
+	   (setq mode 'top-comment))
          (skip-chars-forward " \r\t\n")
          (skip-chars-forward " \r\t\n")
          (beginning-of-line))
          (beginning-of-line))
         ;; Cache returned exact match: return it.
         ;; Cache returned exact match: return it.
@@ -5524,7 +5532,7 @@ the process stopped before finding the expected result."
 	      ;; after it.
 	      ;; after it.
 	      ((and (<= elem-end pos) (/= (point-max) elem-end))
 	      ((and (<= elem-end pos) (/= (point-max) elem-end))
 	       (goto-char elem-end)
 	       (goto-char elem-end)
-	       (setq mode (org-element--next-mode type nil)))
+	       (setq mode (org-element--next-mode mode type nil)))
 	      ;; A non-greater element contains point: return it.
 	      ;; A non-greater element contains point: return it.
 	      ((not (memq type org-element-greater-elements))
 	      ((not (memq type org-element-greater-elements))
 	       (throw 'exit element))
 	       (throw 'exit element))
@@ -5552,7 +5560,7 @@ the process stopped before finding the expected result."
 				    (and (= cend pos) (= (point-max) pos)))))
 				    (and (= cend pos) (= (point-max) pos)))))
 		   (goto-char (or next cbeg))
 		   (goto-char (or next cbeg))
 		   (setq next nil
 		   (setq next nil
-			 mode (org-element--next-mode type t)
+			 mode (org-element--next-mode mode type t)
 			 parent element
 			 parent element
 			 end cend))))
 			 end cend))))
 	      ;; Otherwise, return ELEMENT as it is the smallest
 	      ;; Otherwise, return ELEMENT as it is the smallest

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

@@ -2120,6 +2120,8 @@ Outside list"
        (org-test-with-temp-text
        (org-test-with-temp-text
 	   "* H\nDEADLINE: <2014-03-04 tue.>\n<point>:PROPERTIES:\n:prop: value\n:END:"
 	   "* H\nDEADLINE: <2014-03-04 tue.>\n<point>:PROPERTIES:\n:prop: value\n:END:"
 	 (org-element-type (org-element-at-point)))))
 	 (org-element-type (org-element-at-point)))))
+  ;; Parse property drawer at the beginning of the document, possibly
+  ;; after some initial comments.
   (should
   (should
    (eq 'property-drawer
    (eq 'property-drawer
        (org-test-with-temp-text "<point>:PROPERTIES:\n:prop: value\n:END:"
        (org-test-with-temp-text "<point>:PROPERTIES:\n:prop: value\n:END:"