<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                本章的目標是完成一個簡單的 HTML 生成器 —— 這個程序可以自動生成一系列包含超文本鏈接的網頁。除了介紹特定 Lisp 技術之外,本章還是一個典型的自底向上編程(bottom-up programming)的例子。 我們以一些通用 HTML 實用函數作為開始,繼而將這些例程看作是一門編程語言,從而更好地編寫這個生成器。 [TOC] ## 16.1 超文本標記語言 (HTML)[](http://acl.readthedocs.org/en/latest/zhCN/ch16-cn.html#id1 "Permalink to this headline") HTML (HyperText Markup Language,超文本標記語言)用于構建網頁,是一種簡單、易學的語言。本節就對這種語言作概括性介紹。 當你使用*網頁瀏覽器*閱覽網頁時,瀏覽器從遠程服務器獲取 HTML 文件,并將它們顯示在你的屏幕上。每個 HTML 文件都包含任意多個*標簽*(tag),這些標簽相當于發送給瀏覽器的指令。 ![../_images/Figure-16.1.png](http://box.kancloud.cn/2015-08-31_55e3cd0671c19.png) **圖 16.1 一個 HTML 文件** 圖 16.1 給出了一個簡單的 HTML 文件,圖 16.2 展示了這個 HTML 文件在瀏覽器里顯示時大概是什么樣子。 ![../_images/Figure-16.2.png](http://box.kancloud.cn/2015-08-31_55e3cd08124d3.png) **圖 16.2 一個網頁** 注意在尖角括號之間的文本并沒有被顯示出來,這些用尖角括號包圍的文本就是標簽。 HTML 的標簽分為兩種,一種是成雙成對地出現的: ~~~ <tag>...</tag> ~~~ 第一個標簽標志著某種情景(environment)的開始,而第二個標簽標志著這種情景的結束。 這種標簽的一個例子是?`<h2>`?:所有被`<h2>`?和?`</h2>`?包圍的文本,都會使用比平常字體尺寸稍大的字體來顯示。 另外一些成雙成對出現的標簽包括:創建帶編號列表的?`<ol>`?標簽(ol 代表 ordered list,有序表),令文本居中的?`<center>`?標簽,以及創建鏈接的?`<a>`?標簽(a 代表 anchor,錨點)。 被?`<a>`?和?`</a>`?包圍的文本就是超文本(hypertext)。 在大多數瀏覽器上,超文本都會以一種與眾不同的方式被凸顯出來 —— 它們通常會帶有下劃線 —— 并且點擊這些文本會讓瀏覽器跳轉到另一個頁面。 在標簽?`a`?之后的部分,指示了鏈接被點擊時,瀏覽器應該跳轉到的位置。 一個像 ~~~ <a href="foo.html"> ~~~ 這樣的標簽,就標識了一個指向另一個 HTML 文件的鏈接,其中這個 HTML 文件和當前網頁的文件夾相同。 當點擊這個鏈接時,瀏覽器就會獲取并顯示?`foo.html`?這個文件。 當然,鏈接并不一定都要指向相同文件夾下的 HTML 文件,實際上,一個鏈接可以指向互聯網的任何一個文件。 和成雙成對出現的標簽相反,另一種標簽沒有結束標記。 在圖 16.1 里有一些這樣的標簽,包括:創建一個新文本行的?`<br>`?標簽(br 代表 break ,斷行),以及在列表情景中,創建一個新列表項的?`<li>`?標簽(li 代表 list item ,列表項)。 HTML 還有不少其他的標簽,但是本章要用到的標簽,基本都包含在圖 16.1 里了。 ## 16.2 HTML 實用函數 (HTML Utilities)[](http://acl.readthedocs.org/en/latest/zhCN/ch16-cn.html#html-html-utilities "Permalink to this headline") ~~~ (defmacro as (tag content) `(format t "<~(~A~)>~A</~(~A~)>" ',tag ,content ',tag)) (defmacro with (tag &rest body) `(progn (format t "~&<~(~A~)>~%" ',tag) ,@body (format t "~&</~(~A~)>~%" ',tag))) (defmacro brs (&optional (n 1)) (fresh-line) (dotimes (i n) (princ "<br>")) (terpri)) ~~~ **圖 16.3 標簽生成例程** 本節會定義一些生成 HTML 的例程。 圖 16.3 包含了三個基本的、生成標簽的例程。 所有例程都將它們的輸出發送到?`*standard-output*`?;可以通過重新綁定這個變量,將輸出重定向到一個文件。 宏?`as`?和?`with`?都用于在標簽之間生成表達式。其中?`as`?接受一個字符串,并將它打印在兩個標簽之間: ~~~ > (as center "The Missing Lambda") <center>The Missing Lambda</center> NIL ~~~ `with`?則接受一個代碼體(body of code),并將它放置在兩個標簽之間: ~~~ > (with center (princ "The Unbalanced Parenthesis")) <center> The Unbalanced Parenthesis </center> NIL ~~~ 兩個宏都使用了?`~(...~)`?來進行格式化,從而將標簽轉化為小寫字母的標簽。 HTML 并不介意標簽是大寫還是小寫,但是在包含許許多多標簽的 HTML 文件中,小寫字母的標簽可讀性更好一些。 除此之外,?`as`?傾向于將所有輸出都放在同一行,而?`with`?則將標簽和內容都放在不同的行里。 (使用?`~&`?來進行格式化,以確保輸出從一個新行中開始。) 以上這些工作都只是為了讓 HTML 更具可讀性,實際上,標簽之外的空白并不影響頁面的顯示方式。 圖 16.3 中的最后一個例程?`brs`?用于創建多個文本行。 在很多瀏覽器中,這個例程都可以用于控制垂直間距。 ~~~ (defun html-file (base) (format nil "~(~A~).html" base)) (defmacro page (name title &rest body) (let ((ti (gensym))) `(with-open-file (*standard-output* (html-file ,name) :direction :output :if-exists :supersede) (let ((,ti ,title)) (as title ,ti) (with center (as h2 (string-upcase ,ti))) (brs 3) ,@body)))) ~~~ **圖 16.4 HTML 文件生成例程** 圖 16.4 包含用于生成 HTML 文件的例程。 第一個函數根據給定的符號(symbol)返回一個文件名。 在一個實際應用中,這個函數可能會返回指向某個特定文件夾的路徑(path)。 目前來說,這個函數只是簡單地將?`.html`?后綴追加到給定符號名的后邊。 宏?`page`?負責生成整個頁面,它的實現和?`with-open-file`?很相似:?`body`?中的表達式會被求值,求值的結果通過?`*standard-output*`?所綁定的流,最終被寫入到相應的 HTML 文件中。 6.7 小節展示了如何臨時性地綁定一個特殊變量。 在 113 頁的例子中,我們在?`let`?的體內將?`*print-base*`?綁定為?`16`?。 這一次,通過將?`*standard-output*`?和一個指向 HTML 文件的流綁定,只要我們在?`page`?的函數體內調用?`as`?或者?`princ`?,輸出就會被傳送到 HTML 文件里。 `page`?宏的輸出先在頂部打印?`title`?,接著求值?`body`?中的表達式,打印?`body`?部分的輸出。 如果我們調用 ~~~ (page 'paren "The Unbalanced Parenthesis" (princ "Something in his expression told her...")) ~~~ 這會產生一個名為?`paren.html`?的文件(文件名由?`html-file`?函數生成),文件中的內容為: ~~~ <title>The Unbalanced Parenthesis</title> <center> <h2>THE UNBALANCED PARENTHESIS</h2> </center> <br><br><br> Something in his expression told her... ~~~ 除了?`title`?標簽以外,以上輸出的所有 HTML 標簽在前面已經見到過了。 被?`<title>`?標簽包圍的文本并不顯示在網頁之內,它們會顯示在瀏覽器窗口,用作頁面的標題。 ~~~ (defmacro with-link (dest &rest body) `(progn (format t "<a href=\"~A\">" (html-file ,dest)) ,@body (princ "</a>"))) (defun link-item (dest text) (princ "<li>") (with-link dest (princ text))) (defun button (dest text) (princ "[ ") (with-link dest (princ text)) (format t " ]~%")) ~~~ **圖 16.5 生成鏈接的例程** 圖片 16.5 給出了用于生成鏈接的例程。?`with-link`?和?`with`?很相似:它根據給定的地址?`dest`?,創建一個指向 HTML 文件的鏈接。 而鏈接內部的文本,則通過求值?`body`?參數中的代碼段得出: ~~~ > (with-link 'capture (princ "The Captured Variable")) <a href="capture.html">The Captured Variable</a> "</a>" ~~~ `with-link`?也被用在?`link-item`?當中,這個函數接受一個字符串,并創建一個帶鏈接的列表項: ~~~ > (link-item 'bq "Backquote!") <li><a href="bq.html">Backquote!</a> "</a>" ~~~ 最后,?`button`?也使用了?`with-link`?,從而創建一個被方括號包圍的鏈接: ~~~ > (button 'help "Help") [ <a href="help.html">Help</a> ] NIL ~~~ ## 16.3 迭代式實用函數 (An Iteration Utility)[](http://acl.readthedocs.org/en/latest/zhCN/ch16-cn.html#an-iteration-utility "Permalink to this headline") 在這一節,我們先暫停一下編寫 HTML 生成器的工作,轉到編寫迭代式例程的工作上來。 你可能會問,怎樣才能知道,什么時候應該編寫主程序,什么時候又應該編寫子例程? 實際上,這個問題,沒有答案。 通常情況下,你總是先開始寫一個程序,然后發現需要寫一個新的例程,于是你轉而去編寫新例程,完成它,接著再回過頭去編寫原來的程序。 時間關系,要在這里演示這個開始-完成-又再開始的過程是不太可能的,這里只展示這個迭代式例程的最終形態,需要注意的是,這個程序的編寫并不如想象中的那么簡單。 程序通常需要經歷多次重寫,才會變得簡單。 ~~~ (defun map3 (fn lst) (labels ((rec (curr prev next left) (funcall fn curr prev next) (when left (rec (car left) curr (cadr left) (cdr left))))) (when lst (rec (car lst) nil (cadr lst) (cdr lst))))) ~~~ **圖 16.6 對樹進行迭代** 圖 16.6 里定義的新例程是?`mapc`?的一個變種。它接受一個函數和一個列表作為參數,對于傳入列表中的每個元素,它都會用三個參數來調用傳入函數,分別是元素本身,前一個元素,以及后一個元素。(當沒有前一個元素或者后一個元素時,使用?`nil`?代替。) ~~~ > (map3 #'(lambda (&rest args) (princ args)) '(a b c d)) (A NIL B) (B A C) (C B D) (D C NIL) NIL ~~~ 和?`mapc`?一樣,?`map3`?總是返回?`nil`?作為函數的返回值。需要這類例程的情況非常多。在下一個小節就會看到,這個例程是如何讓每個頁面都實現“前進一頁”和“后退一頁”功能的。 `map3`?的一個常見功能是,在列表的兩個相鄰元素之間進行某些處理: ~~~ > (map3 #'(lambda (c p n) (princ c) (if n (princ " | "))) '(a b c d)) A | B | C | D NIL ~~~ 程序員經常會遇到上面的這類問題,但只要花些功夫,定義一些例程來處理它們,就能為后續工作節省不少時間。 ## 16.4 生成頁面 (Generating Pages)[](http://acl.readthedocs.org/en/latest/zhCN/ch16-cn.html#generating-pages "Permalink to this headline") 一本書可以有任意數量的大章,每個大章又有任意數量的小節,而每個小節又有任意數量的分節,整本書的結構呈現出一棵樹的形狀。 盡管網頁使用的術語和書本不同,但多個網頁同樣可以被組織成樹狀。 本節要構建的是這樣一個程序,它生成多個網頁,這些網頁帶有以下結構: 第一頁是一個目錄,目錄中的鏈接指向各個*節點*(section)頁面。 每個節點包含一些指向*項*(item)的鏈接。 而一個項就是一個包含純文本的頁面。 除了頁面本身的鏈接以外,根據頁面在樹狀結構中的位置,每個頁面都會帶有前進、后退和向上的鏈接。 其中,前進和后退鏈接用于在同級(sibling)頁面中進行導航。 舉個例子,點擊一個項頁面中的前進鏈接時,如果這個項的同一個節點下還有下一個項,那么就跳到這個新項的頁面里。 另一方面,向上鏈接將頁面跳轉到樹形結構的上一層 —— 如果當前頁面是項頁面,那么返回到節點頁面;如果當前頁面是節點頁面,那么返回到目錄頁面。 最后,還會有索引頁面:這個頁面包含一系列鏈接,按字母順序排列所有項。 ![../_images/Figure-16.7.png](http://box.kancloud.cn/2015-08-31_55e3cd09c9c68.png) **圖 16.7 網站的結構** 圖 16.7 展示了生成程序創建的頁面所形成的鏈接結構。 ~~~ (defparameter *sections* nil) (defstruct item id title text) (defstruct section id title items) (defmacro defitem (id title text) `(setf ,id (make-item :id ',id :title ,title :text ,text))) (defmacro defsection (id title &rest items) `(setf ,id (make-section :id ',id :title ,title :items (list ,@items)))) (defun defsite (&rest sections) (setf *sections* sections)) ~~~ **圖 16.8 定義一個網站** 圖 16.8 包含定義頁面所需的數據結構。程序需要處理兩類對象:項和節點。這兩類對象的結構很相似,不過節點包含的是項的列表,而項包含的是文本塊。 節點和項兩類對象都帶有?`id`?域。 標識符(id)被用作符號(symbol),并達到以下兩個目的:在?`defitem`?和?`defsection`?的定義中, 標識符會被設置到被創建的項或者節點當中,作為我們引用它們的一種手段;另一方面,標識符還會作為相應文件的前綴名(base name),比如說,如果項的標識符為?`foo`?,那么項就會被寫到?`foo.html`?文件當中。 節點和項也同時帶有?`title`?域。這個域的值應該為字符串,并且被用作相應頁面的標題。 在節點里,項的排列順序由傳給?`defsection`?的參數決定。 與此類似,在目錄里,節點的排列順序由傳給?`defsite`?的參數決定。 ~~~ (defconstant contents "contents") (defconstant index "index") (defun gen-contents (&optional (sections *sections*)) (page contents contents (with ol (dolist (s sections) (link-item (section-id s) (section-title s)) (brs 2)) (link-item index (string-capitalize index))))) (defun gen-index (&optional (sections *sections*)) (page index index (with ol (dolist (i (all-items sections)) (link-item (item-id i) (item-title i)) (brs 2))))) (defun all-items (sections) (let ((is nil)) (dolist (s sections) (dolist (i (section-items s)) (setf is (merge 'list (list i) is #'title<)))) is)) (defun title< (x y) (string-lessp (item-title x) (item-title y))) ~~~ **圖 16.9 生成索引和目錄** 圖 16.9 包含的函數用于生成索引和目錄。 常量?`contents`?和?`index`?都是字符串,它們分別用作?`contents`?頁面的標題和?`index`?頁面的標題;另一方面,如果有其他頁面包含了目錄和索引這兩個頁面,那么這兩個常量也會作為這些頁面文件的前綴名。 函數?`gen-contents`?和?`gen-index`?非常相似。 它們都打開一個 HTML 文件,生成標題和鏈接列表。 不同的地方是,索引頁面的項必須是有序的。 有序列表通過?`all-items`?函數生成,它遍歷各個項并將它加入到保存已知項的列表當中,并使用?`title<`?函數作為排序函數。 注意,因為?`title<`?函數對大小寫敏感,所以在對比標題前,輸入必須先經過?`string-lessp`?處理,從而忽略大小寫區別。 實際程序中的對比操作通常更復雜一些。舉個例子,它們需要忽略無意義的句首詞匯,比如?`"a"`?和?`"the"`?。 ~~~ (defun gen-site () (map3 #'gen-section *sections*) (gen-contents) (gen-index)) (defun gen-section (sect <sect sect>) (page (section-id sect) (section-title sect) (with ol (map3 #'(lambda (item <item item>) (link-item (item-id item) (item-title item)) (brs 2) (gen-item sect item <item item>)) (section-items sect))) (brs 3) (gen-move-buttons (if <sect (section-id <sect)) contents (if sect> (section-id sect>))))) (defun gen-item (sect item <item item>) (page (item-id item) (item-title item) (princ (item-text item)) (brs 3) (gen-move-buttons (if <item (item-id <item)) (section-id sect) (if item> (item-id item>))))) (defun gen-move-buttons (back up forward) (if back (button back "Back")) (if up (button up "Up")) (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 餅干公司新推出的產品。 ~~~ (defitem des "Fortune Cookies: Dessert or Fraud?" "...") (defitem case "The Case for Pessimism" "...") (defsection position "Position Papers" des case) (defitem luck "Distribution of Bad Luck" "...") (defitem haz "Health Hazards of Optimism" "...") (defsection abstract "Research Abstracts" luck haz) (defsite position abstract) ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看