Continuing my work in using org-mode as a structured markup language to encode data in files, today we look at how to extract keywords and properties from org-files. This will enable me to store data in the org-files, and then retrieve it later from code. For example, you may want to store the email address, or a student-id in the file so you can connect some other property such as a grade to it.
First we consider the built-in org commands to access aspects of the file properties. The
org-export-get-environment command returns a plist of properties of the file.
(:author ("John Kitchin") :creator "Generated by Org mode 7.9.4 in Emacs 126.96.36.199." :date nil :description nil :email "email@example.com" :exclude-tags ("noexport") :headline-levels 3 :keywords nil :language "en" :preserve-breaks nil :section-numbers t :select-tags ("export") :time-stamp-file t :title "file-options" :with-archived-trees headline :with-author t :with-clocks nil :with-creator comment :with-drawers nil :with-email nil :with-emphasize t :with-entities t :with-fixed-width t :with-footnotes t :with-inlinetasks t :with-plannings nil :with-priority nil :with-special-strings t :with-sub-superscript t :with-toc t :with-tables t :with-tags not-in-toc :with-tasks t :with-timestamps t :with-todo-keywords t :input-file "c:/Users/jkitchin/Dropbox/blogofile-jkitchin.github.com/_blog/file-options.org" :footnote-definition-alist nil :id-alist nil :macro-modification-time "(eval (format-time-string \"$1\" '(20870 38242)))" :macro-input-file "file-options.org" :macro-date "(eval (format-time-string \"$1\"))" :macro-time "(eval (format-time-string \"$1\"))" :macro-property "(eval (org-entry-get nil \"$1\" 'selective))" :back-end nil :translate-alist nil)
We can access a particular property like this:
(plist-get (org-export-get-environment) ':email)
Most of those properties are set externally to this file. It may be useful to set your own options in a file, e.g. we may want to set a STUDENT-ID tag in our file.
We want to be able to extract the value of that keyword in emacs-lisp code. Here is one org way to do that. This code parses the buffer to get a list of elements, then applies a lambda function that makes a (key . value) to all the keyword elements, finally returning an alist. The second function gets the value for a specific key.
; suggested by Nicolas Goaziou (defun jk-org-kwds () "parse the buffer and return a cons list of (property . value) from lines like: #+PROPERTY: value" (org-element-map (org-element-parse-buffer 'element) 'keyword (lambda (keyword) (cons (org-element-property :key keyword) (org-element-property :value keyword))))) (defun jk-org-kwd (KEYWORD) "get the value of a KEYWORD in the form of #+KEYWORD: value" (cdr (assoc KEYWORD (jk-org-kwds)))) (jk-org-kwd "STUDENT-ID")
That is serious org-fu there. Here is an alternative approach that uses searching and regular expressions. The idea is to use a case-insensitive regular expression search in the buffer to grab the value.
(defun jk-get-file-keyword (KEYWORD) "get the value from a line like this #+KEYWORD: value in a file." (interactive) (let ((case-fold-search t) (re (format "^#\\+%s:[ \t]+\\([^\t\n]+\\)" KEYWORD))) (if (not (save-excursion (or (re-search-forward re nil t) (re-search-backward re nil t)))) (error (format "No line containing #+%s: value found" KEYWORD))) (match-string 1))) (jk-get-file-keyword "STUDENT-ID")
Both methods basically do the same thing, and enable you to store data in org-files that is easily retrievable.
Copyright (C) 2013 by John Kitchin. See the License for information about copying.