Додаємо атрибути до посилань 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)))
comments powered by Disqus