Emacs Lisp: How to Write a make-html-table Command

, , …,

This page shows a example of writing a emacs lisp command that turns the current block of text into a HTML table.

Problem Description

I want to write a command, such that, when called, the current block of text the cursor is on, becomes a HTML table. Suppose the block of text is this:

a  b  c
1  2  3
this  and  that

after pressing a button, it should become:

<table class="nrm">
<tr><td>a</td><td>b</td><td>c</td></tr>
<tr><td>1</td><td>2</td><td>3</td></tr>
<tr><td>this</td><td>and</td><td>that</td></tr>
</table>

and is rendered in browser like this:

abc
123
thisandthat

when you use CSS code similar to this:

table.nrm {border:solid 1px #000000;border-collapse:collapse;margin:.5ex}
table.nrm th, table.nrm td {border:solid thin #808080;padding:.5ex}
table.nrm th {background-color:#d7e4f2}

Solution

The problem has 3 major steps:

Grab Text Block

Grabbing a block of text is done by knowing the position of occurrences of a consecutive newline characters (represented by "\n" in emacs), before and after the current cursor position. Such a unit of text is usually defined as a “paragraph” in emacs's syntax table, and the function thing-at-point will do this for us.

; set mytext to be the current paragraph
(setq mytext (thing-at-point 'paragraph))

; gets the beginning and ending positions of the paragraph
; as a cons cell (cons p1 p2)
; set it to var bnds
(setq bnds (bounds-of-thing-at-point 'paragraph))

For detail, see: Emacs Lisp: Using thing-at-point.

String Transform

Now, we write a function that takes 2 argument: a text block as string, and a separator as string, and return htmlized version of the text block.

This may sound complicated, but it is easily done by replacing the separator in the text block by </td><td>, and wrap each line by <tr><td> and </td></tr>, then wrap a <table> and </table> around the whole block.

The primary function we'll use for this is replace-regexp-in-string. Here's the code:

(defun make-html-table-string (textblock delim)
  "Transform the string TEXTBLOCK into a HTML marked up table.
“\n” is used as delimiter of rows.
The argument DELIM is a char used as the delimiter for columns.

See the parent function `make-html-table'."
  (setq textblock (replace-regexp-in-string delim "</td><td>" textblock))
  (setq textblock (replace-regexp-in-string "\n" "</td></tr>\n<tr><td>" textblock))
  (setq textblock (substring textblock 0 -8)) ; delete the beginning “<tr><td>” in last line
  (concat "<table class=\"nrm\">\n<tr><td>" textblock "</table>")
  )

To test this code, we can call it like this:

(make-html-table-string "1 2 3\na b c\n" " ")

Wrapper

With the above functions, our job becomes very simple. We write a wrapper. Here's the code:

(defun make-html-table (sep)
  "Transform the current paragraph into a HTML table.

The “current paragraph” is defined as having empty lines before and
after the block of text the cursor is on.

For example:

a*b*c
1*2*3
this*and*that

with “*” as separator, becomes

<table class=\"nrm\">
<tr><td>a</td><td>b</td><td>c</td></tr>
<tr><td>1</td><td>2</td><td>3</td></tr>
<tr><td>this</td><td>and</td><td>that</td></tr>
</table>"
  (interactive "sEnter string pattern for column separation:")
  (let (bds p1 p2 myStr)
    (setq bds (bounds-of-thing-at-point 'paragraph))
    (setq p1 (+ (car bds) 1))
    (setq p2 (cdr bds))
    (setq myStr (buffer-substring-no-properties p1 p2))
    (delete-region p1 p2)
    (insert (make-html-table-string myStr sep) "\n")
  ))

One thing to note here is this line: (interactive "sEnter string pattern for column separation:"). The (interactive …) form is a way to get arguments when the function is called interactively. See: Emacs Lisp Idioms: Prompting for User Input.

Emacs rules!

The above is a simple method to turn a text block into a HTML table. Once you have the table as HTML code, you can not easily add or delete row or columns. Emacs 22 comes with a table.el package that makes table editing a little easier. See: Working with Tables In Emacs.

blog comments powered by Disqus