From 2c28183f8803d813970fcbf030c9001300544480 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gustav=20Wikstr=C3=B6m?= <gustav@whil.se>
Date: Sun, 26 May 2019 21:46:31 +0200
Subject: [PATCH 1/2] New top level document concept and minor restructure for
 keywords

Top-level document element and accompanying modifications through the
system to make it work and fit together nicely.

Also some restructuring in code that works with keywords, since most
keywords relate to the document and it helps to make it explicit in
code which keywords do that and which keywords don't.

* lisp/org.el

** (new) org-keyword-regexp
Addition of regexp for keywords.  Let's try to use parameters and
constants instead of hardcoding these things all over the place...

** (removed) org-options-keywords.
This naming of this list is incongruent with the manual and the
concepts used throughout org-mode.  It also fits better in
org-element. So I'm removing this list in favour of new lists for
keywords in that file.

** (renamed, modified) org--setup-collect-keywords -> org-collect-keywords
Renamed and generalized org--setup-collect-keywords to make it work
for multiple purposes.  Is not limited to a fixed set of keywords any
longer.  New name: org-collect-keywords.

** (renamed, modified) org-make-options-regexp -> org-make-keyword-regexp
The EXTRA argument is never used so it is removed.  No need for
potential (or old?) functionatlity that adds complexity.  Can be used
together with org-collect-keywords to speed up processing.

** (renamed) org-set-regexps-and-options -> org-set-regexps-and-keywords
Renamed to more propertly match what it is doing.

* lisp/org-element.el
Addition of document as a concept and as a top-level node in the
content of org-data.  The content of a document element is everything
that previously was the content of org-data.

** org-element-*-keywords
Instead of org-options-keywords, a couple of lists are added to
org-element for keywords.  Multiple lists are used to indicate
different use-cases for particular sets of keywords.

*** (new) org-element-document-keywords
Keywords in this constant are those that apply for the whole document.

*** (new) org-element-export-keywords
List of keywords branded as export keywords.  These keywords are
supposed to be supported by all export backends.

*** (new) org-element-inline-keywords
List of keywords that provide content at a certain position in the
outline.

*** (new) org-element--get-document-keywords
Parses a document for keywords that are not dependent on their
position and should be regarded as applying to the whole document.

Return document and document export keywords based on reworked
function in org.el; org-collect-keywords.

** (modified) org-element-keyword-parser
Uses (new) org-keyword-regexp instead of hardcoding it's own regexp.

* lisp/ox.el
Fixing code so that it respects the new document element and uses
org-element functions instead of parsing org-element-contents itself.

* lisp/ob-core.el
Unused declaration for function, org-make-options-regexp, is removed.
It is removed since the function was renamed in lisp/org.el and found
here due to a dependency check.

* lisp/org-pcomplete.el
Cascading fixes due to cleanup in org and org-element.

* testing/lisp/test-org-element.el
Fix existing tests so they care for the document element.

* org.el
---
 lisp/ob-core.el                  |   1 -
 lisp/org-element.el              | 122 +++++++++++++++++++++++++------
 lisp/org-pcomplete.el            |  13 ++--
 lisp/org.el                      |  59 ++++++++-------
 lisp/ox.el                       |  33 +++++----
 testing/lisp/test-org-element.el |   4 +-
 testing/lisp/test-org.el         |   2 +-
 7 files changed, 159 insertions(+), 75 deletions(-)

diff --git a/lisp/ob-core.el b/lisp/ob-core.el
index 97ec18fd1..a8366ee33 100644
--- a/lisp/ob-core.el
+++ b/lisp/ob-core.el
@@ -66,7 +66,6 @@
 (declare-function org-list-to-generic "org-list" (LIST PARAMS))
 (declare-function org-list-to-lisp "org-list" (&optional delete))
 (declare-function org-macro-escape-arguments "org-macro" (&rest args))
-(declare-function org-make-options-regexp "org" (kwds &optional extra))
 (declare-function org-mark-ring-push "org" (&optional pos buffer))
 (declare-function org-narrow-to-subtree "org" ())
 (declare-function org-next-block "org" (arg &optional backward block-regexp))
