Getting keyword options in org-files

| categories: org-mode | tags:

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" :date nil :description nil :email "" :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/" :footnote-definition-alist nil :id-alist nil :macro-modification-time "(eval (format-time-string \"$1\" '(20870 38242)))" :macro-input-file "" :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.

#+STUDENT-ID: jkitchin

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."
  (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.

org-mode source

Discuss on Twitter