From 59fa97efff2b8ee50bda55e63903cf7db1f4af2b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gustav=20Wikstr=C3=B6m?= <gustav@whil.se>
Date: Sun, 26 May 2019 22:13:09 +0200
Subject: [PATCH 2/2] Org document property-drawers

* doc/org-manual.org

Describe the new possibility to define property-blocks on document
level, in addition to at headline level.

* etc/ORG-NEWS

Mention the change

* lisp/org.el

Code is modified to work with the concept of a node-level of 0 for
all text before the first headline.  Property drawers on
document-level, i.e node-level 0, work exactly the same as property
drawers on headlines.

Property drawers on document level can (only) be defined at the top of
a document.

** (Renamed) org-file-properties -> org-keyword-properties
Due to the fact that properties can be defined for the whole document
using property drawers this local variable needs a rename to make its
name less ambigous.

** (Modified) org-refresh-properties
Made to work also before first headline.

** (Modified) org-refresh-property
Made to work also before first headline.

** (Modified) org-refresh-category-properties
Made to work before first headline due to property drawers being
available there now!

** (Modified) org-get-property-block
Make it work also before first headline.

** (New) org-at-property-block-p
New function to validate if point is at the start of a property
block.

** (Modified) org-entry-properties
Make it work before first headline

** (Renamed) org-property-global-value -> org-property-global-or-keyword-value
Renamed to make its name match its functionality.

** (Modified) org-entry-get-with-inheritance
Make it work also before first headline.

** (Modified) org-entry-put
Make it work also before first headline.

** (Modified) org-insert-property-drawer
Make it work also before first headline.

** (New) org-back-to-heading-or-point-min
New function to make a document work as a level 0 node in the outline.

** (New) org-at-keyword-p
Predicate function to answer to if we're currently at a keyword line
or not.

** (New) org-up-heading-or-point-min
New function to make a document work as a level 0 node in the outline.

** (Modified) org-end-of-subtree
Make it work for the whole buffer, not just inside headlines. The end
of the buffer "subtree" is the end of the file.

* lisp/org-element.el

Add properties both from property drawer and property keywords to
document element. Drawer has precedence if properties are defined in
both.

Add post-blank property to document element  post-element is according
to ducumentation a property that all elements should have.

** (new) org-element--get-document-properties
Collect properties for whole documents from the property keywords and
from the document property drawer.

** (modified) org-element-document-parser
Add document properties to the document parser.

** (renamed) org-element--get-node-properties ->
   org-element--get-headline-properties
