How to add extra attributes to link in Org-mode?

Problem

In general, Org has a generic "solution" for this task:

#+ATTR_HTML: :rel nofollow
Lorem ispum [[https://example.com][dolor]]!

That will be exported to:

<p rel="nofollow">Lorem ispum <a href="https://example.com" rel="nofollow">dolor</a>!</p>

That in my very honest opinion it looks really awful.

Idea

I've decided to slightly change the link syntax but without breaking backwards compatibility:

[[https://example.com#|:rel nofollow :title View example.com]][Here is my link]]

This should be exported to:

<a href="https://example.com#" rel="nofollow" title="View example.com">Here is my link</a>

Looks a way better than the default solution. Even if end-user will user vanilla org-mode to export this piece all should be workable except the link anchor that's is not critical in most cases.

Implementation

After long looking through the endless code of the Org I found a function that is responsible for parsing link and providing link object —org-element-link-parser.

To add extra parameters to parser I've used advice :filter-retun— it returns the value for the original function to the advice-function for the further processing.

(require 's)
(require 'dash)

(defun pelican/org-link-extra-attrs (orig-fun &rest args)
  "Post processor for parsing links"
  (setq parser-result orig-fun)

  ;;; Retrieving inital values that should be replaced
  (setq raw-path (plist-get (nth 1 parser-result) :raw-link))
  (setq path (plist-get (nth 1 parser-result) :raw-link))

  ;; Checking if link match the regular expression
  (if (string-match-p "^https?://.*|\s?:" raw-path)
      (progn
	;; Retrieving and decoding parameters after the vertical bar
	(setq results (s-split "|" (url-unhex-string raw-path)))
	;; URL cleanup
	(setq raw-path (car results))
	(setq path (car (s-split "|" path)))

	;; Splitting elements by colon and remove any empty values
	(setq results (--drop-while (< (length it) 1)
				    (s-split ":" (car (-slice results 1)))))
	;; Splitting key and value and trimming any spaces
	(setq results (--map  (s-split-up-to "\s" (s-trim it) 1) results))

	;; Updating the ouput with the new values
	(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)))

	 ;; Or returning original value of the function
	 orig-fun))

(advice-add 'org-element-link-parser :filter-return #'pelican/org-link-extra-attrs)

Now we need to add the support of the new attributes to the parser for the HTML exporting backend.

(defun pelican/link-html (link desc info)
  "Custom syntax processor for extra link atrributes
  Syntax: [[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 link has any atrributes
  (if extra-attrs
      (progn
	;; Creating string formated as key="value"
	(setq extra-attrs
	      (s-trim (--reduce-from (concat acc " "
					     (format "%s=%s" (nth 0 it)
						     (s-wrap (nth 1 it)  "\""))) "" extra-attrs)))

	;; Inserting atrribute string to link tag
	(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 output
    processed-link-html))

;; Custom backend settings
(org-export-define-derived-backend 'pelican-html 'html
  :translate-alist '((link . pelican/link-html)))
comments powered by Disqus