Xah Lee, 2006-10
This page shows a example of writing a emacs lisp function that creates a HTML link on a url string in the buffer. If you don't know elisp, first take a look at Emacs Lisp Basics.
Suppose you work in HTML a lot, and often need to make a URL into a link.
Suppose you have this URL text in your buffer:
http://en.wikipedia.org/wiki/Emacs
and your cursor is on that line. You want to be able to, press a button, and have the text changed to
<a href="http://en.wikipedia.org/wiki/Emacs">Emacs</a>
This page shows you how to write this function.
First, our function will be of this form:
(defun wrap-url () "Wee! make you a link!" (interactive) ; 1. grab the url in the current line ; 2. parse the url into a list, and grab the last element ; this will be our linktext ; 3. insert string “<a href="” url “">” linktext “</a>” )
We will construct little lisp functions for each step, then put them all together.
; this snippet grabs the current block of text delimited by space. ; the key lies in the versatile re-search-backward and re-search-forward ; and buffer-substring (defun ff () "This snippet grabs the sequence of chars delimited by space." (interactive) (let (x1 x2 url) (progn (re-search-backward " ") (setq x1 (re-search-forward " ")) (re-search-forward " ") (setq x2 (re-search-backward " ")) (setq url (buffer-substring x1 x2)) (kill-region x1 x2) (insert url) ) ) )
Now, we write another test function that returns the last part in a URL separated by slash. For example, http://en.wikipedia.org/wiki/Green_tea will return Green_tea.
The key to write this function is the function split-string.
(defun ff () "test" (interactive) (let ((url "http://en.wikipedia.org/wiki/Green_tea") linktext) (insert (car (last (split-string url "/"))) ) ) )
Now, we have the URL string and the linktext string. We need to concatenate them into one string. The key function is “concat”
; concatenate strings (defun ff () "test" (interactive) (insert (concat "a" "b" "c")) )
Now we know how to code all the major steps for our wrap url function. We can put them together.
(defun wrap-url () "Make thing at cursor point into a html link.\n Example: http://en.wikipedia.org/wiki/Emacs becomes <a href=\"http://en.wikipedia.org/wiki/Emacs\">Emacs</a>" (interactive) (let (x1 x2 url linktext) (progn (re-search-backward " ") (setq x1 (re-search-forward " ")) (re-search-forward " ") (setq x2 (re-search-backward " ")) (setq url (buffer-substring x1 x2)) (setq linktext (car (last (split-string url "/"))) ) ) (delete-region x1 x2) (insert (concat "<a href=\"" url "\">" linktext "</a>")) ) )
Now, we can test this. For example, here is a URL:
article http://en.wikipedia.org/wiki/Emacs Emacs ...
and put your cursor on the URL, and type “M-x wrap-url”. The URL will turn into a link.
Now we need to refine the code and do some clean up. For example, if the URL starts on a line by itself, the function won't work, because it is looking for spaces as the URL's delimiters. We can fix this by adding more chars in argument of the re-search-* functions.
Also, suppose, if the link is not a wikipedia link, but a generic link such as “http://yahoo.com/news/3829.php” then it makes little sense to have the link text shown as “3829.php”. So, we modify the code so that if it is not a wikipedia link, the link text should be the full url.
Also, some links contain percent encoded characters. For example:
http://en.wikipedia.org/wiki/Europa_%28mythical%29
The “%28” and “%29” is really the left parenthesis and right parenthesis. The linktext should show them as the decoded chars as in “Europa (mythical)”.
Another thing is that, wrap-url may be a useful function that can be called by other functions. So, it would be useful, to make wrap-url take a string input and return the processed text as output. Then write a wrapper function that calls wrap-url for interactive use.
The following is the result of this clean up.
The wrap-url-string is a function that takes a string and return a string.
(defun wrap-url-string (url &optional raw) "Make the string into a html link.\n Example: http://en.wikipedia.org/wiki/Emacs becomes <a href=\"http://en.wikipedia.org/wiki/Emacs\">Emacs↗</a>. If the url is a wikipedia link, then the link text is shortened to its title. If the optional argument raw is not nil, no shortening will happen." (require 'gnus-util) (let ((linktext url)) (setq linktext (gnus-url-unhex-string linktext nil)) (if (and (not raw) (string-match "wikipedia.org" url)) (progn (setq linktext (concat (car (last (split-string linktext "/"))) "↗") ) ) ) (concat "<a href=\"" url "\">" linktext "</a>" ) ) )
Note: in the above, the wrap-url-string takes a optional argument “raw”. This is done with “&optional”. If the argument list contains “&optional”, then all parameters after it is optional. If not given, the default value is nil.
Reference: Elisp Manual: Argument-List.
Also, we needed to turn the percent encoded strings such as “%28” “%29” to “(” and “)”. Lucky for us, such function already exist, in the package “gnus-util” as “gnus-url-unhex-string”. (it is bundled with emacs) So, we load the package by “(require 'gnus-util)”.
Now, the following wrap-url function is the interactive wrapper that calls wrap-url-string.
(defun wrap-url (&optional raw) "Make thing at cursor point into a html link.\n Example: http://en.wikipedia.org/wiki/Emacs becomes <a href=\"http://en.wikipedia.org/wiki/Emacs\">Emacs↗</a> If the url is a wikipedia link, then the linktext is shortened to the article name. If the optional argument raw is non-nil, then no shortening is done." (interactive "P") (re-search-backward "[\n\t ()<>「」]" nil t) (looking-at "[\n\t ()<>「」]?\\([^\n\t ()<>「」]+\\)") (let ( (p1 (match-beginning 1)) (p2 (match-end 1)) (url (match-string 1)) ) (delete-region p1 p2) (goto-char p1) (insert (wrap-url-string url raw)) ) )
The code uses looking-at, instead of the numerous re-search-* functions to grab the url. This makes the code more clear. We used a set of characters for the url delimiter, so that for example, url inside parenthesis (http://xahlee.org/) will not include the parenthesis as part of the url string.
What described above is the general process of writing code in lisp. In fact, it is the general process in functional programing. Start with a spec sketch. Then break it down to components. Code the components, then finally string them together into a larger function. Or, collect functions together into a coherent package.
Related essays:
Page created: 2006-10. © 2006 by Xah Lee.