diff --git a/lisp/org-element.el b/lisp/org-element.el
index 56b3cc413..cdd3eb67f 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -41,7 +41,10 @@
 ;; The next part implements a parser and an interpreter for each
 ;; element and object type in Org syntax.
 ;;
-;; The following part creates a fully recursive buffer parser.  It
+;; Org-element can parse org-mode documents.  The top-node in the
+;; parse-tree will always have TYPE `org-data' and PROPERTIES nil.
+;;
+;; The following part creates a fully recursive org-mode parser.  It
 ;; also provides a tool to map a function to elements or objects
 ;; matching some criteria in the parse tree.  Functions of interest
 ;; are `org-element-parse-buffer', `org-element-map' and, to a lesser
@@ -92,6 +95,7 @@
 (defvar org-edit-src-content-indentation)
 (defvar org-emph-re)
 (defvar org-emphasis-regexp-components)
+(defvar org-keyword-regexp)
 (defvar org-keyword-time-not-clock-regexp)
 (defvar org-match-substring-regexp)
 (defvar org-odd-levels-only)
@@ -220,8 +224,8 @@ specially in `org-element--object-lex'.")
   (org-element-cache-reset 'all))
 
 (defconst org-element-all-elements
-  '(babel-call center-block clock comment comment-block diary-sexp drawer
-	       dynamic-block example-block export-block fixed-width
+  '(babel-call center-block clock comment comment-block diary-sexp document
+	       drawer dynamic-block example-block export-block fixed-width
 	       footnote-definition headline horizontal-rule inlinetask item
 	       keyword latex-environment node-property paragraph plain-list
 	       planning property-drawer quote-block section
@@ -229,9 +233,9 @@ specially in `org-element--object-lex'.")
   "Complete list of element types.")
 
 (defconst org-element-greater-elements
-  '(center-block drawer dynamic-block footnote-definition headline inlinetask
-		 item plain-list property-drawer quote-block section
-		 special-block table)
+  '(center-block document drawer dynamic-block footnote-definition headline
+		 inlinetask item plain-list property-drawer quote-block
+		 section special-block table)
   "List of recursive element types aka Greater Elements.")
 
 (defconst org-element-all-objects
@@ -250,6 +254,27 @@ specially in `org-element--object-lex'.")
   (append org-element-recursive-objects '(paragraph table-row verse-block))
   "List of object or element types that can directly contain objects.")
 
+(defconst org-element-document-keywords
+  '("ARCHIVE" "BIND" "CATEGORY" "COLUMNS" "CONSTANTS" "FILETAGS" "LINK"
+    "MACRO" "OPTIONS" "PRIORITIES" "PROPERTY" "SEQ_TODO" "SETUPFILE"
+    "STARTUP" "TAGS" "TODO" "TYP_TODO")
+  "List of supported keywords that are used to configure document
+  global options and properties.")
+
+(defconst org-element-export-keywords
+  '("AUTHOR" "CREATOR" "DATE" "EMAIL" "EXCLUDE_TAGS" "LANGUAGE"
+    "SELECT_TAGS" "TITLE" "EXPORT_FILE_NAME")
+  "List of keywords that are mentioned in the manual as something
+  that should be supported by all export backends.  More keywords
+  might exist that are specific for certain backends.")
+
+(defconst org-element-inline-keywords
+  '("INCLUDE" "TOC" "INDEX" "ASCII" "HTML" "LATEX" "ODT" "TEXINFO" "BEAMER")
+  "List of keywords that provide content at certain positions in
+  the outline.  These keywords are considered as a part of the
+  document outline rather than as properties of the document
+  element.")
+
 (defconst org-element-affiliated-keywords
   '("CAPTION" "DATA" "HEADER" "HEADERS" "LABEL" "NAME" "PLOT" "RESNAME" "RESULT"
     "RESULTS" "SOURCE" "SRCNAME" "TBLNAME")
@@ -726,6 +751,51 @@ Assume point is at the beginning of the block."
 CONTENTS is the contents of the element."
   (format "#+begin_center\n%s#+end_center" contents))
 
+;;;; Document
+
+(defun org-element--get-document-keywords ()
+  "Return keywords that associate with the document.
+Returns a plist with one key, `:document-keywords', and an alist
+of all keywords related to the whole document with their
+corresponding values.  Does not expand setupfile keywords to get
+inherited properties."
+  (let ((document-keywords (org-collect-keywords
+			    (org-make-keyword-regexp
+			     org-element-document-keywords)
+			    nil nil t))
+	(export-keywords (org-collect-keywords
+			  (org-make-keyword-regexp
+			   org-element-export-keywords)
+			  nil nil t)))
+    (list :document-keywords document-keywords
+	  :export-keywords export-keywords)))
+
+(defun org-element-document-parser ()
+  "Parse a document for it's settings and properties.
+Return a list whose CAR is `document' and CDR is a plist
+containing `:buffer', `:file', `:level', `:contents-begin',
+`:contents-end' and `:post-blank'.
+
+In addition to the above, the plist also contains configurations
+and properties that applies for the whole document, coming from
+document keywords."
+  (save-excursion
+    (list 'document
+	  (nconc
+	   (list :buffer (current-buffer)
+		 :file buffer-file-name
+		 :level 0
+		 :contents-begin (point-min)
+		 :contents-end (point-max)
+		 :begin (point-min)
+		 :end (point-max)
+		 :post-blank 0)
+	   (org-element--get-document-keywords)))))
+
+(defun org-element-document-interpreter (_ contents)
+  "Interpret document element as Org syntax.
+CONTENTS is the contents of the element."
+  contents)
 
 ;;;; Drawer
 
