Как мне пройтись по дереву в режиме орг?

10

Фон

Я пишу режим презентации для Emacs. Я хотел бы, чтобы вводом были файлы org, так как файлы org отлично подходят для данных.

проблема

Я должен преобразовать файл режима org в список «слайдов» структур данных, которые я могу перебирать. Для этого я хотел бы взять что-то вроде следующего файла режима org:

* 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

и быть в состоянии идти это. Я пытался (org-element-parse-buffer), и это дает мне список элементов, но трудно понять, как идти дальше в них. Например, вызов (org-element-map (org-element-parse-buffer) 'headline #'identity)дает список из трех элементов; последний представляет «вложенный заголовок». Я хочу, чтобы «вложенный заголовок» был потомком «это второй заголовок с содержимым».

Как избежать проблемы XY

Я, конечно, открыт для других способов преобразования файла режима org в структуру данных Elisp. Я не думаю, что org-export является подходящим инструментом для меня, потому что я не хочу получить новый файл, содержащий результаты, но структуру данных, через которую я могу перебирать. Мой наивный способ - что-то вроде «дать мне все заголовки верхнего уровня, и тогда я смогу получить их свойства и содержащиеся в них элементы (например, простой текст или вложенные списки - будь то дополнительные заголовки или списки тире)».

zck
источник
2
Я считаю , что третий дополнительный аргумент no-recursionв org-element-mapдолжен делать то , что вы хотите.
wvxvw
2
Как насчет перехода в конец файла, а затем поиска заголовка в обратном направлении, захвата всего, а затем продолжения - повторяя процесс - пока не дойдете до верхней части файла, а затем бросьте «Готово»? Мы возвращаемся назад, потому что точка уже находится в начале заголовка после каждого поиска, поэтому это более эффективно, чем идти вперед, а затем немного вернуться назад, чтобы оказаться в начале заголовка. Вот как работает org-повестка дня - то есть org-повестки дня-список, org-search-view, org-tags-view.
юрист

Ответы:

7

У меня была похожая проблема, поэтому, возможно, это поможет - я не очень хорошо знаком с экспортом org и внутренностями org, но я не смог найти ничего, что могло бы проанализировать файл org в древовидной структуре. Но учитывая буфер как

* england
** london
** bristol
* france

это даст вам

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

и может включать в себя другую информацию из дерева, а также.


Таким образом, учитывая плоский список уровней, нам нужно создать дерево, например (1 1 2 3 1) => (1 1 (2 (3)) 1). Я не мог найти функцию, которая бы делала это, поэтому написал одну после большого количества отрисовок cons-ячеек - я уверен, что есть лучший способ сделать это, но это работает. Функция unflattenберет плоский список и несколько функций для извлечения необходимой информации из списка и уровней элементов и создает древовидную структуру.

В org-get-header-listможно добавить дополнительную информацию вы хотите извлечь из каждого пункта с вызовами org-element-property, а затем в org-get-header-treeвас можете включать в себя функцию для извлечения информации из списка.

В нынешнем виде это не включает обработку списков тире, но, возможно, его можно адаптировать для обработки этих списков без особых проблем ...


(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")
Брайан Бернс
источник