问题描述

OrgMode内置的创建表格的函数是 org-table-create , 传入"列数x行数"参数即可生成特定行数、列数的表格。这种交互函数在编写org文档时很实用,但在代码中却显得鸡肋。因为在代码中,我们通常希望表格和数据可以一起生成,而不是手动添加数据。

我在折腾 gk-habit.el 时,就产生了这样的需求:生成习惯的月度打卡视图。就像下面这个样子:

Sun Mon Tue Wed Thu Fri Sat
            1
            --
2 3 4 5 6 7 8
-- -- -- -- -- --
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
         

思路分析

解决这个问题的关键是把握几个操作org表格的函数 org-create-table, org-table-next-field, org-table-insert-hline, org-table-kill-row… 然后就是在表格创建的过程中依次插入数据。用于创建表格的每一行的数据用列表表示,分隔线用 hl 对象表示。

  • 首先创建一个一行n列的表格,因为 org table 的函数只有在表格内才能使用。其中n为每行元素的个数。
  • 在数据列表中循环,如果元素是一个list,表示是数据。继续在该list中循环,插入数据后跳到下一个单元格(注意数字要转为字符串)。
  • 如果元素是 hl 对象,表示是分隔线,直接插入一行分割线 (org-table-insert-hline 1)
  • 每一行插入最后一个数据后会执行“跳到下一个单元格”的操作,当右边没有单元格时会自动插入新的一行。
  • 因此,最后会多出一行,用 org-table-kill-row 函数删掉。

代码实现

(defun gk-org-table-create (LIST)
  "Create org table from a LIST form at point."
  (let ((column (catch 'break
                  (dolist (row-data LIST)
                    (when (listp row-data)
                      (throw 'break (length row-data))))))
        (beg (point)))
    (org-table-create (concat (number-to-string column) "x1"))
    (goto-char beg)
    (when (org-at-table-p)
      (org-table-next-field)
      (dotimes (i (length LIST))
        (let ((row-data (nth i LIST)))
          (if (listp row-data)
              (dolist (data row-data)
                (cond
                 ((numberp data)
                  (insert (number-to-string data)))
                 ((null data)
                  (insert ""))
                 (t (insert data)))
                (org-table-next-field))
            (when (equal 'hl row-data)
              (org-table-insert-hline 1)))
          (when (= i (1- (length LIST)))
            (org-table-kill-row))))))
  (forward-line))

使用案例

(gk-org-table-create
 '(("n1" "n2" "n3" "n4" "n5")
   hl
   (1 2 3 4 5)
   (6 7 8 9 10)
   hl
   ("c1" "c2" "c3" "c4" "c5")
   hl
   ("a" "b" "c" "d" "e")
   ("f" "g" "h" "i" "j")))

| n1 | n2 | n3 | n4 | n5 |
|----+----+----+----+----|
| 1  | 2  | 3  | 4  | 5  |
| 6  | 7  | 8  | 9  | 10 |
|----+----+----+----+----|
| c1 | c2 | c3 | c4 | c5 |
|----+----+----+----+----|
| a  | b  | c  | d  | e  |
| f  | g  | h  | i  | j  |

实现开篇提出的习惯打卡的视图是个更复杂的问题,这里涉及到了不同月份的天数不同,起始星期不同,以及每天对应的打卡状态不同等问题。解决了这些问题后,将得到的数据整合成 gk-org-table-create 合法的数据列表形式即可生成相应的表格。相关代码在这里

如果你有更简单、漂亮的实现,欢迎留言探讨~