Background

I'm writing a presentation mode for Emacs. I'd like the input to be org files, as org files are great for data.

Problem

I have to convert the org mode file into a list of "slide" data structures that I can iterate through. To do this, I'd like to take something like the following org-mode file:

* this is the first headline, with a title property and no contents
* this is the second headline, with contents
- dash list nested under the second headline
  - further nested
** nested headline

and be able to walk it. I've tried (org-element-parse-buffer), and that does give me a list of elements, but it's hard to figure out how to walk further in them. For example, calling (org-element-map (org-element-parse-buffer) 'headline #'identity) gives a list of three elements; the last one represents the "nested headline". I want "nested headline" to be a child of "this is the second headline, with contents".

Avoiding the XY problem

I'm certainly open to other ways of converting an org-mode file into an Elisp data structure. I don't think org-export is the right tool for me, because I don't want to end up with a new file containing the results, but a data structure I can iterate through. My naive way is something like "give me all the top-level headlines, and then I can get their properties and contained elements (e.g., plain text or nested lists -- whether further headlines or dash lists)".

  • 2
    I believe the third optional argument no-recursion of org-element-map should do what you want. – wvxvw Oct 24 '15 at 8:51
  • 2
    How about going to the bottom of the file and then search backwards for a heading, grab everything, and then keep going -- repeating the process -- until you reach the top of the file, then throw done? We go backwards because point is already at the beginning of the heading after each search, so it's more efficient than going forwards and then going back a bit to be at the beginning of the heading. This is how org-agenda works -- i.e., org-agenda-list, org-search-view, org-tags-view. – lawlist Oct 25 '15 at 16:15

I had a similar problem, so maybe this will help, at least partially - I'm not very familiar with org export or org internals, but I couldn't find anything that would parse an org file to a tree structure, just flat list representations. But given a buffer like

* england
** london
** bristol
* france

it will give you

(org-get-header-tree) => ("england" ("london" "bristol") "france")

but can return other information as well.


So given a flat list of levels we need to produce a tree, e.g. (1 1 2 3 1) => (1 1 (2 (3)) 1). I couldn't find a function that would do this either so wrote one after much drawing of cons cells - I'm sure there's a better way of doing this but it works. The function unflatten takes a flat list and couple of functions to extract the information you want from the list and the item levels and produces a tree structure.

In org-get-header-list you could add more information you want to extract from each item with calls to org-element-property, and then in org-get-header-tree you could include functions to extract the information from the list.

As it stands this doesn't include handling for dash lists, but maybe it could be adapted to handle those also without too much trouble...

(defun unflatten (xs &optional fn-value fn-level)
  "Unflatten a list XS into a tree, e.g. (1 2 3 1) => (1 (2 (3)) 1).
FN-VALUE specifies how to extract the values from each element, which
are included in the output tree, FN-LEVEL tells how to extract the
level of each element. By default these are the `identity' function so
it will work on a list of numbers."
  (let* ((level 1)
         (tree (cons nil nil))
         (start tree)
         (stack nil)
         (fn-value (or fn-value #'identity))
         (fn-level (or fn-level #'identity)))
    (dolist (x xs)
      (let ((x-value (funcall fn-value x))
            (x-level (funcall fn-level x)))
        (cond ((> x-level level)
               (setcdr tree (cons (cons x-value nil) nil))
               (setq tree (cdr tree))
               (push tree stack)
               (setq tree (car tree))
               (setq level x-level))
              ((= x-level level)
               (setcdr tree (cons x-value nil))
               (setq tree (cdr tree)))
              ((< x-level level)
               (while (< x-level level)
                 (setq tree (pop stack))
                 (setq level (- level 1)))
               (setcdr tree (cons x-value nil))
               (setq tree (cdr tree))
               (setq level x-level)))))
      (cdr start)))

; eg (unflatten '(1 2 3 2 3 4)) => '(1 (2 (3) 2 (3 (4))))


(defun org-get-header-list (&optional buffer) 
  "Get the headers of an org buffer as a flat list of headers and levels.
Buffer will default to the current buffer."
  (interactive)
  (with-current-buffer (or buffer (current-buffer))
    (let ((tree (org-element-parse-buffer 'headline)))
      (org-element-map 
          tree 
          'headline
        (lambda (el) (list 
                 (org-element-property :raw-value el) ; get header title without tags etc
                 (org-element-property :level el) ; get depth
                 ;; >> could add other properties here
                 ))))))

; eg (org-get-header-list) => (("pok" 1) ("lkm" 1) (("cedar" 2) ("yr" 2)) ("kjn" 1))


(defun org-get-header-tree (&optional buffer)
  "Get the headers of the given org buffer as a tree."
  (interactive)
  (let* ((headers (org-get-header-list buffer))
         (header-tree (unflatten headers  
                 (lambda (hl) (car hl))  ; extract information to include in tree
                 (lambda (hl) (cadr hl)))))  ; extract item level
    header-tree))

; eg (org-get-header-tree) => ("pok" "lkm" ("cedar" "yr") "kjn")

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.