2

I'd like to extract from an org file a top-level headline and the plain text just under it. I'm new to elisp, but i've tried a bunch of things, and haven't made much progress. I'd really appreciate some pointers.

Suppose we've got the file:

* Test

Some descriptive text

** TODO Test Org mode
** TODO Keep testing

I'd like to include just the top headline and the descriptive text just under it in another file. (Not using #+INCLUDE searches, which for some reason I also can't get to work). So I'm trying to extract just a piece of this buffer's AST.

I'm trying to use org-map-entries because it will be able to look through all my agenda files, each of which relates to a different project.

First, does org-map-entries just not allow me to access children of headlines? (I think the text description is in a section that's a child of the first headline. Right?)

Assuming that's true, I've tried to org-element-parse-buffer of the whole buffer when I find a top-level headline. With the whole AST, I think I should be able to get the text just under the top-level headline. But I'm stumped.

Any pointers?

(defun get-headline-with-text ()
    (let ((entry (org-element-at-point)))
        (if (eq 1 (org-element-property :level entry))
            (let ((hdl (nth 2 (org-element-parse-buffer))))
                 (list (org-element-property :raw-value hdl) 
                       (org-element-property :value (car (org-element-contents hdl))))
            )
            nil)
    )
)

(org-map-entries '(get-headline-with-text) t nil)

2 Answers 2

1

First, does org-map-entries just not allow me to access children of headlines? (I think the text description is in a section that's a child of the first headline. Right?)

org-map-entries by default walks through the full data. So it gives you access to children of headlines if you generated the data with (org-element-parse-buffer).

The section consists of the text below the headline up to the next headline.

Assuming that's true, I've tried to org-element-parse-buffer of the whole buffer when I find a top-level headline. With the whole AST, I think I should be able to get the text just under the top-level headline.

The following version of your function shows how you get the bounds of the content with org-element-map.

(defun get-headline-with-text ()
  "Return a list with the headline text of the top-level headline for point as first element and the section text as second element."
  (interactive)
  (require 'subr-x) ;; for when-let
  (when-let ((data (org-element-parse-buffer 'greater-elements)) ;; sparse parsing...
         (headline (org-element-map
               data
               'headline
             (lambda (el)
               (let ((beg (org-element-property :begin el))
                 (end (org-element-property :end el)))
                 (and (>= (point) beg)
                  (<= (point) end)
                  el)))
             nil
             'first-match
             'no-recursion))
         (headline-text (org-element-property :raw-value headline))
         (section (org-element-map
              headline
              'section
            #'identity
            nil
            'first-match
            'no-recursion))
         (text-begin (org-element-property :contents-begin section))
         (text-end (org-element-property :contents-end section)))
    (list headline-text (buffer-substring text-begin text-end))))
5
  • Shouldn't you put require outside of the function body?
    – user12563
    Commented Mar 19, 2019 at 19:32
  • @DoMiNeLa10 It depends since when-let is a macro. If the stuff goes into an initialization file like ~/.emacs the current implementation is best. Initialization files should not be compiled. Thus subr-x can be loaded right before the usage of when-let. If the stuff goes into a byte-compiled library it is best to put (require 'subr-x) outside the function wrapped into eval-when-compile since the macro is (only) needed at compile time.
    – Tobias
    Commented Mar 19, 2019 at 20:32
  • eval-when-compile works in interpreted scripts as well, so code might be written in such a way that it could be moved into a utility library and compiled when needed.
    – user12563
    Commented Mar 19, 2019 at 20:42
  • @DoMiNeLa10 The motivation for putting the require into the function body is that we probably do not need to load subr-x.elc at all if we never call get-headline-with-text.
    – Tobias
    Commented Mar 20, 2019 at 1:06
  • The output of the function is clearer if you use buffer-substring-no-properties insead of buffer-substring. Then (car (cdr (get-headline-with-text))) gets the text.
    – Heikki
    Commented Mar 20, 2019 at 8:51
2

First, does org-map-entries just not allow me to access children of headlines? (I think the text description is in a section that's a child of the first headline. Right?)

TBH I'm not sure, but you can use org-element-map to save the position of each top-level headline. Once you have the position, go to it and move the point to the next heading. Everything between points a and b is what you need.

(defun get-headline-with-text ()
  (let (hl1
        (hls (org-element-map (org-element-parse-buffer)
                 'headline #'identity)))
    (dolist (hl hls)
      (when (= (org-element-property :level hl) 1)
        (push (org-element-property :begin hl) hl1)))
    (setq hl1
          (cl-loop for hl in hl1
                   for beg = (save-excursion
                               (goto-char hl)
                               (point))
                   for end = (save-excursion
                               (goto-char hl)
                               (outline-next-visible-heading 1)
                               (point))
                   for result = (buffer-substring-no-properties beg end)
                   collect result))
    (nreverse hl1)))

Update

The function above parses all top-level headlines in the buffer, which is not what the question asks. Here's another attempt:

(defun get-headline-with-text ()
  (save-excursion
    (save-restriction
      (widen)
      (ignore-errors (outline-up-heading 1))
      (let* ((elt (org-element-at-point))
             (title (org-element-property :title elt))
             (beg (progn (org-end-of-meta-data t) (point)))
             (end (progn (outline-next-visible-heading 1) (point))))
        (list title (buffer-substring-no-properties beg end))))))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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