1

Given an org document, I'd like to traverse all headlines of the given level and extract the headline title, provided that:

  1. Headline title can be generated by inline source blocks that use node properties, and
  2. I don't know beforehand what are the names of the node properties that are going to be used to generate the headline.

For example for the document given below:

* Section about src_elisp{(org-entry-get nil "Name")} :this:
:PROPERTIES:
:Name:     J.S. Bach
:END:

* Section about src_elisp{(org-entry-get nil "Composer")} :this:
:PROPERTIES:
:Composer:     W.A. Mozart
:END:

* Code
#+begin_src elisp :eval yes :results output :exports both

(defun level1-headlines-filter ()
  (setq headline (org-entry-get nil "ITEM"))
  (setq props (org-get-entry))
  (setq string (concat headline "\n" props))
  (setq string (org-export-string-as string 'org t '(:with-toc nil)))
  (setq all (list string)))

(setq HEADLINES-PARSED (org-map-entries #'level1-headlines-filter "LEVEL=1+this") )

(print HEADLINES-PARSED)

#+end_src

HEADLINES-PARSED should contain strings:

"Section about J.S. Bach"
"Section about W.A. Mozart"

Now the results are:

(("Section about 
:PROPERTIES:
:Name:     J.S. Bach
:END:
") ("Section about 
:PROPERTIES:
:Composer:     W.A. Mozart
:END:
"))
2
  • org-export-string-as copies the given string to a temp buffer and runs org-export-as on that buffer. You give it only the headline as the string to copy, so it is missing the PROPERTIES drawer and the source block evaluation does not find any property and returns an empty string. You have to at least copy the PROPERTIES drawer as part of the string that you want to export, not just the headline.
    – NickD
    Nov 15, 2023 at 10:22
  • @NickD Basing on your comment I have rewritten the code a bit. I moved forward a bit, the output changed but it is still not the result I expect to obtain. Could you provide some more help please? Nov 15, 2023 at 19:01

2 Answers 2

2

As I pointed out, the headline by itself is not enough for org-export-string-as to work: it is missing the context of the PROPERTIES drawer, so trying to evaluate source blocks that look for properties in the temp buffer that org-export-string-as constructs is not going to work.

In addition, the headline is no longer a headline: it has lost the initial * that made it into a headline. So that has to be added back to the string, before org-export-string-as can have any hope of working.

Somewhat surprisingly, that seems to be enough, at least for the simple example you provide. The function level1-headlines-filter can and should be simplified: there is no need for all the variables, although I've kept a couple of local variables for readability's sake below. The following seems to work:

(defun level1-headlines-filter ()
  (let (headline props)
    (setq headline (org-entry-get nil "ITEM"))
    (setq props (org-get-entry))
    (org-export-string-as (concat "* " headline "\n" props) 'org t '(:with-toc nil))))

I get:

#+RESULTS:
:results:

("* Section about =J.S. Bach=
" "* Section about =W.A. Mozart=
")
:end:

BTW, the verbatim markup in this result seems to be a bug, but it has not been resolved yet AFAIK. The commit that I pointed out as fixing the bug (in the upstream main branch only alas) you ran into in your previous question points to a conversation on the mailing list where this point is discussed as another bug (see further down the thread): a workaround is shown, but no final resolution is reached.

1
  • Thank you Nick! The verbatim markup is indeed a problem in my case. Fortunately I have circumvented it by removing unnecessary characters from the output strings (see my answer). Rather brute force approach but it works in my particular case. Nov 15, 2023 at 22:37
1

Basing on NickD's helpful comments I have come up with the solution that satisfies my personal needs in this case. The code:

* Section about src_elisp{(org-entry-get nil "Name")} :this:
:PROPERTIES:
:Name:     J.S. Bach
:END:

* Section about src_elisp{(org-entry-get nil "Composer")} :this:
:PROPERTIES:
:Composer:     W.A. Mozart
:END:

* Code
#+begin_src elisp :eval yes :results output :exports both

(defun replace-in-string (what with in)
  (replace-regexp-in-string (regexp-quote what) with in nil 'literal))


(defun level1-headlines-filter ()
  (let (headline props)
    (setq headline (org-entry-get nil "ITEM"))
    (setq props (org-get-entry))
    (setq string 
          (replace-in-string "\n" "" 
          (replace-in-string "* " "" 
          (replace-in-string "=" "" 
          (org-export-string-as 
            (concat "* " headline "\n" props) 
            'org t '(:with-toc nil))))))
   ))

(setq HEADLINES-PARSED (org-map-entries #'level1-headlines-filter "LEVEL=1+this") )

(print HEADLINES-PARSED)

#+end_src

generates expected:

#+RESULTS:
: 
: (("Section about J.S. Bach") ("Section about W.A. Mozart"))
2
  • I would get rid of the (setq string (and the corresponding closing paren): the function works by returning the string, so you don't need to store it.
    – NickD
    Nov 15, 2023 at 23:01
  • 1
    Well... after a while spent on thinking about it, it is possible rewrite the function in such a way that there is no need to use setq at all. Those setqs are just artifacts from my programming in non-functional languages. They help me to understand what is going on between all those billions of parenthesis ;) Nov 16, 2023 at 7:18

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.