Додаємо атрибути до посилань Org-mode
Проблема
В загальному, Org пропонує штатні "милиці" для вирішення цього завдання:
#+ATTR_HTML: :rel nofollow
Lorem ispum [[https://example.com][dolor]]!
Що в результаті перетвориться на:
<p rel="nofollow">Lorem ispum <a href="https://example.com" rel="nofollow">dolor</a>!</p>
Виглядає це все вкрай жахливо, і сенсу користуватись таким рішенням я не бачу жодного.
Ідея
Я вирішив дещо змінити синтаксис посилань, при цьому не ламаючи зворотню сумісність.
[[https://example.com#|:rel nofollow :title View example.com]][Here is my link]]
Має трансформуватись в:
<a href="https://example.com#" rel="nofollow" title="View example.com">Here is my link</a>
Виглядає значно краще ніж штатний варіант. Навіть якщо інший користувач в стоковому org-mode експортуватиме цей фрагмент, то поламається лише "якір" (anchor), що в більшості випадків не є критичним.
Реалізація
Після довгих пошуків по безкінечному коду Org було знайдено функцію яка відповідає за парсинг і формування об'єкту посилання —org-element-link-parser
. Для додавання парсингу додаткових параметрів ідеально підходить advice :filter-retun
який повертає значення оригінальної функції в advice-функцію для подальшої обробки.
(require 's)
(require 'dash)
(defun pelican/org-link-extra-attrs (orig-fun &rest args)
"Пост процесор для парсингу посилань"
(setq parser-result orig-fun)
;;; Отримуємо початкові значення які потрібно замінити
(setq raw-path (plist-get (nth 1 parser-result) :raw-link))
(setq path (plist-get (nth 1 parser-result) :raw-link))
;; Перевіряємо чи посилання відповідає регулярному виразу
(if (string-match-p "^https?://.*|\s?:" raw-path)
(progn
;; Отримуємо параметри після вертикального слешу і декодуємо їх
(setq results (s-split "|" (url-unhex-string raw-path)))
;; Очищаємо URL
(setq raw-path (car results))
(setq path (car (s-split "|" path)))
;; Розділяємо елементи за двокрапою і видаляємо пусті елементи (якщо є)
(setq results (--drop-while (< (length it) 1)
(s-split ":" (car (-slice results 1)))))
;; Розділяємо ключ і значення і видаляємо зайві пробіли
(setq results (--map (s-split-up-to "\s" (s-trim it) 1) results))
;; Оновлюємо вивід парсера новими атрибутами
(setq orig-fun-cleaned (plist-put (nth 1 orig-fun) :raw-link raw-path))
(setq orig-fun-cleaned (plist-put orig-fun-cleaned :path path))
(list 'link
(-snoc orig-fun-cleaned :extra-attrs results)))
;; Або повертаємо значення оригінальної функції
orig-fun))
(advice-add 'org-element-link-parser :filter-return #'pelican/org-link-extra-attrs)
Тепер залишилось додати підтримку нових атрибутів парсеру при експорті посилань в HTML.
(defun pelican/link-html (link desc info)
"Процесор власного синтаксисису для задання атрибутів посилань.
Синтаксис: [[http://example.com/#|:attr value][Link text]]"
(setq extra-attrs (org-element-property :extra-attrs link))
(setq processed-link-html (org-html-link link desc info))
;; Якщо посилання має будь які атрибути
(if extra-attrs
(progn
;; Створюємо форматований рядок атрибутів в форматі key="value"
(setq extra-attrs
(s-trim (--reduce-from (concat acc " "
(format "%s=%s" (nth 0 it)
(s-wrap (nth 1 it) "\""))) "" extra-attrs)))
;; Вставляємо параметри в тег посилання
;; Приклад: <a href="#" name="Foo">Bar</a>
(setq insert-point (string-match ">" processed-link-html))
(setq processed-link-html
(concat (substring processed-link-html 0 insert-point)
extra-attrs
(substring processed-link-html insert-point))))
;; Повертаємо HTML
processed-link-html))
;; Додаємо налаштування до власного бекенду
(org-export-define-derived-backend 'pelican-html 'html
:translate-alist '((link . pelican/link-html)))