Вставки в Zipper-деревья в XML-файлах в Clojure - PullRequest
9 голосов
/ 20 мая 2010

Я запутался, как идиоматически изменять дерево xml, доступ к которому осуществляется через zip-filter.xml clojure.contrib. Стоит ли пытаться сделать это вообще, или есть лучший способ?

Скажите, что у меня есть фиктивный xml-файл "itemdb.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-фильтром.

Если вы заметите что-то особенно странное в этом небольшом примере, укажите это.

Ответы [ 2 ]

8 голосов
/ 20 мая 2010

Во-первых, вы должны использовать :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-> возвращает последовательность locs на молнии; zip/rightmost принимает только один, поэтому вы должны сначала выловить один (отсюда first в приведенном выше);

  2. после того, как вы закончите модификацию молнии, вам нужно использовать zip/root, чтобы вернуть (измененную версию) базовое дерево.

В качестве заключительного замечания по стилю, print + format = printf. : -)

5 голосов
/ 20 мая 2010

В create-item вы опечатали: содержимое для: содержимого, и вы должны предпочесть векторы спискам для литералов.

(Я собирался сделать более полный ответ, но Михал, как уже было написано, неплохо.)

Альтернативой zip-фильтру является 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))))
...