Как экспортировать заголовки верхнего уровня буфера режима org в отдельные файлы?

17

Как можно org-modeэкспортировать каждый заголовок верхнего уровня буфера в отдельный файл, названный по значению соответствующего CUSTOM_ID+ (очищенного) заголовка?

Допустим, буфер содержит:

* Title of Heading 1
  :PROPERTIES:
  :CUSTOM_ID: fibrillogenesis
  :END:
  Suspendisse potenti. Mauris ac felis vel velit tristique imperdiet.  

** Sub-Heading
   Nullam rutrum.

* Another Title for Heading 2
  :PROPERTIES:
  :CUSTOM_ID: mitochondrion
  :END:
  Mauris mollis tincidunt felis.  Sed bibendum.

Конечным результатом будет каталог, содержащий два файла, по одному для каждого из двух заголовков верхнего уровня, с форматом, выбранным во время экспорта (HTML, LaTeX и т. Д.), Со следующими именами файлов и содержимым:

  1. Имя файла первого экспортируемого заголовка: fibrillogenesis-title-of-heading-1.[ext]

    Экспортируемый контент, соответствующий исходному первому заголовку верхнего уровня:

    * Title of Heading 1
      :PROPERTIES:
      :CUSTOM_ID: fibrillogenesis
      :END:
      Suspendisse potenti. Mauris ac felis vel velit tristique imperdiet.  
    
    ** Sub-Heading 
       Nullam rutrum.
    
  2. Имя файла второго экспортируемого заголовка: mitochondrion-another-title-for-heading-2.[ext]

    Экспортируемый контент, соответствующий исходному второму заголовку верхнего уровня:

    * Another Title for Heading 2
    :PROPERTIES:
    :CUSTOM_ID: mitochondrion
    :END:
    Mauris mollis tincidunt felis.  Sed bibendum. 
    

Я был бы очень благодарен за любую подсказку, направление, псевдокод или (лучше) реальный код.

GSL
источник

Ответы:

27

Следующая команда позволяет вам выбрать серверную часть и затем экспортировать каждое поддерево верхнего уровня в отдельный файл:

