newLISP 簡介/使用 XML
如今 XML 檔案被廣泛使用,你可能已經注意到 XML 檔案高度組織的樹狀結構與我們在 newLISP 中遇到的巢狀列表結構類似。那麼,如果你能像處理列表一樣輕鬆地處理 XML 檔案,豈不是很好?
你已經遇到了兩個主要的 XML 處理函式。(見 ref 和 ref-all。)xml-parse 和 xml-type-tags 函式是將 XML 轉換為 newLISP 列表所需的一切。(xml-error 用於診斷錯誤。)xml-type-tags 決定了 XML 標籤如何被 xml-parse 處理,xml-parse 執行 XML 檔案的實際處理,將其轉換為列表。
為了說明這些函式的使用,我們將使用 newLISP 論壇的 RSS 新聞提要
(set 'xml (get-url "http://newlispfanclub.alh.net/forum/feed.php"))
並將檢索到的 XML 儲存在一個檔案中,以避免反覆訪問伺服器
(save {/Users/me/Desktop/newlisp.xml} 'xml) ; save symbol in file
(load {/Users/me/Desktop/newlisp.xml}) ; load symbol from file
XML 以此開頭
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-gb">
<link rel="self" type="application/atom+xml" href="http://newlispfanclub.alh.net/forum/feed.php" />
<title>newlispfanclub.alh.net</title>
<subtitle>Friends and Fans of NewLISP</subtitle>
<link href="http://newlispfanclub.alh.net/forum/index.php" />
<updated>2010-01-11T09:51:39+00:00</updated>
<author><name><![CDATA[newlispfanclub.alh.net]]></name></author>
<id>http://newlispfanclub.alh.net/forum/feed.php</id>
<entry>
<author><name><![CDATA[kosh]]></name></author>
<updated>2010-01-10T12:17:53+00:00</updated>
...
如果你使用 xml-parse 解析 XML,但沒有先使用 xml-type-tags,輸出將如下所示
(xml-parse xml)
(("ELEMENT" "feed" (("xmlns" "http://www.w3.org/2005/Atom")
("xml:lang" "en-gb"))
(("TEXT" "\n") ("ELEMENT" "link" (("rel" "self")
("type" "application/atom+xml")
("href" "http://newlispfanclub.alh.net/forum/feed.php"))
())
("TEXT" "\n\n")
("ELEMENT" "title" () (("TEXT" "newlispfanclub.alh.net")))
("TEXT" "\n")
("ELEMENT" "subtitle" () (("TEXT" "Friends and Fans of NewLISP")))
("TEXT" "\n")
("ELEMENT" "link" (("href" "http://newlispfanclub.alh.net/forum/index.php")) ())
; ...
雖然它看起來已經有點像 LISP,但你可以看到元素已經被標記為 "ELEMENT"、"TEXT"。現在,我們可以不用這些標籤,而這基本上就是 xml-type-tags 的作用。它允許你為四種類型的 XML 標籤確定標籤:TEXT、CDATA、COMMENTS 和 ELEMENTS。我們將使用四個 nil 將它們全部隱藏起來。我們還將使用一些 xml-parse 的選項來進一步整理輸出。
(xml-type-tags nil nil nil nil)
(set 'sxml (xml-parse xml 15)) ; options: 15 (see below)
;->
((feed ((xmlns "http://www.w3.org/2005/Atom") (xml:lang "en-gb")) (link ((rel "self")
(type "application/atom+xml")
(href "http://newlispfanclub.alh.net/forum/feed.php")))
(title "newlispfanclub.alh.net")
(subtitle "Friends and Fans of NewLISP")
(link ((href "http://newlispfanclub.alh.net/forum/index.php")))
(updated "2010-01-11T09:51:39+00:00")
(author (name "newlispfanclub.alh.net"))
(id "http://newlispfanclub.alh.net/forum/feed.php")
(entry (author (name "kosh"))
(updated "2010-01-10T12:17:53+00:00")
(id "http://newlispfanclub.alh.net/forum/viewtopic.php?t=3447&p=17471#p17471")
(link ((href "http://newlispfanclub.alh.net/forum/viewtopic.php?t=3447&p=17471#p17471")))
(title ((type "html")) "newLISP in the real world \226\128\162 Re: suggesting the newLISP manual as CHM")
(category ((term "newLISP in the real world")
(scheme "http://newlispfanclub.alh.net/forum/viewforum.php?f=16")
(label "newLISP in the real world")))
(content ((type "html")
(xml:base "http://newlispfanclub.alh.net/forum/viewtopic.php?t=3447&p=17471#p17471"))
"\nhello kukma.<br />\nI tried to make the newLISP.chm, and this is it.<br />\n
<br />\n<!-- m --><a class=\"postlink\"
; ...
這現在是一個有用的 newLISP 列表,雖然它相當複雜,但儲存在一個名為 sxml 的符號中。(這種表示 XML 的方式被稱為 S-XML。)
如果你想知道 xml-parse 表示式中的 15 在做什麼,它只是控制輔助 XML 資訊翻譯程度的一種方式:選項如下
- 1 - 抑制空白文字標籤
- 2 - 抑制空屬性列表
- 4 - 抑制註釋標籤
- 8 - 將字串標籤轉換為符號
- 16 - 新增 SXML(S 表示式 XML)屬性標籤
你可以將它們加起來得到選項程式碼編號 - 因此 15 (+ 1 2 4 8) 使用了前四個選項:抑制不需要的東西,並將字串標籤轉換為符號。因此,newLISP 的符號表中添加了新的符號
(author category content entry feed href id label link rel scheme subtitle term
title type updated xml:base xml:lang xmlns)
這些對應於 XML 檔案中的字串標籤,它們將幾乎立即變得有用。
目前為止的故事基本上是這樣的
(set 'xml (get-url "http://newlispfanclub.alh.net/forum/feed.php"))
; we stored this in a temporary file while exploring
(xml-type-tags nil nil nil nil)
(set 'sxml (xml-parse xml 15))
它為我們提供了一個儲存在 sxml 符號中的新聞提要的列表版本。
由於此列表具有複雜的巢狀結構,最好使用 ref 和 ref-all 而不是 find 來查詢內容。ref 在列表中查詢表示式的第一個出現位置並返回地址
(ref 'entry sxml)
;-> (0 9 0)
這些數字是符號 item 在列表中第一個出現位置的地址:(0 9 0) 表示 從整個列表的第 0 項開始,然後轉到該項的第 9 項,然後轉到該項的第 0 項。(當然,它是從 0 開始的索引!)
要查詢更高層或包含項,請使用 chop 刪除地址的最後一級
(chop (ref 'entry sxml))
;-> (0 9)
這現在指向包含第一個專案的級別。就像從地址中刪除門牌號,只留下街道名稱一樣。
現在你可以將此地址與其他接受索引列表的表示式一起使用。最方便和簡潔的形式可能是隱式地址,它只是源列表的名稱後跟列表中的索引集
(sxml (chop (ref 'entry sxml))) ; a (0 9) slice of sxml
(entry (author (name "kosh")) (updated "2010-01-10T12:17:53+00:00") (id "http://newlispfanclub.alh.net/forum/viewtopic.php?t=3447&p=17471#p17471")
(link ((href "http://newlispfanclub.alh.net/forum/viewtopic.php?t=3447&p=17471#p17471")))
(title ((type "html")) "newLISP in the real world \226\128\162 Re: suggesting the newLISP manual as CHM")
(category ((term "newLISP in the real world") (scheme "http://newlispfanclub.alh.net/forum/viewforum.php?f=16")
(label "newLISP in the real world")))
(content ((type "html") (xml:base "http://newlispfanclub.alh.net/forum/viewtopic.php?t=3447&p=17471#p17471"))
...
這找到了 entry 的第一個出現位置,並返回了 SXML 的包含部分。
另一種可用的技術是將列表的部分視為關聯列表
(lookup 'title (sxml (chop (ref 'entry sxml))))
;->
newLISP in the real world • Re: suggesting the newLISP manual as CHM
在這裡,我們像之前一樣找到了第一個專案,然後使用 lookup 查找了 title 的第一個出現位置。
使用 ref-all 在列表中查詢符號的所有出現位置。它返回一個地址列表
(ref-all 'title sxml)
;->
((0 3 0)
(0 9 5 0)
(0 10 5 0)
(0 11 5 0)
(0 12 5 0)
(0 13 5 0)
(0 14 5 0)
(0 15 5 0)
(0 16 5 0)
(0 17 5 0)
(0 18 5 0))
透過一個簡單的列表遍歷,你可以快速顯示檔案中所有標題,無論它們在哪個級別
(dolist (el (ref-all 'title sxml))
(println (rest (rest (sxml (chop el))))))
;->
()
("newLISP in the real world \226\128\162 Re: suggesting the newLISP manual as CHM")
("newLISP newS \226\128\162 Re: newLISP Advocacy")
("newLISP in the real world \226\128\162 Re: newLISP-gs opens only splash picture")
("So, what can you actually DO with newLISP? \226\128\162 Re: Conception of Adaptive Programming Languages")
("Dragonfly \226\128\162 Re: Dragonfly 0.60 Released!")
("So, what can you actually DO with newLISP? \226\128\162 Takuya Mannami, Gauche Newlisp Library")
; ...
如果沒有那兩個 rest,你會看到
(title "newlispfanclub.alh.net")
(title ((type "html")) "newLISP in the real world \226\128\162 Re: suggesting the newLISP manual as CHM")
(title ((type "html")) "newLISP newS \226\128\162 Re: newLISP Advocacy")
; ...
如你所見,有許多不同的方法可以訪問 SXML 資料中的資訊。為了簡潔地概括 XML 檔案中的新聞,一種方法是遍歷所有專案,並提取 title 和 description 條目。由於 description 元素是一堆轉義實體,我們也將編寫一個快速而髒的整理例程
(define (cleanup str)
(let (replacements
'(({&amp;} {&})
({&gt;} {>})
({&lt;} {<})
({&nbsp;} { })
({&apos;} {'})
({&quot;} {"})
({&#40;} {(})
({&#41;} {)})
({&#58;} {:})
("\n" "")))
(and
(!= str "")
(map
(fn (f) (replace (first f) str (last f)))
replacements)
(join (parse str {<.*?>} 4) " "))))
(set 'entries (sxml (chop (chop (ref 'title sxml)))))
(dolist (e (ref-all 'entry entries))
(set 'entry (entries (chop e)))
(set 'author (lookup 'author entry))
(println "Author: " (last author))
(set 'content (lookup 'content entry))
(println "Post: " (0 60 (cleanup content)) {...}))
Author: kosh Post: hello kukma. I tried to make the newLISP.chm, and this is it... Author: Lutz Post: ... also, there was a sign-extension error in the newLISP co... Author: kukma Post: Thank you Lutz and welcome home again, the principle has bec... Author: Kazimir Majorinc Post: Apparently, Aparecido Valdemir de Freitas completed his Dr... Author: cormullion Post: Upgrade seemed to go well - I think I found most of the file... Author: Kazimir Majorinc Post: http://github.com/mtakuya/gauche-nl-lib Statistics: Post... Author: itistoday Post: As part of my work on Dragonfly, I've updated newLISP's SMTP... Author: Tim Johnson Post: itistoday wrote: Tim Johnson wrote: Have you done any... ; ...
你可以使用類似的技術來修改 XML 格式的資料。例如,假設你在一個 XML 檔案中儲存元素週期表,並且你想更改元素熔點的相關資料,這些資料目前以開爾文度表示,你想要將其更改為攝氏度。XML 資料看起來像這樣
<?xml version="1.0"?>
<PERIODIC_TABLE>
<ATOM>
...
</ATOM>
<ATOM>
<NAME>Mercury</NAME>
<ATOMIC_WEIGHT>200.59</ATOMIC_WEIGHT>
<ATOMIC_NUMBER>80</ATOMIC_NUMBER>
<OXIDATION_STATES>2, 1</OXIDATION_STATES>
<BOILING_POINT UNITS="Kelvin">629.88</BOILING_POINT>
<MELTING_POINT UNITS="Kelvin">234.31</MELTING_POINT>
<SYMBOL>Hg</SYMBOL>
...
當表格被載入到符號 sxml 中後,使用(set 'sxml (xml-parse xml 15))(其中 xml 包含 XML 原始碼),我們想要更改每個具有以下形式的子列表
(MELTING_POINT ((UNITS "Kelvin")) "629.88")
你可以使用 set-ref-all 函式在一個表示式中查詢和替換元素。首先,以下是一個將溫度從開爾文轉換為攝氏度的函式
(define (convert-K-to-C n)
(sub n 273.15))
現在,set-ref-all 函式可以只調用一次來查詢所有引用並就地修改它們,以便所有熔點都被轉換為攝氏度。形式是
(set-ref-all key list replacement function)
其中函式是使用給定鍵查詢列表元素的方法。
(set-ref-all
'(MELTING_POINT ((UNITS "Kelvin")) *)
sxml
(list
(first $0)
'((UNITS "Celsius"))
(string (convert-K-to-C (float (last $0)))))
match)
在這裡,match 函式使用萬用字元結構搜尋 SXML 列表(MELTING_POINT ( (UNITS "Kelvin") ) *)以查詢所有出現位置。替換表示式從儲存在 $0 中的匹配表示式構建替換子列表。在該表示式被求值後,SXML 從以下內容
; ...
(ATOM
(NAME "Mercury")
(ATOMIC_WEIGHT "200.59")
(ATOMIC_NUMBER "80")
(OXIDATION_STATES "2, 1")
(BOILING_POINT
((UNITS "Kelvin")) "629.88")
(MELTING_POINT
((UNITS "Kelvin")) "234.31")
; ...
更改為以下內容
; ...
(ATOM
(NAME "Mercury")
(ATOMIC_WEIGHT "200.59")
(ATOMIC_NUMBER "80")
(OXIDATION_STATES "2, 1")
(BOILING_POINT
((UNITS "Kelvin")) "629.88")
(MELTING_POINT
((UNITS "Celsius")) "-38.84")
; ...
XML 並不總是像這樣容易操作 - 存在屬性、CDATA 部分等等。
如果你想反過來將 newLISP 列表轉換為 XML,以下函式建議了一種可能的方法。它遞迴地遍歷列表
(define (expr2xml expr (level 0))
(cond
((or (atom? expr) (quote? expr))
(print (dup " " level))
(println expr))
((list? (first expr))
(expr2xml (first expr) (+ level 1))
(dolist (s (rest expr)) (expr2xml s (+ level 1))))
((symbol? (first expr))
(print (dup " " level))
(println "<" (first expr) ">")
(dolist (s (rest expr)) (expr2xml s (+ level 1)))
(print (dup " " level))
(println "</" (first expr) ">"))
(true
(print (dup " " level))
(println "<error>" (string expr) "<error>"))))
(expr2xml sxml)
<rss>
<version>
0.92
</version>
<channel>
<docs>
http://backend.userland.com/rss092
</docs>
<title>
newLISP Fan Club
</title>
<link>
http://www.alh.net/newlisp/phpbb/
</link>
<description>
Friends and Fans of newLISP
</description>
<managingEditor>
newlispfanclub-at-excite.com
</managingEditor>
...
這幾乎是我們開始的地方!
以下示例最初設定在一家小企業的運輸部門。我將專案改為水果。XML 資料檔案包含所有已售專案的條目及其價格。我們希望生成一份報告,列出每種價格售出了多少件,以及總價值。
以下摘自 XML 資料
<FRUIT>
<NAME>orange</NAME>
<charge>0</charge>
<COLOR>orange</COLOR>
</FRUIT>
<FRUIT>
<NAME>banana</NAME>
<COLOR>yellow</COLOR>
<charge>12.99</charge>
</FRUIT>
<FRUIT>
<NAME>banana</NAME>
<COLOR>yellow</COLOR>
<charge>0</charge>
</FRUIT>
<FRUIT>
<NAME>banana</NAME>
<COLOR>yellow</COLOR>
<charge>No Charge</charge>
</FRUIT>
這是定義和組織任務的主要函式
(define (work-through-files file-list)
(dolist (fl file-list)
(set 'table '())
(scan-file fl)
(write-report fl)))
呼叫了兩個函式:scan-file,它掃描一個 XML 檔案並將所需的資訊儲存在一個表中,該表將成為某種 newLISP 列表,以及 write-report,它掃描該表並輸出一份報告。
scan-file 函式接收一個路徑名,將檔案轉換為 SXML,查詢所有 charge 項(使用 ref-all),並統計每個值的次數。我們允許一些免費專案被標記為 No Charge、no charge 或 nocharge
(define (scan-file f)
(xml-type-tags nil nil nil nil)
(set 'sxml (xml-parse (read-file f) 15))
(set 'r-list (ref-all 'charge sxml))
(dolist (r r-list)
(set 'charge-text (last (sxml (chop r))))
(if (= (lower-case (replace " " charge-text "")) "nocharge")
(set 'charge (lower-case charge-text))
(set 'charge (float charge-text 0 10)))
(if (set 'result (lookup charge table 1))
; if this price already exists in table, increment it
(setf (assoc charge table) (list charge (inc result)))
; or create an entry for it
(push (list charge 1) table -1))))
write-report 函式對錶格進行排序和分析,在進行過程中保持執行總計
(define (write-report fl)
(set 'total-items 0 'running-total 0 'total-priced-items 0)
(println "sorting")
(sort table (fn (x y) (< (float (x 0)) (float (y 0)))))
(println "sorted ")
(println "File: " fl)
(println " Charge Quantity Subtotal")
(dolist (c table)
(set 'price (float (first c)))
(set 'quantity (int (last c)))
(inc total-items quantity)
(cond
; do the No Charge items:
((= price nil) (println (format " No charge %12d" quantity)))
; do 0.00 items
((= price 0) (println (format " 0.00 %12d" quantity)))
; do priced items:
(true
(begin
(set 'subtotal (mul price quantity))
(inc running-total subtotal)
(if (> price 0) (inc total-priced-items quantity))
(println (format "%8.2f %8d %12.2f" price quantity subtotal))))))
; totals
(println (format "Total charged %8d %12.2f" total-priced-items running-total))
(println (format "Grand Total %8d %12.2f" total-items running-total)))
該報告需要比 scan-file 函式更多地進行調整,特別是使用者希望(出於某些原因)將 0 和無費用專案分開。
Charge Quantity Subtotal
No charge 138
0.00 145
0.11 1 0.11
0.29 1 0.29
1.89 72 136.08
1.99 17 33.83
2.99 18 53.82
12.99 55 714.45
17.99 1 17.99
Total charged 165 956.57
Grand Total 448 956.57