Clojure中对XML文件的Zipper树进行插入操作

9
我不确定如何通过clojure.contrib的zip-filter.xml适当地更改XML树。我应该尝试这样做吗?还是有更好的方法?
假设我有一个名为“itemdb.xml”的虚拟XML文件,内容如下:
<itemlist> 
  <item id="1">
    <name>John</name>
    <desc>Works near here.</desc>
  </item>
  <item id="2">
    <name>Sally</name>
    <desc>Owner of pet store.</desc>
  </item>
</itemlist>

我有一些代码:

(require '[clojure.zip :as zip]
  '[clojure.contrib.duck-streams :as ds]
  '[clojure.contrib.lazy-xml :as lxml]
  '[clojure.contrib.zip-filter.xml :as zf]) 

(def db (ref (zip/xml-zip (lxml/parse-trim (java.io.File. "itemdb.xml")))))

;; Test that we can traverse and parse.
(doall (map #(print (format "%10s: %s\n"
       (apply str (zf/xml-> % :name zf/text))
       (apply str (zf/xml-> % :desc zf/text))))
     (zf/xml-> @db :item)))

;; I assume something like this is needed to make the xml tags
(defn create-item [name desc]
  {:tag :item
   :attrs {:id "3"}
   :contents
   (list {:tag :name :attrs {} :contents (list name)}
         {:tag :desc :attrs {} :contents (list desc)})})

(def fred-item (create-item "Fred" "Green-haired astrophysicist."))

;; This disturbs the structure somehow
(defn append-item [xmldb item]
  (zip/insert-right (-> xmldb zip/down zip/rightmost) item))

;; I want to do something more like this
(defn append-item2 [xmldb item]
  (zip/insert-right (zip/rightmost (zf/xml-> xmldb :item)) item))

(dosync (alter db append-item2 fred-item))

;; Save this simple xml file with some added stuff.
(ds/spit "appended-itemdb.xml"
    (with-out-str (lxml/emit (zip/root @db) :pad true)))

我不清楚如何在这种情况下适当使用clojure.zip函数,以及它如何与zip-filter交互。

如果您在这个小例子中发现任何特别奇怪的地方,请指出来。


contrib.zip-filter.xml现已移至https://github.com/clojure/data.zip/。 - claj
2个回答

8
首先,在您定义Fred时应使用:content(而不是:contents)。
有了这个更改,以下内容似乎可以工作:
(-> (zf/xml-> @db :item) ; a convenient way to get to the :item zipper locs
    first                ; but we actually need just one
    zip/rightmost        ; let's move to the rightmost sibling of the first :item
                         ; (which is the last :item in this case)
    (zip/insert-right fred-item) ; insert Fred to the right
    zip/root)            ; get the modified XML map,
                         ; which is the root of the modified zipper

您的append-item2非常相似,只需要进行两个更正:

  1. zf/xml->返回一系列zipper locs;zip/rightmost仅接受一个,因此您必须先找到其中一个(因此上面有first);

  2. 在修改zipper后,您需要使用zip/root来返回底层树的(修改后的版本)。

最后关于样式的注释:print + format = printf。:-)


感谢您的更正!这里仍然存在一些奇怪的行为,似乎通过在代码的最终zip/root之后添加zip/xml-zip来解决。如果没有添加额外的行,您将无法进行多个附加操作...显然,zip/root不会返回一个zipper,而只是另一个位置,因此由append-item返回的类型仍然不完全正确,但足够好用于最后的lxml/emit...对于printf注释不得不笑...惊人的是你可以盯着某件事情看很多次而没有看到明显的错误。;-) - ivar

5
在create-item中,您将:contents错误拼写为:content,并且应该使用向量来表示字面量,而不是列表。
(我本来想提供更全面的答案,但Michal已经写了一个相当不错的答案。)
zip-filter的替代方案是Enlive:
(require '[net.cgrand.enlive-html :as e]) ;' <- fix SO colorizer

(def db (ref (-> "itemdb.xml" java.io.File. e/xml-resource))

(defn create-item [name desc]
  {:tag :item
   :attrs {:id "3"}
   :content [{:tag :name :attrs {} :content [name]}
             {:tag :desc :attrs {} :content [desc]}]})

(def fred-item (create-item "Fred" "Green-haired astrophysicist."))

(dosync (alter db (e/transformation [:itemlist] (e/append fred-item))))

1
在查看了[http://github.com/swannodette/enlive-tutorial/]后,我必须更仔细地研究enlive。 - ivar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接