(defun org-export-all (backend)
  "Export all subtrees that are *not* tagged with :noexport: to
separate files.

Note that subtrees must have the :EXPORT_FILE_NAME: property set
to a unique value for this to work properly."
  (interactive "sEnter backend: ")
  (let ((fn (cond ((equal backend "html") 'org-html-export-to-html)
                  ((equal backend "latex") 'org-latex-export-to-latex)
                  ((equal backend "pdf") 'org-latex-export-to-pdf))))
    (save-excursion
      (set-mark (point-min))
      (goto-char (point-max))
      (org-map-entries (lambda () (funcall fn nil t)) "-noexport" 'region-start-level))))

В настоящее время поддерживается экспорт HTML ( html), LaTeX ( latex) и PDF ( pdf). Вы можете добавить поддержку большего количества бэкэндов, добавив больше предложений в cond.

Как говорит строка документации, для каждого поддерева вам нужно установить в качестве :EXPORT_FILE_NAME:свойства имя файла, в которое вы хотите экспортировать его. (См. Ниже для других вариантов.)

Автоматическое создание имени файла экспорта из текста заголовка

Если вы не хотите добавлять :EXPORT_FILE_NAME:свойства к каждому заголовку верхнего уровня, вы можете изменить его, org-export-allчтобы автоматически генерировать имя файла, например, из текста заголовка, временно устанавливая :EXPORT_FILE_NAME:во время экспорта:

(defun org-export-all (backend)
  "Export all subtrees that are *not* tagged with :noexport: to
separate files.

Subtrees that do not have the :EXPORT_FILE_NAME: property set
are exported to a filename derived from the headline text."
  (interactive "sEnter backend: ")
  (let ((fn (cond ((equal backend "html") 'org-html-export-to-html)
                  ((equal backend "latex") 'org-latex-export-to-latex)
                  ((equal backend "pdf") 'org-latex-export-to-pdf)))
        (modifiedp (buffer-modified-p)))
    (save-excursion
      (set-mark (point-min))
      (goto-char (point-max))
      (org-map-entries
       (lambda ()
         (let ((export-file (org-entry-get (point) "EXPORT_FILE_NAME")))
           (unless export-file
             (org-set-property
              "EXPORT_FILE_NAME"
              (replace-regexp-in-string " " "_" (nth 4 (org-heading-components)))))
           (funcall fn nil t)
           (unless export-file (org-delete-property "EXPORT_FILE_NAME"))
           (set-buffer-modified-p modifiedp)))
       "-noexport" 'region-start-level))))

Эта функция генерирует имя файла экспорта, заменяя пробелы на «_» в тексте заголовка. Если вы хотите сгенерировать имя файла другим способом, измените replace-regexp-in-stringsexp на любой другой.

Генерация :EXPORT_FILE_NAME:при настройке:CUSTOM_ID:

Следуя совету, org-set-propertyавтоматически установит соответствующее значение, :EXPORT_FILE_NAME:когда вы установите :CUSTOM_ID::

(defadvice org-set-property (after set-export-file-name
                                   (property value) activate compile)
  (when (equal org-last-set-property "CUSTOM_ID")
    (let ((export-file-name
           (concat (org-entry-get nil "CUSTOM_ID")
                   "-"
                   (replace-regexp-in-string " " "-" (downcase (org-get-heading t t))))))
      (org-entry-put nil "EXPORT_FILE_NAME" export-file-name))))

Обратите внимание, что это не добавит расширение файла к значению, :EXPORT_FILE_NAME:но это не имеет значения, потому что при экспорте в конкретный сервер org-mode автоматически выберет правильное расширение для результирующих файлов .


Дополнительная информация

Массовое обновление существующих поддеревьев

Если у вас есть большое количество существующих поддеревьев, для которых вам нужно установить :EXPORT_FILE_NAME:свойство, вы можете использовать макрос клавиатуры . Поместите точку на первое поддерево, затем сделайте следующее:

  • F3

    ... чтобы начать запись.

  • C-c C-x p CUSTOM_ID RET RET

    ... сделать набор Emacs :EXPORT_FILE_NAME:на основе :CUSTOM_ID:.

  • C-c C-f

    ... чтобы перейти к следующему заголовку верхнего уровня.

  • F4

    ... чтобы остановить запись.

Чтобы повторить макрос для следующего поддерева, нажмите F4. Чтобы повторить макрос для всех оставшихся поддеревьев, нажмите M-0 F4(это ноль).

Сохранение макросов для будущих сессий

По умолчанию клавиатурные макросы не сохраняются между сеансами. Чтобы сохранить макрос в вашем init-файле для дальнейшего использования, сделайте это:

  1. Назовите макрос:

    M-x name-last-kbd-macro RET org-set-export-file-name RET

  2. Найдите свой init-файл и переместитесь в то место, куда вы хотите вставить макрос.

  3. Вставьте макрос:

    M-x insert-kbd-macro RET org-set-export-file-name RET

    Emacs вставит следующий код в точку:

    (fset 'org-set-export-file-name
       "\C-c\C-xpCUSTOM_ID\C-m\C-m\C-c\C-f")

    Если вы щуритесь достаточно сильно, вы увидите, что второй аргумент fsetсодержит последовательность клавиш, которые вы нажимали при записи макроса :)

  4. (Необязательно) Для org-set-export-file-nameдостижения наилучших результатов, вы можете привязать к ключу:

    (define-key org-mode-map (kbd "<f6>") 'org-set-export-file-name)
  5. Сохранить.

itsjeyd
источник
1
Ницца. Не могли бы вы дать мне подсказку о том, как программно установить :EXPORT_FILE_NAME:свойство :CUSTOM_ID:+heading-title-lowercasedдля каждого заголовка?
GSL
1
@gsl Вы можете посоветовать org-set-propertyавтоматически генерировать :EXPORT_FILE_NAME:свойство при установке :CUSTOM_ID:.
itsjeyd
1
Спасибо за совет! Я не очень хорошо elispзнаю, но я постараюсь. Мне нужно выяснить, как захватить заголовок каждого заголовка, заменить пробел чертой, поместить в нижний регистр, добавить эту очищенную строку в :CUSTOM_ID:и, наконец, установить свойство org.
GSL
1
@gsl Я добавил defadviceк моему ответу , который автоматически устанавливает :EXPORT_FILE_NAME:на <custom-id>-<heading>при установке :CUSTOM_ID:.
itsjeyd
1
Большое спасибо, я многому научился с вашим кодом. Если у вас уже есть org-modeфайл с CUSTOM_IDустановленным s, как можно запустить код для установки «EXPORT_FILE_NAME»? Там не было бы новых вставок. Я думаю defadvice, не будет работать? Есть ли возможность циклического обхода всех заголовков верхнего уровня и применения к ним кода?
gsl