With the introduction of document property drawers while still
maintaining the concept of `node-property' for a property within a
property drawer, the function to get properties from headlines
property drawers need to be renamed to not be confusing.

(I suppose the concept of `node-property' could be removed in favour
of the more general `property' concept.  I couldn't calculate the
side-effects of doing that though.  And it would mean making changes in
far more files)

** (modified) org-element--current-element
Can now detect property-blocks before first headline according to it's
positional rules.

* lisp/org-attach.el

** (Modified) org-attach
Make it possible to call the attachment dispatcher (org-attach) also
before the first headline, since document property drawers make
attachments possible for the whole document now.

* lisp/org-capture.el

Modified only due to rename of function in org.el.

* testing/lisp/test-org.el

Add tests to make sure the specification of document-level
property-blocks can be maintained over time!

Additions to existing:
- /insert-property-drawer
- /set-property
- /delete-property
- /delete-property-globally

Added:
- /at-property-p
- /at-property-block-p
- /get-property-block
- /entry-get
- /refresh-properties

* testing/examples/property-inheritance.org

Switch from property-keywords to a property-drawer in the testfile.
Functionality should be the same, but now using a document drawer
instead of property-keywords.

Reason for switching is that I'd like us to slowly depricate
property-keywords.

* testing/lisp/test-org-element.el

Addition to existing test definitions:
- /node-property
- /property-drawer-parser

* contrib/lisp/ox-taskjuggler.el

A comment is modified only due to rename of function in org.el.
---
 contrib/lisp/ox-taskjuggler.el            |   2 +-
 doc/org-manual.org                        |  27 +-
 etc/ORG-NEWS                              |   8 +
 lisp/org-attach.el                        |   2 +-
 lisp/org-capture.el                       |   4 +-
 lisp/org-element.el                       |  55 +++-
 lisp/org.el                               | 267 ++++++++++++-------
 testing/examples/property-inheritance.org |   6 +-
 testing/lisp/test-org-element.el          |  21 ++
 testing/lisp/test-org.el                  | 306 +++++++++++++++++++++-
 10 files changed, 576 insertions(+), 122 deletions(-)

diff --git a/contrib/lisp/ox-taskjuggler.el b/contrib/lisp/ox-taskjuggler.el
index 16a6a3a74..bc90b93a4 100644
--- a/contrib/lisp/ox-taskjuggler.el
+++ b/contrib/lisp/ox-taskjuggler.el
@@ -137,7 +137,7 @@
 ;;   :END:
 ;;
 ;;;; * TODO
-;;   - Look at org-file-properties, org-global-properties and
+;;   - Look at org-keyword-properties, org-global-properties and
 ;;     org-global-properties-fixed
 ;;   - What about property inheritance and org-property-inherit-p?
 ;;   - Use TYPE_TODO as an way to assign resources
diff --git a/doc/org-manual.org b/doc/org-manual.org
index eb0a1e939..3855b9543 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -4951,7 +4951,7 @@ with many examples, see [[*Matching tags and properties]].
 
 A property is a key-value pair associated with an entry.  Properties
 can be set so they are associated with a single entry, with every
-entry in a tree, or with every entry in an Org file.
+entry in a tree, or with the whole buffer.
 
 There are two main applications for properties in Org mode.  First,
 properties are like tags, but with a value.  Imagine maintaining
@@ -5015,8 +5015,13 @@ disks in a box like this:
   :END:
 #+end_example
 
-If you want to set properties that can be inherited by any entry in
-a file, use a line like:
+Properties can be inserted on buffer level.  That means they apply
+before the first headline and can be inherited by all entries in a
+file.  Property blocks defined before first headline needs to be
+located at the top of the buffer, allowing only comments and keywords
+above.
+
+Properties can also be defined using lines like:
 
 #+cindex: @samp{_ALL} suffix, in properties
 #+cindex: @samp{PROPERTY}, keyword
@@ -5081,7 +5086,9 @@ The following commands help to work with properties:
   #+findex: org-insert-drawer
   Insert a property drawer into the current entry.  The drawer is
   inserted early in the entry, but after the lines with planning
-  information like deadlines.
+  information like deadlines.  If before first headline the drawer is
+  inserted at the top of the drawer after any potential comments or
+  keywords.
 
 - {{{kbd(C-c C-c)}}} (~org-property-action~) ::
 
@@ -5304,11 +5311,6 @@ done by defining a column format line.
 :DESCRIPTION: Where defined, where valid?
 :END:
 
-To define a column format for an entire file, use a line like:
-
-#+cindex: @samp{COLUMNS}, keyword
-: #+COLUMNS: %25ITEM %TAGS %PRIORITY %TODO
-
 To specify a format that only applies to a specific tree, add
 a =COLUMNS= property to the top node of that tree, for example:
 
@@ -5319,6 +5321,13 @@ a =COLUMNS= property to the top node of that tree, for example:
    :END:
 #+end_example
 
+A =COLUMNS= property within a property drawer before first headline
+will apply to the entire file.  As an addition to property drawers,
+keywords can also be defined for an entire file using a line like:
+
+#+cindex: @samp{COLUMNS}, keyword
+: #+COLUMNS: %25ITEM %TAGS %PRIORITY %TODO
+
 If a =COLUMNS= property is present in an entry, it defines columns for
 the entry itself, and for the entire subtree below it.  Since the
 column definition is part of the hierarchical structure of the
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 089f62995..86fba544f 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -123,6 +123,14 @@ auto-commit attachments to git:
   one need to require the =org-attach-git= module in the startup.
 
 ** New features
+*** Property drawers before first headline, outline  level 0
+Property drawers will now work before first headline and org-mode is
+moving more towards making things before the first headline behave
+just as if it was at outline level 0.  Inheritance for properties will
+work also for this level.  In other words; defining things in a
+property drawer before the first headline will make them "inheritable"
+for all headlines.
+
 *** Add split-window-right option for src block edit window placement
 Given the increasing popularity of wide screen monitors, splitting
 horizontally may make more sense than splitting vertically.  An
diff --git a/lisp/org-attach.el b/lisp/org-attach.el
index 726085014..f1c9d69d8 100644
--- a/lisp/org-attach.el
+++ b/lisp/org-attach.el
@@ -243,7 +243,7 @@ Shows a list of commands and prompts for another key to execute a command."
       (when marker
 	(set-buffer (marker-buffer marker))
 	(goto-char marker))
-      (org-back-to-heading t)
+      (org-back-to-heading-or-point-min t)
       (save-excursion
 	(save-window-excursion
 	  (unless org-attach-expert
diff --git a/lisp/org-capture.el b/lisp/org-capture.el
index 4f97e17ea..67343e02a 100644
--- a/lisp/org-capture.el
+++ b/lisp/org-capture.el
@@ -1736,11 +1736,11 @@ The template may still contain \"%?\" for cursor positioning."
 			 (_ (error "Invalid `org-capture--clipboards' value: %S"
 				   org-capture--clipboards)))))
 		    ("p"
-		     ;; We remove file properties inherited from
+		     ;; We remove keyword properties inherited from
 		     ;; target buffer so `org-read-property-value' has
 		     ;; a chance to find allowed values in sub-trees
 		     ;; from the target buffer.
-		     (setq-local org-file-properties nil)
+		     (setq-local org-keyword-properties nil)
 		     (let* ((origin (set-marker (make-marker)
 						(org-capture-get :pos)
 						(org-capture-get :buffer)))
diff --git a/lisp/org-element.el b/lisp/org-element.el
index cdd3eb67f..ee5c7a735 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -73,6 +73,9 @@
 (require 'org-table)
 
 (declare-function org-at-heading-p "org" (&optional _))
+(declare-function org-before-first-heading-p "org")
+(declare-function org-at-comment-p "org" ())
+(declare-function org-at-keyword-p "org" ())
 (declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading))
 (declare-function org-escape-code-in-string "org-src" (s))
 (declare-function org-find-visible "org" ())
@@ -770,6 +773,42 @@ inherited properties."
     (list :document-keywords document-keywords
 	  :export-keywords export-keywords)))
 
+(defun org-element--get-document-properties ()
+  "Return document properties associated to the whole document.
+Upcase property names to avoid confusion between properties
+obtained through property drawer and default properties from the
+parser (e.g. `:end' and :END:).  Return value is a plist."
+  (let ((keyword-properties
+	 (alist-get 'property
+		    (org-collect-keywords
+		     (org-make-keyword-regexp
+		      '("PROPERTY")))))
+	properties)
+    ;; Parse properties from keywords first.
+    (dolist (cons keyword-properties)
+      (setq properties
+	    (plist-put properties
+		       (intern (concat ":" (car cons)))
+		       (cdr cons))))
+    ;; If the document property drawer exist, let properties
+    ;; set there override properties from keywords.
+    (save-excursion
+      (goto-char (point-min))
+      (when (org-before-first-heading-p)
+	(while (and (org-at-comment-p) (bolp)) (forward-line))
+	(while (and (org-at-keyword-p) (bolp)) (forward-line)))
+      (when (looking-at org-property-drawer-re)
+	(forward-line)
+	(let ((end (match-end 0)))
+	  (while (< (line-end-position) end)
+	    (looking-at org-property-re)
+	    (setq properties
+		  (plist-put properties
+			     (intern (concat ":" (upcase (match-string 2))))
+			     (match-string-no-properties 3)))
+	    (forward-line)))))
+    properties))
+
 (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
@@ -778,7 +817,7 @@ containing `:buffer', `:file', `:level', `:contents-begin',
 
 In addition to the above, the plist also contains configurations
 and properties that applies for the whole document, coming from
-document keywords."
+document keywords and the document property drawer."
   (save-excursion
     (list 'document
 	  (nconc
@@ -790,6 +829,7 @@ document keywords."
 		 :begin (point-min)
 		 :end (point-max)
 		 :post-blank 0)
+	   (org-element--get-document-properties)
 	   (org-element--get-document-keywords)))))
 
 (defun org-element-document-interpreter (_ contents)
@@ -1001,8 +1041,8 @@ CONTENTS is the contents of the footnote-definition."
 
 ;;;; Headline
 
-(defun org-element--get-node-properties ()
-  "Return node properties associated to headline at point.
+(defun org-element--get-headline-properties ()
+  "Return properties associated to headline at point.
 Upcase property names.  It avoids confusion between properties
 obtained through property drawer and default properties from the
 parser (e.g. `:end' and :END:).  Return value is a plist."
@@ -1088,7 +1128,7 @@ Assume point is at beginning of the headline."
 	   (archivedp (member org-archive-tag tags))
 	   (footnote-section-p (and org-footnote-section
 				    (string= org-footnote-section raw-value)))
-	   (standard-props (org-element--get-node-properties))
+	   (standard-props (org-element--get-headline-properties))
 	   (time-props (org-element--get-time-properties))
 	   (end (min (save-excursion (org-end-of-subtree t t)) limit))
 	   (contents-begin (save-excursion
@@ -1233,7 +1273,7 @@ Assume point is at beginning of the inline task."
 		       (and (re-search-forward org-outline-regexp-bol limit t)
 			    (looking-at-p "[ \t]*END[ \t]*$")
 			    (line-beginning-position))))
-	   (standard-props (and task-end (org-element--get-node-properties)))
+	   (standard-props (and task-end (org-element--get-headline-properties)))
 	   (time-props (and task-end (org-element--get-time-properties)))
 	   (contents-begin (and task-end
 				(< (point) task-end)
@@ -3984,7 +4024,10 @@ element it has to parse."
 	     ;; LaTeX Environment.
 	     ((looking-at org-element--latex-begin-environment)
 	      (org-element-latex-environment-parser limit affiliated))
-	     ;; Drawer and Property Drawer.
+	     ;; Property drawer (before first headline, else it's catched above).
+	     ((org-at-property-block-p)
+	      (org-element-property-drawer-parser limit))
+	     ;; Drawer.
 	     ((looking-at org-drawer-regexp)
 	      (org-element-drawer-parser limit affiliated))
 	     ;; Fixed Width
diff --git a/lisp/org.el b/lisp/org.el
index 358989070..5c21e130c 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -3185,8 +3185,13 @@ This list will be combined with the constant `org-global-properties-fixed'.
 The entries in this list are cons cells where the car is a property
 name and cdr is a string with the value.
 
-You can set buffer-local values for the same purpose in the variable
-`org-file-properties' this by adding lines like
+Buffer local properties are added either by a document property drawer
+
+:PROPERTIES:
+:NAME: VALUE
+:END:
+
+or by adding lines like
 
 #+PROPERTY: NAME VALUE"
   :group 'org-properties
@@ -3194,10 +3199,14 @@ You can set buffer-local values for the same purpose in the variable
 	  (cons (string :tag "Property")
 		(string :tag "Value"))))
 
-(defvar-local org-file-properties nil
+(defvar-local org-keyword-properties nil
   "List of property/value pairs that can be inherited by any entry.
-Valid for the current buffer.
-This variable is populated from #+PROPERTY lines.")
+Valid for the current buffer.  This variable is populated from
+#+PROPERTY lines.
+
+Note that properties are defined also in property drawers.
+Properties defined there will take precedence over properties
+defined as keywords.")
 
 (defgroup org-agenda nil
   "Options concerning agenda views in Org mode."
@@ -3206,11 +3215,18 @@ This variable is populated from #+PROPERTY lines.")
 
 (defvar-local org-category nil
   "Variable used by Org files to set a category for agenda display.
-Such files should use a file variable to set it, for example
+There are multiple ways to set the category.  One way is to set
+it in the document property drawer.  For example:
+
+:PROPERTIES:
+:CATEGORY: ELisp
+:END:
+
+Other ways to define it is as an emacs file variable, for example
 
 #   -*- mode: org; org-category: \"ELisp\"
 
-or contain a special line
+or for the file to contain a special line:
 
 #+CATEGORY: ELisp
 
@@ -4343,8 +4359,8 @@ related expressions."
       (setq org-tag-groups-alist
 	    (org-tag-alist-to-groups org-current-tag-alist))
       (unless tags-only
-	;; File properties.
-	(setq-local org-file-properties (cdr (assq 'property alist)))
+	;; Properties.
+	(setq-local org-keyword-properties (cdr (assq 'property alist)))
 	;; Archive location.
 	(let ((archive (cdr (assq 'archive alist))))
 	  (when archive (setq-local org-archive-location archive)))
@@ -4352,9 +4368,9 @@ related expressions."
 	(let ((cat (org-string-nw-p (cdr (assq 'category alist)))))
 	  (when cat
 	    (setq-local org-category (intern cat))
-	    (setq-local org-file-properties
+	    (setq-local org-keyword-properties
 			(org--update-property-plist
-			 "CATEGORY" cat org-file-properties))))
+			 "CATEGORY" cat org-keyword-properties))))
 	;; Columns.
 	(let ((column (cdr (assq 'columns alist))))
 	  (when column (setq-local org-columns-default-format column)))
@@ -8276,13 +8292,14 @@ the value of the drawer property."
 	 (inhibit-read-only t)
 	 (inherit? (org-property-inherit-p dprop))
 	 (property-re (org-re-property (concat (regexp-quote dprop) "\\+?") t))
-	 (global (and inherit? (org--property-global-value dprop nil))))
+	 (global-or-keyword (and inherit?
+				 (org--property-global-or-keyword-value dprop nil))))
     (with-silent-modifications
       (org-with-point-at 1
-	;; Set global values (e.g., values defined through
-	;; "#+PROPERTY:" keywords) to the whole buffer.
-	(when global (put-text-property (point-min) (point-max) tprop global))
-	;; Set local values.
+	;; Set global and keyword based values to the whole buffer.
+	(when global-or-keyword
+	  (put-text-property (point-min) (point-max) tprop global-or-keyword))
+	;; Set values based on property-drawers throughout the document.
 	(while (re-search-forward property-re nil t)
 	  (when (org-at-property-p)
 	    (org-refresh-property tprop (org-entry-get (point) dprop) inherit?))
@@ -8290,21 +8307,29 @@ the value of the drawer property."
 
 (defun org-refresh-property (tprop p &optional inherit)
   "Refresh the buffer text property TPROP from the drawer property P.
-The refresh happens only for the current headline, or the whole
-sub-tree if optional argument INHERIT is non-nil."
-  (unless (org-before-first-heading-p)
-    (save-excursion
-      (org-back-to-heading t)
-      (let ((start (point))
-	    (end (save-excursion
-		   (if inherit (org-end-of-subtree t t)
-		     (or (outline-next-heading) (point-max))))))
-	(if (symbolp tprop)
-	    ;; TPROP is a text property symbol.
-	    (put-text-property start end tprop p)
-	  ;; TPROP is an alist with (property . function) elements.
-	  (pcase-dolist (`(,prop . ,f) tprop)
-	    (put-text-property start end prop (funcall f p))))))))
+The refresh happens only for the current entry, or the whole
+sub-tree if optional argument INHERIT is non-nil.
+
+If point is before first headline, the function applies to the
+part before the first headline.  In that particular case, when
+optional argument INHERIT is non-nil, it refreshes properties for
+the whole buffer."
+  (save-excursion
+    (org-back-to-heading-or-point-min t)
+    (let ((start (point))
+	  (end (save-excursion
+		 (cond ((and inherit (org-before-first-heading-p))
+			(point-max))
+		       (inherit
+			(org-end-of-subtree t t))
+		       ((outline-next-heading))
+		       ((point-max))))))
+      (if (symbolp tprop)
+	  ;; TPROP is a text property symbol.
+	  (put-text-property start end tprop p)
+	;; TPROP is an alist with (property . function) elements.
+	(pcase-dolist (`(,prop . ,f) tprop)
+	  (put-text-property start end prop (funcall f p)))))))
 
 (defun org-refresh-category-properties ()
   "Refresh category text properties in the buffer."
@@ -8320,9 +8345,9 @@ sub-tree if optional argument INHERIT is non-nil."
 		(t org-category))))
     (with-silent-modifications
       (org-with-wide-buffer
-       ;; Set buffer-wide category.  Search last #+CATEGORY keyword.
-       ;; This is the default category for the buffer.  If none is
-       ;; found, fall-back to `org-category' or buffer file name.
+       ;; Set buffer-wide property from keyword.  Search last #+CATEGORY
+       ;; keyword.  If none is found, fall-back to `org-category' or
+       ;; buffer file name, or set it by the document property drawer.
        (put-text-property
 	(point-min) (point-max)
 	'org-category
@@ -8334,15 +8359,20 @@ sub-tree if optional argument INHERIT is non-nil."
 		(throw 'buffer-category
 		       (org-element-property :value element)))))
 	  default-category))
-       ;; Set sub-tree specific categories.
+       ;; Set categories from the document property drawer or
+       ;; property drawers in the outline.  If category is found in
+       ;; the property drawer for the whole buffer that value
+       ;; overrides the keyword-based value set above.
        (goto-char (point-min))
        (let ((regexp (org-re-property "CATEGORY")))
 	 (while (re-search-forward regexp nil t)
 	   (let ((value (match-string-no-properties 3)))
 	     (when (org-at-property-p)
 	       (put-text-property
-		(save-excursion (org-back-to-heading t) (point))
-		(save-excursion (org-end-of-subtree t t) (point))
+		(save-excursion (org-back-to-heading-or-point-min t))
+		(save-excursion (if (org-before-first-heading-p)
+				    (point-max)
+				  (org-end-of-subtree t t)))
 		'org-category
 		value)))))))))
 
@@ -10646,7 +10676,7 @@ prefer a state in the current sequence over on in another sequence."
 	    (setq tg (org-add-props tg nil 'face
 				    (org-get-todo-face tg)))
 	    (when (and (= cnt 0) (not ingroup)) (insert "  "))
-	    (setq prompt (concat prompt "[" (char-to-string c) "] " tg " ")) 
+	    (setq prompt (concat prompt "[" (char-to-string c) "] " tg " "))
 	    (insert "[" c "] " tg (make-string
 				   (- fwidth 4 (length tg)) ?\ ))
 	    (when (and (= (setq cnt (1+ cnt)) ncol)
@@ -12921,30 +12951,45 @@ Modifications are made by side-effect.  Return new alist."
 
 (defun org-get-property-block (&optional beg force)
   "Return the (beg . end) range of the body of the property drawer.
-BEG is the beginning of the current subtree, or of the part
-before the first headline.  If it is not given, it will be found.
-If the drawer does not exist, create it if FORCE is non-nil, or
-return nil."
+BEG is the beginning of the current subtree or the beginning of
+the document if before the first headline.  If it is not given,
+it will be found.  If the drawer does not exist, create it if
+FORCE is non-nil, or return nil."
   (org-with-wide-buffer
-   (when beg (goto-char beg))
-   (unless (org-before-first-heading-p)
-     (let ((beg (cond (beg)
+   (let ((beg (cond (beg (goto-char beg))
 		      ((or (not (featurep 'org-inlinetask))
 			   (org-inlinetask-in-task-p))
-		       (org-back-to-heading t))
-		      (t (org-with-limited-levels (org-back-to-heading t))))))
-       (forward-line)
-       (when (looking-at-p org-planning-line-re) (forward-line))
-       (cond ((looking-at org-property-drawer-re)
-	      (forward-line)
-	      (cons (point) (progn (goto-char (match-end 0))
-				   (line-beginning-position))))
-	     (force
-	      (goto-char beg)
-	      (org-insert-property-drawer)
-	      (let ((pos (save-excursion (search-forward ":END:")
-					 (line-beginning-position))))
-		(cons pos pos))))))))
+		       (org-back-to-heading-or-point-min t) (point))
+		      (t (org-with-limited-levels
+			  (org-back-to-heading-or-point-min t))
+			 (point)))))
+     ;; Move point to its position according to its positional rules.
+     (cond ((org-before-first-heading-p)
+	    (while (and (bolp) (or (org-at-keyword-p) (org-at-comment-p))) (forward-line)))
+	   (t (forward-line)
+	      (when (looking-at-p org-planning-line-re) (forward-line))))
+     (cond ((looking-at org-property-drawer-re)
+	    (forward-line)
+	    (cons (point) (progn (goto-char (match-end 0))
+				 (line-beginning-position))))
+	   (force
+	    (goto-char beg)
+	    (org-insert-property-drawer)
+	    (let ((pos (save-excursion (re-search-forward org-property-drawer-re)
+				       (line-beginning-position))))
+	      (cons pos pos)))))))
+
+(defun org-at-property-block-p ()
+  "Non-nil when point is at the first line of a property drawer.
+The property drawer is validated according to its positional
+rules using `org-get-property-block'."
+  (save-excursion
+    (beginning-of-line)
+    (and (looking-at org-property-start-re)
+	 (forward-line)
+	 (let ((property-drawer (org-get-property-block)))
+	   (and property-drawer
+		(= (point) (car property-drawer)))))))
 
 (defun org-at-property-p ()
   "Non-nil when point is inside a property drawer.
@@ -13029,7 +13074,7 @@ Return value is an alist.  Keys are properties, as upcased
 strings."
   (org-with-point-at pom
     (when (and (derived-mode-p 'org-mode)
-	       (ignore-errors (org-back-to-heading t)))
+	       (org-back-to-heading-or-point-min t))
       (catch 'exit
 	(let* ((beg (point))
 	       (specific (and (stringp which) (upcase which)))
@@ -13238,13 +13283,13 @@ unless LITERAL-NIL is non-nil."
 	;; Return final values.
 	(and (not (equal value '(nil))) (nreverse value))))))
 
-(defun org--property-global-value (property literal-nil)
-  "Return value for PROPERTY in current buffer.
+(defun org--property-global-or-keyword-value (property literal-nil)
+  "Return value for PROPERTY as defined by global properties or by keyword.
 Return value is a string.  Return nil if property is not set
-globally.  Also return nil when PROPERTY is set to \"nil\",
-unless LITERAL-NIL is non-nil."
+globally or by keyword.  Also return nil when PROPERTY is set to
+\"nil\", unless LITERAL-NIL is non-nil."
   (let ((global
-	 (cdr (or (assoc-string property org-file-properties t)
+	 (cdr (or (assoc-string property org-keyword-properties t)
 		  (assoc-string property org-global-properties t)
 		  (assoc-string property org-global-properties-fixed t)))))
     (if literal-nil global (org-not-nil global))))
@@ -13393,12 +13438,12 @@ However, if LITERAL-NIL is set, return the string value \"nil\" instead."
 			   value)))
 	   (cond
 	    ((car v)
-	     (org-back-to-heading t)
+	     (org-back-to-heading-or-point-min t)
 	     (move-marker org-entry-property-inherited-from (point))
 	     (throw 'exit nil))
-	    ((org-up-heading-safe))
+	    ((org-up-heading-or-point-min))
 	    (t
-	     (let ((global (org--property-global-value property literal-nil)))
+	     (let ((global (org--property-global-or-keyword-value property literal-nil)))
 	       (cond ((not global))
 		     (value (setq value (concat global " " value)))
 		     (t (setq value global))))
@@ -13430,8 +13475,8 @@ decreases scheduled or deadline date by one day."
 	 (user-error "Invalid property name: \"%s\"" property)))
   (org-with-point-at pom
     (if (or (not (featurep 'org-inlinetask)) (org-inlinetask-in-task-p))
-	(org-back-to-heading t)
-      (org-with-limited-levels (org-back-to-heading t)))
+	(org-back-to-heading-or-point-min t)
+      (org-with-limited-levels (org-back-to-heading-or-point-min t)))
     (let ((beg (point)))
       (cond
        ((equal property "TODO")
@@ -13567,19 +13612,26 @@ COLUMN formats in the current buffer."
 Do nothing if the drawer already exists.  The newly created
 drawer is immediately hidden."
   (org-with-wide-buffer
+   ;; Set point to the position where the drawer should be inserted.
    (if (or (not (featurep 'org-inlinetask)) (org-inlinetask-in-task-p))
-       (org-back-to-heading t)
-     (org-with-limited-levels (org-back-to-heading t)))
-   (forward-line)
-   (when (looking-at-p org-planning-line-re) (forward-line))
+       (org-back-to-heading-or-point-min t)
+     (org-with-limited-levels (org-back-to-heading-or-point-min t)))
+   (if (org-before-first-heading-p)
+       (while (and (bolp) (or (org-at-keyword-p) (org-at-comment-p))) (forward-line))
+     (progn
+       (forward-line)
+       (when (looking-at-p org-planning-line-re) (forward-line))))
    (unless (looking-at-p org-property-drawer-re)
      ;; Make sure we start editing a line from current entry, not from
      ;; next one.  It prevents extending text properties or overlays
      ;; belonging to the latter.
-     (when (bolp) (backward-char))
-     (let ((begin (1+ (point)))
+     (when (and (bolp) (> (point) (point-min))) (backward-char))
+     (let ((begin (if (= (point) (point-min))
+		      (point)
+		    (1+ (point))))
 	   (inhibit-read-only t))
-       (insert "\n:PROPERTIES:\n:END:")
+       (unless (= begin (point-min)) (insert "\n"))
+       (insert ":PROPERTIES:\n:END:")
        (org-flag-drawer t nil (line-end-position 0) (point))
        (when (eobp) (insert "\n"))
        (org-indent-region begin (point))))))
@@ -20484,6 +20536,15 @@ interactive command with similar behavior."
     (error (error "Before first headline at position %d in buffer %s"
 		  (point) (current-buffer)))))
 
+(defun org-back-to-heading-or-point-min (&optional invisible-ok)
+  "Go back to heading or first point in buffer.
+If point is before first heading go to first point in buffer
+instead of back to heading."
+  (condition-case nil
+      (outline-back-to-heading invisible-ok)
+    (error
+     (goto-char (point-min)))))
+
 (defun org-before-first-heading-p ()
   "Before first heading?"
   (org-with-limited-levels
@@ -20517,6 +20578,12 @@ unless optional argument NO-INHERITANCE is non-nil."
       (beginning-of-line)
       (looking-at "^[ \t]*# "))))
 
+(defun org-at-keyword-p nil
+  "Is cursor at a keyword-line?"
+  (save-excursion
+    (move-beginning-of-line 1)
+    (looking-at org-keyword-regexp)))
+
 (defun org-at-drawer-p nil
   "Is cursor at a drawer keyword?"
   (save-excursion
@@ -20564,6 +20631,17 @@ make a significant difference in outlines with very many siblings."
 	   (re-search-backward (format "^\\*\\{1,%d\\} " level-up) nil t)
 	   (funcall outline-level)))))
 
+(defun org-up-heading-or-point-min ()
+  "Move to the heading line of which the present is a subheading, or point-min.
+This version is needed to make point-min behave like a virtual
+heading of level 0 for property-inheritance.  It will return the
+level of the headline found (down to 0) or nil if already at a
+point before the first headline or at point-min."
+  (when (ignore-errors (org-back-to-heading t))
+    (if (< 1 (funcall outline-level))
+	(org-up-heading-safe)
+      (unless (= (point) (point-min)) (goto-char (point-min))))))
+
 (defun org-first-sibling-p ()
   "Is this heading the first child of its parents?"
   (interactive)
@@ -20664,28 +20742,31 @@ If there is no such heading, return nil."
 (defun org-end-of-subtree (&optional invisible-ok to-heading)
   "Goto to the end of a subtree."
   ;; This contains an exact copy of the original function, but it uses
-  ;; `org-back-to-heading', to make it work also in invisible
-  ;; trees.  And is uses an invisible-ok argument.
+  ;; `org-back-to-heading-or-point-min', to make it work also in invisible
+  ;; trees and before first headline.  And is uses an invisible-ok argument.
   ;; Under Emacs this is not needed, but the old outline.el needs this fix.
   ;; Furthermore, when used inside Org, finding the end of a large subtree
   ;; with many children and grandchildren etc, this can be much faster
   ;; than the outline version.
-  (org-back-to-heading invisible-ok)
+  (org-back-to-heading-or-point-min invisible-ok)
   (let ((first t)
 	(level (funcall outline-level)))
-    (if (and (derived-mode-p 'org-mode) (< level 1000))
-	;; A true heading (not a plain list item), in Org
-	;; This means we can easily find the end by looking
-	;; only for the right number of stars.  Using a regexp to do
-	;; this is so much faster than using a Lisp loop.
-	(let ((re (concat "^\\*\\{1," (int-to-string level) "\\} ")))
-	  (forward-char 1)
-	  (and (re-search-forward re nil 'move) (beginning-of-line 1)))
-      ;; something else, do it the slow way
-      (while (and (not (eobp))
-		  (or first (> (funcall outline-level) level)))
-	(setq first nil)
-	(outline-next-heading)))
+    (cond ((= level 0)
+	   (goto-char (point-max)))
+	  ((and (derived-mode-p 'org-mode) (< level 1000))
+	   ;; A true heading (not a plain list item), in Org
+	   ;; This means we can easily find the end by looking
+	   ;; only for the right number of stars.  Using a regexp to do
+	   ;; this is so much faster than using a Lisp loop.
+	   (let ((re (concat "^\\*\\{1," (int-to-string level) "\\} ")))
+	     (forward-char 1)
+	     (and (re-search-forward re nil 'move) (beginning-of-line 1))))
+	  (t
+	   ;; something else, do it the slow way
+	   (while (and (not (eobp))
+		       (or first (> (funcall outline-level) level)))
+	     (setq first nil)
+	     (outline-next-heading))))
     (unless to-heading
       (when (memq (preceding-char) '(?\n ?\^M))
 	;; Go to end of line before heading
diff --git a/testing/examples/property-inheritance.org b/testing/examples/property-inheritance.org
index 6979de595..9c0b7e662 100644
--- a/testing/examples/property-inheritance.org
+++ b/testing/examples/property-inheritance.org
@@ -1,5 +1,7 @@
-#+property: header-args :var  foo=1
-#+property: header-args+ :var bar=2
+:PROPERTIES:
+:header-args: :var foo=1
+:header-args+: :var bar=2
+:END:
 
 #+begin_src emacs-lisp
   (+ foo bar)
diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el
index 2a1716134..9e9cbb381 100644
--- a/testing/lisp/test-org-element.el
+++ b/testing/lisp/test-org-element.el
@@ -1925,6 +1925,15 @@ e^{i\\pi}+1=0
 	    (let ((element (org-element-at-point)))
 	      (list (org-element-property :key element)
 		    (org-element-property :value element))))))
+  ;; The insides of property blocks on document level are parsed the
+  ;; same way as headline property blocks.  I.e. the concept of
+  ;; `node-property' apply also for properties in those blocks.
+  (should
+   (equal '("abc" "value")
+	  (org-test-with-temp-text ":PROPERTIES:\n<point>:abc: value\n:END:"
+	    (let ((element (org-element-at-point)))
+	      (list (org-element-property :key element)
+		    (org-element-property :value element))))))
   ;; Value should be trimmed.
   (should
    (equal "value"
@@ -2111,6 +2120,18 @@ Outside list"
        (org-test-with-temp-text
 	   "* H\nDEADLINE: <2014-03-04 tue.>\n<point>:PROPERTIES:\n:prop: value\n:END:"
 	 (org-element-type (org-element-at-point)))))
+  (should
+   (eq 'property-drawer
+       (org-test-with-temp-text "<point>:PROPERTIES:\n:prop: value\n:END:"
+	 (org-element-type (org-element-at-point)))))
+  (should
+   (eq 'property-drawer
+       (org-test-with-temp-text "# C\n#+Key: V\n# C\n<point>:PROPERTIES:\n:prop: value\n:END:"
+	 (org-element-type (org-element-at-point)))))
+  (should-not
+   (eq 'property-drawer
+       (org-test-with-temp-text "\n<point>:PROPERTIES:\n:prop: value\n:END:"
+	 (org-element-type (org-element-at-point)))))
   ;; Allow properties without value and no property at all.
   (should
    (eq 'property-drawer
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 01e965ea7..2b5d3d280 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -386,10 +386,117 @@
 
 ;;; Drawers
 
+(ert-deftest test-org/at-property-p ()
+  "Test `org-at-property-p' specifications."
+  (should
+   (equal 't
+	  (org-test-with-temp-text "* H\n:PROPERTIES:\n<point>:PROP: t\n:END:\n"
+	    (org-at-property-p))))
+  (should
+   (equal 't
+	  (org-test-with-temp-text ":PROPERTIES:\n<point>:PROP: t\n:END:\n"
+	    (org-at-property-p)))))
+
+(ert-deftest test-org/at-property-block-p ()
+  "Test `org-at-property-block-p' specifications."
+  (should
+   (equal 't
+	  (org-test-with-temp-text "* H\n<point>:PROPERTIES:\n:PROP: t\n:END:\n"
+	    (org-at-property-block-p))))
+  (should
+   (equal 't
+	  (org-test-with-temp-text ":PROPERTIES:\n:PROP: t\n:END:\n"
+	    (org-at-property-block-p))))
+  ;; The function only returns t if point is at the first line of a
+  ;; property block.
+  (should-not
+   (equal 't
+	  (org-test-with-temp-text ":PROPERTIES:\n<point>:PROP: t\n:END:\n"
+	    (org-at-property-block-p))))
+  (should))
+
+(ert-deftest test-org/get-property-block ()
+  "Test `org-get-property-block' specifications."
+  (should
+   (equal '(14 . 14)
+	  (org-test-with-temp-text ":PROPERTIES:\n:END:\n* H\n"
+	    (org-get-property-block))))
+  (should
+   (equal '(14 . 14)
+	  (org-test-with-temp-text ":PROPERTIES:\n:END:\n"
+	    (org-get-property-block))))
+  ;; Comments above a document property block is ok.
+  (should
+   (equal '(18 . 18)
+	  (org-test-with-temp-text "# C\n:PROPERTIES:\n:END:\n"
+	    (org-get-property-block))))
+  ;; Keywords above a document property block is ok.
+  (should
+   (equal '(23 . 23)
+	  (org-test-with-temp-text "#+Key: v\n:PROPERTIES:\n:END:\n"
+	    (org-get-property-block))))
+  ;; Comments and keywords are allowed before a document property block.
+  (should
+   (equal '(27 . 36)
+	  (org-test-with-temp-text "# C\n#+key: v\n:PROPERTIES:\n:KEY: V:\n:END:\n"
+	    (org-get-property-block))))
+  (should
+   (equal '(36 . 45)
+	  (org-test-with-temp-text "#+Key: v\n# C\n#+Key: v\n:PROPERTIES:\n:KEY: V:\n:END:\n"
+	    (org-get-property-block))))
+  ;; A document property block will not be valid if there are lines
+  ;; with whitespace above it
+  (should-not
+   (org-test-with-temp-text "\n:PROPERTIES:\n:END:\n"
+     (org-get-property-block)))
+  (should
+   (equal '(18 . 18)
+	  (org-test-with-temp-text "* H\n:PROPERTIES:\n:END:\n<point>"
+	    (org-get-property-block))))
+  (should
+   (equal "* H\n:PROPERTIES:\n:END:\n"
+	  (org-test-with-temp-text "* H"
+	    (let ((org-adapt-indentation nil))
+	      (org-get-property-block nil 'force))
+	    (buffer-string))))
+  (should
+   (equal ":PROPERTIES:\n:END:\n"
+	  (org-test-with-temp-text ""
+	    (org-get-property-block nil 'force)
+	    (buffer-string))))
+  (should
+   (equal "* H1\n  :PROPERTIES:\n  :END:\n* H2"
+	  (org-test-with-temp-text "* H1\n* H2"
+	    (let ((org-adapt-indentation t))
+	      (org-get-property-block nil 'force))
+	    (buffer-string)))))
+
 (ert-deftest test-org/insert-property-drawer ()
   "Test `org-insert-property-drawer' specifications."
-  ;; Error before first headline.
-  (should-error (org-test-with-temp-text "" (org-insert-property-drawer)))
+  ;; Insert drawer in empty buffer
+  (should
+   (equal ":PROPERTIES:\n:END:\n"
+	  (org-test-with-temp-text ""
+	    (let ((org-adapt-indentation nil)) (org-insert-property-drawer))
+	    (buffer-string))))
+  ;; Insert drawer in document header with existing comment and
+  ;; keyword.
+  (should
+   (equal "# C\n#+TITLE: T\n:PROPERTIES:\n:END:\n"
+	  (org-test-with-temp-text "# C\n#+TITLE: T"
+	    (let ((org-adapt-indentation nil)) (org-insert-property-drawer))
+	    (buffer-string))))
+  (should
+   (equal ":PROPERTIES:\n:END:"
+	  (org-test-with-temp-text ":PROPERTIES:\n:END:"
+	    (let ((org-adapt-indentation nil)) (org-insert-property-drawer))
+	    (buffer-string))))
+  ;; Insert drawer in document header with one existing heading in buffer.
+  (should
+   (equal ":PROPERTIES:\n:END:\n* T\n"
+	  (org-test-with-temp-text "<point>\n* T\n"
+	    (let ((org-adapt-indentation nil)) (org-insert-property-drawer))
+	    (buffer-string))))
   ;; Insert drawer right after headline if there is no planning line,
   ;; or after it otherwise.
   (should
@@ -2178,19 +2285,19 @@ SCHEDULED: <2014-03-04 tue.>"
    (equal "foo=1"
 	  (org-test-with-temp-text "#+PROPERTY: var foo=1"
 	    (org-mode-restart)
-	    (cdr (assoc "var" org-file-properties)))))
+	    (cdr (assoc "var" org-keyword-properties)))))
   (should
    (equal
     "foo=1 bar=2"
     (org-test-with-temp-text "#+PROPERTY: var foo=1\n#+PROPERTY: var+ bar=2"
       (org-mode-restart)
-      (cdr (assoc "var" org-file-properties)))))
+      (cdr (assoc "var" org-keyword-properties)))))
   (should
    (equal
     "foo=1 bar=2"
     (org-test-with-temp-text "#+PROPERTY: var foo=1\n#+PROPERTY: VAR+ bar=2"
       (org-mode-restart)
-      (cdr (assoc "var" org-file-properties)))))
+      (cdr (assoc "var" org-keyword-properties)))))
   ;; ARCHIVE keyword.
   (should
    (equal "%s_done::"
@@ -2207,7 +2314,7 @@ SCHEDULED: <2014-03-04 tue.>"
    (equal "test"
 	  (org-test-with-temp-text "#+CATEGORY: test"
 	    (org-mode-restart)
-	    (cdr (assoc "CATEGORY" org-file-properties)))))
+	    (cdr (assoc "CATEGORY" org-keyword-properties)))))
   ;; COLUMNS keyword.
   (should
    (equal "%25ITEM %TAGS %PRIORITY %TODO"
@@ -2291,7 +2398,7 @@ SCHEDULED: <2014-03-04 tue.>"
 	  (org-test-with-temp-text
 	      (format "#+SETUPFILE: \"%s/examples/setupfile.org\"" org-test-dir)
 	    (org-mode-restart)
-	    (cdr (assoc "a" org-file-properties))))))
+	    (cdr (assoc "a" org-keyword-properties))))))
 
 
 
@@ -4941,6 +5048,66 @@ Paragraph<point>"
 	  (org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:A+: 2\n:END:"
 	    (org-property-values "A")))))
 
+(ert-deftest test-org/set-property ()
+  "Test `org-set-property' specifications."
+  (should
+   (equal
+    ":PROPERTIES:\n:TEST: t\n:END:\n"
+    (org-test-with-temp-text ""
+      (let ((org-property-format "%s %s"))
+	(org-set-property "TEST" "t"))
+      (buffer-string))))
+  (should
+   (equal
+    "* H\n:PROPERTIES:\n:TEST: t\n:END:\n"
+    (org-test-with-temp-text "* H"
+      (let ((org-adapt-indentation nil)
+	    (org-property-format "%s %s"))
+	(org-set-property "TEST" "t"))
+      (buffer-string)))))
+
+(ert-deftest test-org/delete-property ()
+  "Test `org-delete-property' specifications."
+  (should
+   (equal
+    ""
+    (org-test-with-temp-text ":PROPERTIES:\n:TEST: t\n:END:\n"
+      (org-delete-property "TEST")
+      (buffer-string))))
+  (should
+   (equal
+    ":PROPERTIES:\n:TEST1: t\n:END:\n"
+    (org-test-with-temp-text ":PROPERTIES:\n:TEST1: t\n:TEST2: t\n:END:\n"
+      (org-delete-property "TEST2")
+      (buffer-string))))
+  (should
+   (equal
+    "* H\n"
+    (org-test-with-temp-text "* H\n:PROPERTIES:\n:TEST: t\n:END:\n"
+      (org-delete-property "TEST")
+      (buffer-string))))
+  (should
+   (equal
+    "* H\n:PROPERTIES:\n:TEST1: t\n:END:\n"
+    (org-test-with-temp-text "* H\n:PROPERTIES:\n:TEST1: t\n:TEST2: t\n:END:\n"
+      (org-delete-property "TEST2")
+      (buffer-string)))))
+
+(ert-deftest test-org/delete-property-globally ()
+  "Test `org-delete-property-global' specifications."
+  (should
+   (equal
+    ""
+    (org-test-with-temp-text ":PROPERTIES:\n:TEST: t\n:END:\n"
+      (org-delete-property-globally "TEST")
+      (buffer-string))))
+  (should
+   (equal
+    "* H\n"
+    (org-test-with-temp-text ":PROPERTIES:\n:TEST: t\n:END:\n* H\n:PROPERTIES:\n:TEST: nil\n:END:"
+      (org-delete-property-globally "TEST")
+      (buffer-string)))))
+
 (ert-deftest test-org/find-property ()
   "Test `org-find-property' specifications."
   ;; Regular test.
@@ -5022,6 +5189,10 @@ Paragraph<point>"
 (ert-deftest test-org/entry-get ()
   "Test `org-entry-get' specifications."
   ;; Regular test.
+  (should
+   (equal "1"
+	  (org-test-with-temp-text ":PROPERTIES:\n:A: 1\n:END:"
+	    (org-entry-get (point) "A"))))
   (should
    (equal "1"
 	  (org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:"
@@ -5061,6 +5232,11 @@ Paragraph<point>"
      (org-entry-get (point) "B" nil t)))
   ;; Handle inheritance, when allowed.  Include extended values and
   ;; possibly global values.
+  (should
+   (equal
+    "1"
+    (org-test-with-temp-text ":PROPERTIES:\n:A: 1\n:END:\n* H"
+      (org-entry-get (point-max) "A" t))))
   (should
    (equal
     "1"
@@ -5076,12 +5252,30 @@ Paragraph<point>"
    (org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2"
      (let ((org-use-property-inheritance nil))
        (org-entry-get (point-max) "A" 'selective))))
+  (should
+   (equal
+    "1 2"
+    (org-test-with-temp-text
+	":PROPERTIES:\n:A: 1\n:END:\n* H\n:PROPERTIES:\n:A+: 2\n:END:"
+      (org-entry-get (point-max) "A" t))))
   (should
    (equal
     "1 2"
     (org-test-with-temp-text
 	"* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2\n:PROPERTIES:\n:A+: 2\n:END:"
       (org-entry-get (point-max) "A" t))))
+  (should
+   (equal
+    "1 2"
+    (org-test-with-temp-text
+	":PROPERTIES:\n:A: 1\n:END:\n* H1\n* H2\n:PROPERTIES:\n:A+: 2\n:END:"
+      (org-entry-get (point-max) "A" t))))
+  (should
+   (equal
+    "1 2"
+    (org-test-with-temp-text
+	"* H1\n:PROPERTIES:\n:A: 1\n:END:\n* H2.1\n* H2.2\n:PROPERTIES:\n:A+: 2\n:END:"
+      (org-entry-get (point-max) "A" t))))
   (should
    (equal "1"
 	  (org-test-with-temp-text
@@ -5093,6 +5287,14 @@ Paragraph<point>"
 	  (org-test-with-temp-text
 	      "#+PROPERTY: A 0\n* H\n:PROPERTIES:\n:A+: 1\n:END:"
 	    (org-mode-restart)
+	    (org-entry-get (point-max) "A" t))))
+  ;; document level property-drawer has precedance over
+  ;; global-property by PROPERTY-keyword.
+  (should
+   (equal "0 2"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:A: 0\n:END:\n#+PROPERTY: A 1\n* H\n:PROPERTIES:\n:A+: 2\n:END:"
+	    (org-mode-restart)
 	    (org-entry-get (point-max) "A" t)))))
 
 (ert-deftest test-org/entry-properties ()
@@ -5416,8 +5618,44 @@ Paragraph<point>"
 	    (let ((org-use-property-inheritance t))
 	      (org-refresh-properties "A" 'org-test))
 	    (get-text-property (point) 'org-test))))
+  ;; When a document level property-drawer is used, those properties
+  ;; should work exactly like headline-properties as if at a
+  ;; headline-level 0.
+  (should
+   (equal "1"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:A: 1\n:END:\n"
+	    (org-mode-restart)
+	    (let ((org-use-property-inheritance t))
+	      (org-refresh-properties "A" 'org-test))
+	    (get-text-property (point) 'org-test))))
+  (should-not
+   (equal "1"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:A: 1\n:END:\n<point>* H1"
+	    (org-mode-restart)
+	    (let ((org-use-property-inheritance nil))
+	      (org-refresh-properties "A" 'org-test))
+	    (get-text-property (point) 'org-test))))
+  (should
+   (equal "1"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:A: 1\n:END:\n<point>* H1"
+	    (org-mode-restart)
+	    (let ((org-use-property-inheritance t))
+	      (org-refresh-properties "A" 'org-test))
+	    (get-text-property (point) 'org-test))))
+  (should
+   (equal "2"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:A: 1\n:END:\n<point>* H1\n:PROPERTIES:\n:A: 2\n:END:"
+	    (org-mode-restart)
+	    (let ((org-use-property-inheritance t))
+	      (org-refresh-properties "A" 'org-test))
+	    (get-text-property (point) 'org-test))))
   ;; When property is inherited, use global value across the whole
-  ;; buffer.  However local values have precedence.
+  ;; buffer.  However local values have precedence, as well as the
+  ;; document level property-drawer.
   (should-not
    (equal "1"
 	  (org-test-with-temp-text "#+PROPERTY: A 1\n<point>* H1"
@@ -5437,10 +5675,62 @@ Paragraph<point>"
 	  (org-test-with-temp-text
 	      "#+PROPERTY: A 1\n<point>* H\n:PROPERTIES:\n:A: 2\n:END:"
 	    (org-mode-restart)
+	    (let ((org-use-property-inheritance t))
+	      (org-refresh-properties "A" 'org-test))
+	    (get-text-property (point) 'org-test))))
+  ;; When both keyword-property and document-level property-block is
+  ;; defined, the property-block has precedance.
+  (should
+   (equal "1"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:A: 1\n:END:\n#+PROPERTY: A 2\n<point>* H1"
+	    (org-mode-restart)
 	    (let ((org-use-property-inheritance t))
 	      (org-refresh-properties "A" 'org-test))
 	    (get-text-property (point) 'org-test)))))
 
+(ert-deftest test-org/refresh-category-properties ()
+  "Test `org-refresh-category-properties' specifications"
+  (should
+   (equal "cat1"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:CATEGORY: cat1\n:END:"
+	    (org-refresh-category-properties)
+	    (get-text-property (point) 'org-category))))
+  (should
+   (equal "cat1"
+	  (org-test-with-temp-text
+	      "* H\n:PROPERTIES:\n:CATEGORY: cat1\n:END:"
+	    (org-refresh-category-properties)
+	    (get-text-property (point) 'org-category))))
+  ;; Even though property-inheritance is deactivated, category
+  ;; property should be inherited.  As described in
+  ;; `org-use-property-inheritance'.
+  (should
+   (equal "cat1"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:CATEGORY: cat1\n:END:\n<point>* H"
+	    (org-mode-restart)
+	    (let ((org-use-property-inheritance nil))
+	      (org-refresh-category-properties))
+	    (get-text-property (point) 'org-category))))
+  (should
+   (equal "cat1"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:CATEGORY: cat1\n:END:\n<point>* H"
+	    (org-mode-restart)
+	    (let ((org-use-property-inheritance t))
+	      (org-refresh-category-properties))
+	    (get-text-property (point) 'org-category))))
+  (should
+   (equal "cat2"
+	  (org-test-with-temp-text
+	      ":PROPERTIES:\n:CATEGORY: cat1\n:END:\n<point>* H\n:PROPERTIES:\n:CATEGORY: cat2\n:END:\n"
+	    (org-mode-restart)
+	    (let ((org-use-property-inheritance t))
+	      (org-refresh-category-properties))
+	    (get-text-property (point) 'org-category)))))
+
 
 ;;; Refile
 
-- 
2.17.1

