You can do it like this:
#+begin_src elisp :results drawer
(defun my/org-get-filetags (e)
(let ((type (org-element-type e))
(key (org-element-property :key e))
(value (org-element-property :value e)))
(if (and (eq type 'keyword) (equal key "FILETAGS"))
(string-split value ":" t))))
(org-element-map (org-element-parse-buffer) '(keyword) #'my/org-get-filetags)
#+end_src
#+RESULTS:
:results:
((foo bar baz) (tag1 tag2 tag3))
:end:
The Org mode file I used for the testing has two #+FILETAGS:
keywords like this:
#+FILETAGS: :foo:bar:baz:
#+FILETAGS: :tag1:tag2:tag3:
...
That explains the output of the code block.
This asks org-element-map
to act on the parse tree of the buffer, selecting only keyword
s (like #+FILETAGS
) and applying the function my/org-get-filetags
on each such element.
The function itself selects the key
and value
properties of the element, checks that the type of the element is keyword
and the key is the string "FILETAGS"
and, if so, splits the value (a string of the form ":tag1:tag2:tag3:"
on the :
separator and returns the list of tags (omitting the opening and closing null strings because of the final t
argument given to string-split
. See its doc string with C-h f string-split
for the details.)
org-element-map
wraps all the return values in a list, so the result is going to be something like (("foo" "bar" "baz") ("tag1" "tag2" "tag3"))
which you might need to massage some more for your purposes, but I'll leave that to you.
If your file is big, then parsing the whole buffer to get the single keyword you are interested in will be relatively slow. You might be better off if you first search for #+FILETAGS:
, moving to the beginning of the line and then using (org-element-at-point)
which does a partial parse around point
. There would be no need for org-element-map
then:
#+begin_src elisp :results drawer
(progn
(save-excursion
(goto-char (point-min))
(search-forward "#+FILETAGS:")
(beginning-of-line)
(my/org-get-filetags (org-element-at-point))))
#+end_src
#+RESULTS:
:results:
(foo bar baz)
:end:
Note that we are reusing the same function as in the first code block to get the tags, and that we pass to it explicitly the single element that org-element-at-point
returns - in the previous case, org-element-map
would call it repeatedly, implicitly passing each element in the parse tree to it. The final result is a list of tags, rather than a list of lists of tags, since org-element-map
is not part of this implementation, so there is no wrapping of return values. This assumes there is a single #+FILETAGS:
keyword in the file, otherwise it will only find the first one (as in this case).
But you could loop searching for all of them:
#+begin_src elisp :results drawer
(defun my/org-get-ALL-filetags ()
(let ((ret))
(goto-char (point-min))
(while (search-forward "#+FILETAGS:" nil t)
(save-excursion
(beginning-of-line)
(setq ret (cons (my/org-get-filetags (org-element-at-point)) ret))))
(nreverse (delq nil ret))))
(my/org-get-ALL-filetags)
#+end_src
#+RESULTS:
:results:
((foo bar baz) (tag1 tag2 tag3))
:end:
This produces the same list of lists structure as the org-element-map
implementation, at the cost of complicating the return value of the function a bit: we cons each list to the front, delete any nil
s (which might be produced when you have the code block as part of the file, because the search succeeds not only for the "real" #+FILETAGS:
keywords but also for the "fake" instance in the code block itself - the search finds the string in the function, but the parsing of the line does not return a keyword
type and my/org-get-filetags
applied to the element at point
returns nil
which gets consed to the return value). cons
adds each list of tags to the front, so we have to reverse the list to produce the "natural" order.