Xah Lee, 2006-11-29
This page is emacs lisp lesson on a real-world task. It show how emacs lisp is used in creating HTML navigational bar for a online book. Specifically, we need to insert proper text to each file for a set of files. You should be familiar with Elisp Language Basics.
I want to insert the “Next Chapter”, “Previous Chapter” links to a series of HTML files that are chapters of a book.
This lesson essentially teaches you how to do a regex pattern replace by a function that return text based on the current file's name.
I have a lot of books in HTML form. Usually, the file names have this pattern: chap1.html, chap2.html, chap3.html ... etc.
Each file is a chapter of a book. And in each file, I need to place a navigation bar, so that there's a Next Chapter and Previous Chapter links at the bottom of each page.
Normally, this can be done by writing a short Perl or Python script. The script will open the file, parse the file name so that it knows which chapter this file is. If the current file is “chapter3.html”, then the script will generate a string like this:
<div class="navbar"> <a href="chapter2.html">PREVIOUS</a>| <a href="index.html">TOP</a>| <a href="chapter4.html">NEXT</a> </div>
For a person familiar with scripting languages, the job can be done in about 20 minutes. Basically, your script will traverse a directory and determine which files to process. For each file, your script will parse the file name and generate the navigation bar string appropriate for the file. Then, your script will open the file, insert the nav bar at the appropriate place, then close the file. Your script will need do a backup if you want it to be robust. With that, you'll also have to make sure that the file's owner, group, permissions etc meta data are kept intact.
In the end, some simple script can end up taking twice or trice the time you expected.
However, if you know elisp, you only need to write half of the code, since the file selection, file opening and reading, backing up, saving, etc are all part of the emacs environment. All you really need to write is a elisp function that takes in a file name and returns the navigation bar string. This significantly saves your time. As a added benefit, you get to do this in a interactive, visual process. So, errors are much reduced, and you don't have to worry about your code making a mistake erasing parts of the file or missing some files.
Here's how we do it with emacs.
First, mark the files you want to process in dired. Then, use dired-do-query-replace-regexp to do a find and replace operation on a particular string. For example, replace “<body>” with “<body> navbar-string”. (For a tutorial on using dired-do-query-replace-regexp, see: Interactively Find and Replace String Patterns on Multiple Files. )
The trick lies in your replacement string. You want to use a elisp function that returns the appropriate nav bar for the chapter. (so that the Next and Previous links are correct, according to what chapter the current file is)
In emacs 22, there's a new feature that allows you to put a elisp function as your replacement string. This is done by giving the replacement string this form “ \,(fun-name)”, where fun-name is your elisp function. So, if the function that returns the nav bar string is called “navbar-str”, then in your replacement string you can say “\,(navbar-str)”, and you are all set.
Here is the navbar-str function:
(defun navbar-str () "Returns a navigation bar string with Prev and Next links based on the current file name." (interactive) (let (fname navbar-str chapter-num ) (setq fname (file-name-nondirectory (buffer-file-name)) ) (setq chapter-num (string-to-number (substring (file-name-sans-extension fname) 4))) (setq navbar-str (concat "<div class=\"nav\">★ <a href=\"" "chap" (number-to-string (- chapter-num 1)) ".html" "\">◀</a> <a href=\"index.html\">▲</a> <a href=\"" "chap" (number-to-string (+ chapter-num 1)) ".html" "\">▶</a> Flatland</div>")) navbar-str ) )
In the above code, the buffer-file-name returns the full path of the file of the current buffer. The file-name-nondirectory truncates the path to just the file name. The line “(setq chapter-num (string-to-number (substring (file-name-sans-extension fname) 4)))” extract the chapter number from the file name.
For a example of a online book with Next/Previous navigation bar, see: Flatland.
Emacs is beautiful!
Related essays:
Page created: 2006-11. © 2006 by Xah Lee.