Changing links to files so they work in a blog

| categories: org-mode | tags:

1 Getting links to files in a post to point to the actual file

Occasionally I use a data file in post. In the local org-file buffer I can make a link to the file, and it opens just fine. However, when I publish the post, there is not a mechanism to copy the file to the blog directory, and rewrite the link in exported html so it opens the file.

1.1 link one

This is an explicit file link

1.2 link two

Here we just use a path inside [[]]

1.3 link three

Here we use a decorated link. Org-file containing news

1.4 Checkout all the links

Let us now check out the links. We will use org-element-map to parse the elements and print the link number, the link type, and the :path. For fun, let us put an emacs-lisp link in:

elisp:(princ "test")
and a link to an image:

And finally, I want a link to a file in another directory:

(let ((parsetree (org-element-parse-buffer))
      (counter 0))
  (org-element-map parsetree 'link
    (lambda (link) 
      (let ((type (nth 0 link))
            (plist (nth 1 link))
            (content (nth 2 link)))
        (princ (format "%s %s: %s %s\n" counter (plist-get plist ':type) (plist-get plist :path) content))
        (setq counter (+ counter 1))))))
0 file: nil
1 file: ./ nil
2 file: ./ Org-file containing news
3 elisp: (princ "test") nil
4 file: ../img/tooltip-emacs.png nil
5 file: ../ nil
6 http: // nil
7 https: // blogofile.el

So we can see all the links this way, and we will have to differentiate what kind of links they are. What needs to happen when exporting is that the files we want to refer to in the post must be copied to the media folder of the blog, and a url to that file needs to be put in the exported html in the place of the link. We are going to try the following strategy:

  1. Parse the org-file.
  2. For each file link we will copy the file to the blogofile media folder and make a list of URLS for each link
  3. Write a link filter that gets the nth value of our list of urls and replace the link text with a url to the file. If the link had any content, use that for the URL text.

Ok. Now let us write a block of code that does all those things. I want to avoid overwriting files with the same name from other posts, so we will copy all these files to a directory based on the blog post title: "Changing-links-to-files-so-they work-in-a-blog". We construct URLS for each link type that we want and store them in a list in the variable url-list.

(setq url-list (let ((parsetree (org-element-parse-buffer))
                     (counter 0))
                 (org-element-map parsetree 'link
                   (lambda (link) 
                     (let* ((type (nth 0 link))
                            (plist (nth 1 link))
                            (content (nth 2 link))
                            (path (plist-get plist :path))
                            (type (plist-get plist ':type))
                            (fname (car (last (split-string path "/"))))
                       ;; make directory if it does not exist
                       (make-directory "../media/Changing-links-to-files-so-they work-in-a-blog" t)
                        ;; image
                        ((and (string= type "file")
                              (string-match "png" (file-name-extension fname))) 
                           (copy-file path (concat "../media/Changing-links-to-files-so-they work-in-a-blog/" fname) t)
                           (format "<img src=\"/media/Changing-links-to-files-so-they work-in-a-blog/%s\">" fname)))
                        ;; regular file with content
                        ((and (string= type "file")  content)
                         (progn (copy-file path (concat "../media/Changing-links-to-files-so-they work-in-a-blog/" fname) t)
                                (format "<a href=\"/media/Changing-links-to-files-so-they work-in-a-blog/%s\">%s</a>" fname content)))
                        ;; regular file with no content
                        ((and (string= type "file"))
                         (progn (copy-file path (concat "../media/Changing-links-to-files-so-they work-in-a-blog/" fname) t)
                                (format "<a href=\"/media/Changing-links-to-files-so-they work-in-a-blog/%s\">%s</a>" fname fname)))
                        ;; URLS
                        ((string-match "http" type) (format "<a href=\"%s\">%s</a>" 
                          (plist-get plist :raw-link) (plist-get plist :raw-link)))
                        ;; all other links will be formatted as <pre> blocks on the raw link
                        (t (format "<pre>%s</pre>" (plist-get plist :raw-link)))))))))
(princ url-list)
;(princ (org-element-parse-buffer))
(<a href="/media/Changing-links-to-files-so-they work-in-a-blog/"></a> <a href="/media/Changing-links-to-files-so-they work-in-a-blog/"></a> <a href="/media/Changing-links-to-files-so-they work-in-a-blog/">Org-file containing news</a> <pre>elisp:(princ "test")</pre> <img src="/media/Changing-links-to-files-so-they work-in-a-blog/tooltip-emacs.png"> <a href="/media/Changing-links-to-files-so-they work-in-a-blog/"></a> <a href=""></a> <a href=""></a>)

Now, we are ready to consider the filter. Again, building off of are going to setup a counter, increment the counter in the filter, and get the nth url from the list we constructed above, and replace the text if the URL is not nil.

(let ((counter 0))

  (defun ox-mrkup-filter-link (text back-end info)
    (let ((url (nth counter url-list)))
      (if (not (string= url "nil")) (setq output   (format "%s" url))
        (setq output (format "%s" text)))
      (setq counter (+ counter 1))

  (let ((org-export-filter-link-functions '(ox-mrkup-filter-link))
        (async nil)
        (subtreep nil)
        (visible-only nil)
        (body-only t)
        (ext-plist '()))
    (org-html-export-as-html async subtreep visible-only body-only ext-plist))

    ; now get the output into the org output
    (switch-to-buffer "*Org HTML Export*")

    (setq HTML (buffer-string))
    (setq YAML "---
title: Changing links to files so they work in a blog
date: 2013/09/28 17:49:00
updated: 2013/09/29 08:14:00
categories: org-mode

(insert YAML)
(insert HTML)
(write-region (point-min) (point-max) "../_posts/2013-09-28-Changing-links-to-files-so-they-work-in-a-blog.html")))
(princ "Done.")

You can see from this that we were able to process the org-buffer to get all the links, identify which of those links were external files that were not images (png files), and copy those files to our blog directory. Finally, we used a filter to modify the link text to point towards the blog url for publishing to HTML. I don't have this fully automated in, but now all the pieces are done to enable that one day!

Discuss on Twitter