@@ -2195,10 +2265,9 @@ containing `:key', `:value', `:begin', `:end', `:post-blank' and
     ;; this corner case.
     (let ((begin (or (car affiliated) (point)))
 	  (post-affiliated (point))
-	  (key (progn (looking-at "[ \t]*#\\+\\(\\S-*\\):")
+	  (key (progn (looking-at org-keyword-regexp)
 		      (upcase (match-string-no-properties 1))))
-	  (value (org-trim (buffer-substring-no-properties
-			    (match-end 0) (point-at-eol))))
+	  (value (org-trim (match-string-no-properties 2)))
 	  (pos-before-blank (progn (forward-line) (point)))
 	  (end (progn (skip-chars-forward " \r\t\n" limit)
 		      (if (eobp) (point) (line-beginning-position)))))
@@ -3847,7 +3916,7 @@ recursion.  Allowed values are `headline', `greater-element',
 nil), secondary values will not be parsed, since they only
 contain objects.
 
-Optional argument MODE, when non-nil, can be either
+Optional argument MODE, when non-nil, can be either `document',
 `first-section', `section', `planning', `item', `node-property'
 and `table-row'.
 
@@ -3863,6 +3932,9 @@ element it has to parse."
 	  ;; `org-element-secondary-value-alist'.
 	  (raw-secondary-p (and granularity (not (eq granularity 'object)))))
       (cond
+       ;; Document
+       ((eq mode 'document)
+	(org-element-document-parser))
        ;; Item.
        ((eq mode 'item)
 	(org-element-item-parser limit structure raw-secondary-p))
@@ -3875,6 +3947,8 @@ element it has to parse."
         (org-element-headline-parser limit raw-secondary-p))
        ;; Sections (must be checked after headline).
        ((eq mode 'section) (org-element-section-parser limit))
+       ;; First-section.  Special mode for dealing with the first section
+       ;; in a document, which might or might not be before first headline.
        ((eq mode 'first-section)
 	(org-element-section-parser
 	 (or (save-excursion (org-with-limited-levels (outline-next-heading)))
@@ -4121,9 +4195,10 @@ This function assumes that current major mode is `org-mode'."
     (org-skip-whitespace)
     (org-element--parse-elements
      (point-at-bol) (point-max)
-     ;; Start in `first-section' mode so text before the first
-     ;; headline belongs to a section.
-     'first-section nil granularity visible-only (list 'org-data nil))))
+     ;; Start in `document' mode so propeties and potential other
+     ;; content before the first headline belongs to the document
+     ;; node.
+     'document nil granularity visible-only (list 'org-data nil))))
 
 (defun org-element-parse-secondary-string (string restriction &optional parent)
   "Recursively parse objects in STRING and return structure.
@@ -4324,11 +4399,12 @@ looking into captions:
   "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."
+otherwise.  Modes can be either `document', `first-section',
+`item', `node-property', `planning', `property-drawer',
+`section', `table-row' or nil."
   (if parentp
       (pcase type
+	(`document 'first-section)
 	(`headline 'section)
 	(`inlinetask 'planning)
 	(`plain-list 'item)
@@ -4346,8 +4422,8 @@ otherwise.  Modes can be either `first-section', `item',
   "Parse elements between BEG and END positions.
 
 MODE prioritizes some elements over the others.  It can be set to
-`first-section', `section', `planning', `item', `node-property'
-or `table-row'.
+`document', `first-section', `section', `planning', `item',
+`node-property' or `table-row'.
 
 When value is `item', STRUCTURE will be used as the current list
 structure.
@@ -4375,7 +4451,8 @@ Elements are accumulated into ACC."
 	(let* ((element (org-element--current-element
 			 end granularity mode structure))
 	       (type (org-element-type element))
-	       (cbeg (org-element-property :contents-begin element)))
+	       (cbeg (org-element-property :contents-begin element))
+	       (cend (org-element-property :contents-end element)))
 	  (goto-char (org-element-property :end element))
 	  ;; Visible only: skip invisible parts between siblings.
 	  (when (and visible-only (org-invisible-p2))
@@ -4392,9 +4469,10 @@ Elements are accumulated into ACC."
 		 (or (memq granularity '(element object nil))
 		     (and (eq granularity 'greater-element)
 			  (eq type 'section))
-		     (eq type 'headline)))
+		     (eq type 'headline)
+		     (eq type 'document)))
 	    (org-element--parse-elements
-	     cbeg (org-element-property :contents-end element)
+	     cbeg cend
 	     ;; Possibly switch to a special mode.
 	     (org-element--next-mode type t)
 	     (and (memq type '(item plain-list))
@@ -4404,7 +4482,7 @@ Elements are accumulated into ACC."
 	   ;; GRANULARITY allows it.
 	   ((memq granularity '(object nil))
 	    (org-element--parse-objects
-	     cbeg (org-element-property :contents-end element) element
+	     cbeg cend element
 	     (org-element-restriction type))))
 	  (push (org-element-put-property element :parent acc) elements)
 	  ;; Update mode.
diff --git a/lisp/org-pcomplete.el b/lisp/org-pcomplete.el
index 5a7fb57c9..8ba9c0714 100644
--- a/lisp/org-pcomplete.el
+++ b/lisp/org-pcomplete.el
@@ -51,6 +51,9 @@
 (defvar org-default-priority)
 (defvar org-drawer-regexp)
 (defvar org-element-affiliated-keywords)
+(defvar org-element-document-keywords)
+(defvar org-element-export-keywords)
+(defvar org-element-inline-keywords)
 (defvar org-entities)
 (defvar org-export-default-language)
 (defvar org-export-exclude-tags)
@@ -60,7 +63,6 @@
 (defvar org-link-abbrev-alist)
 (defvar org-link-abbrev-alist-local)
 (defvar org-lowest-priority)
-(defvar org-options-keywords)
 (defvar org-outline-regexp)
 (defvar org-property-re)
 (defvar org-startup-options)
@@ -197,10 +199,11 @@ When completing for #+STARTUP, for example, this function returns
   (require 'org-element)
   (pcomplete-here
    (org-pcomplete-case-double
-    (append (mapcar (lambda (keyword) (concat keyword " "))
-		    org-options-keywords)
-	    (mapcar (lambda (keyword) (concat keyword ": "))
-		    org-element-affiliated-keywords)
+    (append (mapcar (lambda (keyword) (concat keyword ": "))
+		    (append org-element-document-keywords
+			    org-element-export-keywords
+			    org-element-inline-keywords
+			    org-element-affiliated-keywords))
 	    (let (block-names)
 	      (dolist (name
 		       '("CENTER" "COMMENT" "EXAMPLE" "EXPORT" "QUOTE" "SRC"
diff --git a/lisp/org.el b/lisp/org.el
index db957a112..358989070 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -350,6 +350,9 @@ FULL is given."
 
 
 ;;; Syntax Constants
+;;;; Keyword
+(defconst org-keyword-regexp "^[ \t]*#\\+\\(\\S-+?\\):[ \t]*\\(.*\\)$"
+  "Regular expression for keyword-lines")
 
 ;;;; Block
 
@@ -4304,13 +4307,13 @@ See `org-tag-alist' for their structure."
       ;; Preserve order of ALIST1.
       (append (nreverse to-add) alist2)))))
 
-(defun org-set-regexps-and-options (&optional tags-only)
-  "Precompute regular expressions used in the current buffer.
+(defun org-set-regexps-and-keywords (&optional tags-only)
+  "Precompute regular expressions and set variables based on keywords.
 When optional argument TAGS-ONLY is non-nil, only compute tags
 related expressions."
   (when (derived-mode-p 'org-mode)
-    (let ((alist (org--setup-collect-keywords
-		  (org-make-options-regexp
+    (let ((alist (org-collect-keywords
+		  (org-make-keyword-regexp
 		   (append '("FILETAGS" "TAGS" "SETUPFILE")
 			   (and (not tags-only)
 				'("ARCHIVE" "CATEGORY" "COLUMNS" "CONSTANTS"
@@ -4465,16 +4468,17 @@ related expressions."
 		      "[ \t]*$"))
 	(org-compute-latex-and-related-regexp)))))
 
-(defun org--setup-collect-keywords (regexp &optional files alist)
-  "Return setup keywords values as an alist.
-
-REGEXP matches a subset of setup keywords.  FILES is a list of
-file names already visited.  It is used to avoid circular setup
-files.  ALIST, when non-nil, is the alist computed so far.
-
-Return value contains the following keys: `archive', `category',
-`columns', `constants', `filetags', `link', `priorities',
-`property', `scripts', `startup', `tags' and `todo'."
+(defun org-collect-keywords (&optional regexp files alist no-setupfile-expansion)
+  "Return keywords values as an alist.
+
+REGEXP can be provided to override default keyword regexp if only
+a subset of keywords are interesting, for performance purposes.
+FILES is a list of file names already visited.  It is used to
+avoid circular setup files.  ALIST, when non-nil, is the alist
+computed so far.  NO-SETUPFILE-EXPANSION can be used to not
+recurse into setupfiles.  In that case the `setupfile' keyword
+and value will be returned instead, if it exist."
+  (setq regexp (or regexp org-keyword-regexp))
   (org-with-wide-buffer
    (goto-char (point-min))
    (let ((case-fold-search t))
@@ -4554,7 +4558,9 @@ Return value contains the following keys: `archive', `category',
 		 (if todo (push value (cdr todo))
 		   (push (list 'todo value) alist))))
 	      ((equal key "SETUPFILE")
-	       (unless buffer-read-only ; Do not check in Gnus messages.
+	       (if (or buffer-read-only ; Do not check in Gnus messages.
+		       no-setupfile-expansion)
+		   (push (cons 'setupfile value) alist)
 		 (let ((f (and (org-string-nw-p value)
 			       (expand-file-name (org-strip-quotes value)))))
 		   (when (and f (file-readable-p f) (not (member f files)))
@@ -4565,8 +4571,10 @@ Return value contains the following keys: `archive', `category',
 			     ;; Fake Org mode to benefit from cache
 			     ;; without recurring needlessly.
 			     (let ((major-mode 'org-mode))
-			       (org--setup-collect-keywords
-				regexp (cons f files) alist)))))))))))))))
+			       (org-collect-keywords
+				regexp (cons f files) alist))))))))
+	      (t
+	       (push (cons (make-symbol (downcase key)) value) alist)))))))))
   alist)
 
 (defun org-tag-string-to-alist (s)
@@ -4843,7 +4851,7 @@ The following commands are available:
      (vconcat (mapcar (lambda (c) (make-glyph-code c 'org-ellipsis))
 		      org-ellipsis)))
     (setq buffer-display-table org-display-table))
-  (org-set-regexps-and-options)
+  (org-set-regexps-and-keywords)
   (org-set-font-lock-defaults)
   (when (and org-tag-faces (not org-tags-special-faces-re))
     ;; tag faces set outside customize.... force initialization.
@@ -9722,13 +9730,6 @@ keywords relative to each registered export back-end."
 	;; Back-end options.
 	(push (nth 1 option-entry) keywords)))))
 
-(defconst org-options-keywords
-  '("ARCHIVE:" "AUTHOR:" "BIND:" "CATEGORY:" "COLUMNS:" "CREATOR:" "DATE:"
-    "DESCRIPTION:" "DRAWERS:" "EMAIL:" "EXCLUDE_TAGS:" "FILETAGS:" "INCLUDE:"
-    "INDEX:" "KEYWORDS:" "LANGUAGE:" "MACRO:" "OPTIONS:" "PROPERTY:"
-    "PRIORITIES:" "SELECT_TAGS:" "SEQ_TODO:" "SETUPFILE:" "STARTUP:" "TAGS:"
-    "TITLE:" "TODO:" "TYP_TODO:" "SELECT_TAGS:" "EXCLUDE_TAGS:"))
-
 (defcustom org-structure-template-alist
   '(("a" . "export ascii")
     ("c" . "center")
@@ -15870,7 +15871,7 @@ When a buffer is unmodified, it is just killed.  When modified, it is saved
 	      (org-check-agenda-file file)
 	      (set-buffer (org-get-agenda-file-buffer file)))
 	    (widen)
-	    (org-set-regexps-and-options 'tags-only)
+	    (org-set-regexps-and-keywords 'tags-only)
 	    (setq pos (point))
 	    (or (memq 'category org-agenda-ignore-properties)
 		(org-refresh-category-properties))
@@ -21156,13 +21157,11 @@ modified."
 		      (org-do-remove-indentation))))))))
     (funcall unindent-tree (org-element-contents parse-tree))))
 
