跳轉到內容

newLISP 簡介/使用 XML

來自華夏公益教科書

使用 XML

[編輯 | 編輯原始碼]

將 XML 轉換為列表

[編輯 | 編輯原始碼]

如今 XML 檔案被廣泛使用,你可能已經注意到 XML 檔案高度組織的樹狀結構與我們在 newLISP 中遇到的巢狀列表結構類似。那麼,如果你能像處理列表一樣輕鬆地處理 XML 檔案,豈不是很好?

你已經遇到了兩個主要的 XML 處理函式。(見 ref 和 ref-all。)xml-parsexml-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&amp;p=17471#p17471") 
   (link ((href "http://newlispfanclub.alh.net/forum/viewtopic.php?t=3447&amp;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&amp;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 符號中的新聞提要的列表版本。

由於此列表具有複雜的巢狀結構,最好使用 refref-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&amp;p=17471#p17471") 
 (link ((href "http://newlispfanclub.alh.net/forum/viewtopic.php?t=3447&amp;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&amp;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;amp;}  {&amp;})
    ({&amp;gt;}   {>})
    ({&amp;lt;}   {<})
    ({&amp;nbsp;} { })
    ({&amp;apos;} {'})
    ({&amp;quot;} {"})
    ({&amp;#40;}  {(})
    ({&amp;#41;}  {)})
    ({&amp;#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...
; ...

更改 SXML

[編輯 | 編輯原始碼]

你可以使用類似的技術來修改 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 部分等等。

將 SXML 輸出為 XML

[編輯 | 編輯原始碼]

如果你想反過來將 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
華夏公益教科書