16.4 生成页面 (Generating Pages)

一本书可以有任意数量的大章,每个大章又有任意数量的小节,而每个小节又有任意数量的分节,整本书的结构呈现出一棵树的形状。

尽管网页使用的术语和书本不同,但多个网页同样可以被组织成树状。

本节要构建的是这样一个程序,它生成多个网页,这些网页带有以下结构: 第一页是一个目录,目录中的链接指向各个节点(section)页面。 每个节点包含一些指向(item)的链接。 而一个项就是一个包含纯文本的页面。

除了页面本身的链接以外,根据页面在树状结构中的位置,每个页面都会带有前进、后退和向上的链接。 其中,前进和后退链接用于在同级(sibling)页面中进行导航。 举个例子,点击一个项页面中的前进链接时,如果这个项的同一个节点下还有下一个项,那么就跳到这个新项的页面里。 另一方面,向上链接将页面跳转到树形结构的上一层 —— 如果当前页面是项页面,那么返回到节点页面;如果当前页面是节点页面,那么返回到目录页面。 最后,还会有索引页面:这个页面包含一系列链接,按字母顺序排列所有项。

../_images/Figure-16.7.png

图 16.7 网站的结构

图 16.7 展示了生成程序创建的页面所形成的链接结构。

  1. (defparameter *sections* nil)
  2. (defstruct item
  3. id title text)
  4. (defstruct section
  5. id title items)
  6. (defmacro defitem (id title text)
  7. `(setf ,id
  8. (make-item :id ',id
  9. :title ,title
  10. :text ,text)))
  11. (defmacro defsection (id title &rest items)
  12. `(setf ,id
  13. (make-section :id ',id
  14. :title ,title
  15. :items (list ,@items))))
  16. (defun defsite (&rest sections)
  17. (setf *sections* sections))

图 16.8 定义一个网站

图 16.8 包含定义页面所需的数据结构。程序需要处理两类对象:项和节点。这两类对象的结构很相似,不过节点包含的是项的列表,而项包含的是文本块。

节点和项两类对象都带有 id 域。 标识符(id)被用作符号(symbol),并达到以下两个目的:在 defitemdefsection 的定义中, 标识符会被设置到被创建的项或者节点当中,作为我们引用它们的一种手段;另一方面,标识符还会作为相应文件的前缀名(base name),比如说,如果项的标识符为 foo ,那么项就会被写到 foo.html 文件当中。

节点和项也同时带有 title 域。这个域的值应该为字符串,并且被用作相应页面的标题。

在节点里,项的排列顺序由传给 defsection 的参数决定。 与此类似,在目录里,节点的排列顺序由传给 defsite 的参数决定。

  1. (defconstant contents "contents")
  2. (defconstant index "index")
  3. (defun gen-contents (&optional (sections *sections*))
  4. (page contents contents
  5. (with ol
  6. (dolist (s sections)
  7. (link-item (section-id s) (section-title s))
  8. (brs 2))
  9. (link-item index (string-capitalize index)))))
  10. (defun gen-index (&optional (sections *sections*))
  11. (page index index
  12. (with ol
  13. (dolist (i (all-items sections))
  14. (link-item (item-id i) (item-title i))
  15. (brs 2)))))
  16. (defun all-items (sections)
  17. (let ((is nil))
  18. (dolist (s sections)
  19. (dolist (i (section-items s))
  20. (setf is (merge 'list (list i) is #'title<))))
  21. is))
  22. (defun title< (x y)
  23. (string-lessp (item-title x) (item-title y)))

图 16.9 生成索引和目录

图 16.9 包含的函数用于生成索引和目录。 常量 contentsindex 都是字符串,它们分别用作 contents 页面的标题和 index 页面的标题;另一方面,如果有其他页面包含了目录和索引这两个页面,那么这两个常量也会作为这些页面文件的前缀名。

函数 gen-contentsgen-index 非常相似。 它们都打开一个 HTML 文件,生成标题和链接列表。 不同的地方是,索引页面的项必须是有序的。 有序列表通过 all-items 函数生成,它遍历各个项并将它加入到保存已知项的列表当中,并使用 title< 函数作为排序函数。 注意,因为 title< 函数对大小写敏感,所以在对比标题前,输入必须先经过 string-lessp 处理,从而忽略大小写区别。

实际程序中的对比操作通常更复杂一些。举个例子,它们需要忽略无意义的句首词汇,比如 "a""the"

  1. (defun gen-site ()
  2. (map3 #'gen-section *sections*)
  3. (gen-contents)
  4. (gen-index))
  5. (defun gen-section (sect <sect sect>)
  6. (page (section-id sect) (section-title sect)
  7. (with ol
  8. (map3 #'(lambda (item <item item>)
  9. (link-item (item-id item)
  10. (item-title item))
  11. (brs 2)
  12. (gen-item sect item <item item>))
  13. (section-items sect)))
  14. (brs 3)
  15. (gen-move-buttons (if <sect (section-id <sect))
  16. contents
  17. (if sect> (section-id sect>)))))
  18. (defun gen-item (sect item <item item>)
  19. (page (item-id item) (item-title item)
  20. (princ (item-text item))
  21. (brs 3)
  22. (gen-move-buttons (if <item (item-id <item))
  23. (section-id sect)
  24. (if item> (item-id item>)))))
  25. (defun gen-move-buttons (back up forward)
  26. (if back (button back "Back"))
  27. (if up (button up "Up"))
  28. (if forward (button forward "Forward")))

图 16.10 生成网站、节点和项

图 16.10 包含其余的代码: gen-site 生成整个页面集合,并调用相应的函数,生成节点和项。

所有页面的集合包括目录、索引、各个节点以及各个项的页面。 目录和索引的生成由图 16.9 中的代码完成。 节点和项由分别由生成节点页面的 gen-section 和生成项页面的 gen-item 完成。

这两个函数的开头和结尾非常相似。 它们都接受一个对象、对象的左兄弟、对象的右兄弟作为参数;它们都从对象的 title 域中提取标题内容;它们都以调用 gen-move-buttons 作为结束,其中 gen-move-buttons 创建指向左兄弟的后退按钮、指向右兄弟的前进按钮和指向双亲(parent)对象的向上按钮。 它们的不同在于函数体的中间部分: gen-section 创建有序列表,列表中的链接指向节点包含的项,而 gen-item 创建的项则链接到相应的文本页面。

项所包含的内容完全由用户决定。 比如说,将 HTML 标签作为内容也是完全没问题的。 项的文本当然也可以由其他程序来生成。

图 16.11 演示了如何手工地定义一个微型网页。 在这个例子中,列出的项都是 Fortune 饼干公司新推出的产品。

  1. (defitem des "Fortune Cookies: Dessert or Fraud?" "...")
  2. (defitem case "The Case for Pessimism" "...")
  3. (defsection position "Position Papers" des case)
  4. (defitem luck "Distribution of Bad Luck" "...")
  5. (defitem haz "Health Hazards of Optimism" "...")
  6. (defsection abstract "Research Abstracts" luck haz)
  7. (defsite position abstract)

图 16.11 一个微型网站