閱讀842 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Clojure世界:XML處理

   XML處理也是個常見的編程工作,雖然說在Clojure裏你很少使用XML做配置文件,但是跟遺留係統集成或者處理和其他係統通訊,可能都需要處理XML。

    Clojure的標準庫clojure.xml就是用來幹這個事情的。一個簡單的例子如下,首先我們要解析的是下麵這個簡單的XML:
<?xml version="1.0" encoding="UTF-8"?>
<books>
  
<book>
    
<title>The joy of clojure</title>
    
<author>Michael Fogus / Chris House</author>
  
</book>
  
<book>
    
<title>Programming clojure</title>
    
<author>Stuart Halloway</author>
  
</book>
  
<book>
    
<title>Practical clojure</title>
    
<author>Luke Van der Hart</author>
  
</book>
</books>

    解析xml用clojure.xml/parse方法即可,該方法返回一個clojure.xml/element這個struct-map組成的一棵樹:
user=> (use '[clojure.xml])
nil
user
=> (parse "test.xml")
{:tag :books, :attrs nil, :content
 [{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]}
 {:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]}
 {:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Practical clojure"]} {:tag :author, :attrs nil, :content ["Luke Van der Hart"]}]}]}

這是一個嵌套的數據結構,每個節點都是clojure.xml/element結構,element包括:
(defstruct element :tag :attrs :content)
   tag、attrs和content屬性,tag就是該節點的標簽,attrs是一個屬性的map,而content是它的內容或者子節點。element是一個struct map,它也定義了三個方法來分別獲取這三個屬性:
user=> (def x (parse "test.xml"))
#'user/x
user=> (tag x)
:books
user
=> (attrs x)
nil
user
=> (content x)
[{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]}
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]}
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Practical clojure"]} {:tag :author, :attrs nil, :content ["Luke Van der Hart"]}]}]
   books節點是root node,它的content就是三個book子節點,子節點組織成一個vector,我們可以隨意操作:
user=> (tag (first (content x)))
:book
user
=> (content (first (content x)))
[{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]
user
=> (content (first (content (first (content x)))))
[
"The joy of clojure"]

     額外提下,clojure.xml是利用SAX API做解析的。同樣它還有個方法,可以將解析出來的結構還原成xml,通過emit:
user=> (emit x)

<?xml version='1.0' encoding='UTF-8'?>
<books>
<book>
<title>
The joy of clojure
</title>
<author>
Michael Fogus / Chris House
</author>
</book>
<book>

     如果你要按照深度優先的順序遍曆xml,可以利用xml-seq將解析出來的樹構成一個按照深度優先順序排列節點的LazySeq,接下來就可以按照seq的方式處理,比如利用for來過濾節點:
user=> (for [node (xml-seq x)
                  :when (= :author (:tag node))]
            (first (:content node)))
("Michael Fogus / Chris House" "Stuart Halloway" "Luke Van der Hart")

    通過:when指定了條件,要求節點的tag是author,這樣就可以查找出所有的author節點的content,是不是很方便?就像寫英語描述一樣。

    更進一步,如果你想操作parse解析出來的這棵樹,你還可以利用clojure.zip這個標準庫,它有xml-zip函數將xml轉換成zipper結構,並提供一係列方法來操作這棵樹:
user=>(def xz (xml-zip x))
#'user/xz
user=> (node (down xz))
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]}
user
=> (-> xz down right node)
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]}
user
=> (-> xz down right right node)
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Practical clojure"]} {:tag :author, :attrs nil, :content ["Luke Van der Hart"]}]}
user
=> (-> xz down right right lefts)
({:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]}
 {:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]})

  是不是酷得一塌煳塗?可以通過up,down,left,right,lefts,rights,來查找節點的鄰近節點,可以通過node來得到節點本身。一切顯得那麼自然。更進一步,你還可以“編輯“這棵樹,比如刪除The joy of clojure這本書:
user=>  (def loc-in-new-tree (remove (down xz)))
#'user/loc-in-new-tree
user=> (root loc-in-new-tree)
{:tag :books, :attrs nil, :content
[{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]}
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Practical clojure"]} {:tag :author, :attrs nil, :content ["Luke Van der Hart"]}]}]}

   ok,隻剩下兩本書了,更多方法還包括replace做替換,edit更改節點等。因此編輯XML並重新生成,你一般可以利用clojure.zip來更改樹,最後利用clojure.xml/emit將更改還原為xml。

    生成xml除了emit方法,還有一個contrib庫,也就是prxml,這個庫的clojure 1.3版本有人維護了一個分支,在這裏。主要方法就是prxml,它可以將clojure的數據結構轉換成xml:
user=>(prxml [:p [:raw! "<i>here & gone</i>"]])
<p><i>here & gone</i></p>

    顯然,它也可以用於生成HTML。

    xpath的支持可以使用clj-xpath這個開源庫,遺憾的是它目前僅支持clojure 1.2。


文章轉自莊周夢蝶  ,原文發布時間2012-02-18

最後更新:2017-05-18 20:36:13

  上一篇:go  Clojure-Control 0.3.0 is out
  下一篇:go  Clojure世界:文件IO