-(defun org-make-options-regexp (kwds &optional extra)
+(defun org-make-keyword-regexp (kwds)
   "Make a regular expression for keyword lines.
-KWDS is a list of keywords, as strings.  Optional argument EXTRA,
-when non-nil, is a regexp matching keywords names."
+KWDS is a list of keywords, as strings."
   (concat "^[ \t]*#\\+\\("
 	  (regexp-opt kwds)
-	  (and extra (concat (and kwds "\\|") extra))
 	  "\\):[ \t]*\\(.*\\)"))
 
 
diff --git a/lisp/ox.el b/lisp/ox.el
index 1b579f50e..592cb9100 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -1741,13 +1741,15 @@ Return updated plist."
 DATA is parsed tree as returned by `org-element-parse-buffer'.
 OPTIONS is a plist holding export options."
   (catch 'exit
-    (let ((min-level 10000))
-      (dolist (datum (org-element-contents data))
-	(when (and (eq (org-element-type datum) 'headline)
-		   (not (org-element-property :footnote-section-p datum))
-		   (not (memq datum (plist-get options :ignore-list))))
-	  (setq min-level (min (org-element-property :level datum) min-level))
-	  (when (= min-level 1) (throw 'exit 1))))
+    (let ((ignore-list (plist-get options :ignore-list))
+          (min-level 10000))
+      (org-element-map data 'headline
+        (lambda (hl)
+          (when (and
+                 (not (org-element-property :footnote-section-p hl))
+                 (not (memq hl ignore-list)))
+            (setq min-level (min (org-element-property :level hl) min-level))
+            (when (= min-level 1) (throw 'exit 1)))))
       ;; If no headline was found, for the sake of consistency, set
       ;; minimum level to 1 nonetheless.
       (if (= min-level 10000) 1 min-level))))
@@ -1934,7 +1936,8 @@ not exported."
 INFO is a plist containing export directives."
   (let ((type (org-element-type blob)))
     ;; Return contents only for complete parse trees.
-    (if (eq type 'org-data) (lambda (_datum contents _info) contents)
+    (if (member type '(org-data document))
+	(lambda (_datum contents _info) contents)
       (let ((transcoder (cdr (assq type (plist-get info :translate-alist)))))
 	(and (functionp transcoder) transcoder)))))
 
@@ -2755,9 +2758,11 @@ from tree."
     ;; If a select tag is active, also ignore the section before the
     ;; first headline, if any.
     (when selected
-      (let ((first-element (car (org-element-contents data))))
-	(when (eq (org-element-type first-element) 'section)
-	  (org-element-extract-element first-element))))
+      (org-element-map data 'document
+	(lambda (document)
+	  (let ((first-element (car (org-element-contents document))))
+	    (when (eq (org-element-type first-element) 'section)
+	      (org-element-extract-element first-element))))))
     ;; Prune tree and communication channel.
     (funcall walk-data data)
     (dolist (entry (append
@@ -3040,7 +3045,7 @@ Return code as a string."
 				parsed-keywords)
 	 ;; Refresh buffer properties and radio targets after previous
 	 ;; potentially invasive changes.
-	 (org-set-regexps-and-options)
+	 (org-set-regexps-and-keywords)
 	 (org-update-radio-target-regexp)
 	 ;;  Possibly execute Babel code.  Re-run a macro expansion
 	 ;;  specifically for {{{results}}} since inline source blocks
@@ -3049,7 +3054,7 @@ Return code as a string."
 	 (when org-export-use-babel
 	   (org-babel-exp-process-buffer)
 	   (org-macro-replace-all '(("results" . "$1")) parsed-keywords)
-	   (org-set-regexps-and-options)
+	   (org-set-regexps-and-keywords)
 	   (org-update-radio-target-regexp))
 	 ;; Run last hook with current back-end's name as argument.
 	 ;; Update buffer properties and radio targets one last time
@@ -3058,7 +3063,7 @@ Return code as a string."
 	 (save-excursion
 	   (run-hook-with-args 'org-export-before-parsing-hook
 			       (org-export-backend-name backend)))
-	 (org-set-regexps-and-options)
+	 (org-set-regexps-and-keywords)
 	 (org-update-radio-target-regexp)
 	 ;; Update communication channel with environment.
 	 (setq info
diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el
index f2ab38031..2a1716134 100644
--- a/testing/lisp/test-org-element.el
+++ b/testing/lisp/test-org-element.el
@@ -198,7 +198,7 @@ Some other text
    (equal '(org-data nil)
 	  (org-test-with-temp-text "* Headline"
 	    (let* ((tree (org-element-parse-buffer))
-		   (element (org-element-map tree 'headline 'identity nil t)))
+		   (element (org-element-map tree 'document 'identity nil t)))
 	      (org-element-extract-element element)
 	      tree))))
   ;; Extract an element.
@@ -3653,7 +3653,7 @@ Text
 	      "* H1\n** H2\n#+BEGIN_CENTER\n*bold<point>*\n#+END_CENTER"
 	    (mapcar #'car (org-element-lineage (org-element-context))))))
   (should
-   (equal '(paragraph center-block section headline headline org-data)
+   (equal '(paragraph center-block section headline headline document org-data)
 	  (org-test-with-temp-text
 	      "* H1\n** H2\n#+BEGIN_CENTER\n*bold<point>*\n#+END_CENTER"
 	    (mapcar #'car
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 215d8fdb0..01e965ea7 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -2095,7 +2095,7 @@ SCHEDULED: <2014-03-04 tue.>"
 ;;; Keywords
 
 (ert-deftest test-org/set-regexps-and-options ()
-  "Test `org-set-regexps-and-options' specifications."
+  "Test `org-set-regexps-and-keywords' specifications."
   ;; TAGS keyword.
   (should
    (equal '(("A"))
-- 
2.17.1

