2010-05-20 3 views
9

Ich bin verwirrt, wie idiomatisch eine XML-Struktur ändern, auf die durch clojure.contribs zip-filter.xml zugegriffen wird. Sollte das überhaupt versuchen, oder gibt es einen besseren Weg?Einfügen in Zipper-Bäume auf XML-Dateien in Clojure

sagen, dass ich eine Dummy-XML-Datei "itemdb.xml" wie dieses:

<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> 

Und ich habe einige Code:

(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))) 

Ich bin unklar, wie die clojure zu verwenden. zip funktioniert in diesem Fall und wie das mit zip-filter interagiert.

Wenn Sie in diesem kleinen Beispiel etwas besonders Seltsames entdecken, weisen Sie bitte darauf hin.

+0

der contrib.zip-Filter .xml wird jetzt nach https://github.com/clojure/data.zip/ verschoben. – claj

Antwort

8

Zuerst sollten Sie :content (und nicht :contents) in Ihrer Definition von Fred verwenden.

Mit dieser Änderung an Ort und Stelle, so scheint folgende zu arbeiten:

(-> (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 

Ihre append-item2 ist sehr ähnlich, es gibt nur zwei Korrekturen vorzunehmen:

  1. zf/xml-> gibt eine Sequenz des Reißverschlusses locs; zip/rightmost akzeptiert nur einen, also müssen Sie zuerst einen fischen (daher die first oben);

  2. Wenn Sie fertig mit dem Ändern des Reißverschlusses sind, müssen Sie zip/root verwenden, um die zugrunde liegende Struktur wiederherzustellen (die modifizierte Version von).

Als abschließende Bemerkung auf Stil, print + format = printf. :-)

+0

Danke für die Korrekturen! Es gibt hier noch ein merkwürdiges Verhalten, das anscheinend durch das Hinzufügen von zip/xml-zip nach dem letzten Zip/root in Ihrem Code gelöst wurde. Das Versäumnis, diese zusätzliche Zeile hinzuzufügen, verhindert, dass Sie mehr als einen Anhang erstellen ... anscheinend gibt zip/root keinen Zipper zurück, nur einen anderen Ort, so dass der vom append-item zurückgegebene Typ immer noch nicht ganz richtig ist, aber gut genug für die lxml/emit am ende ... Hatte über den printf note zu lachen ... erstaunlich, wie oft man etwas anstarren kann, ohne das offensichtliche zu sehen. ;-) – ivar

5

In create-item haben Sie sich vertippt: Inhalt für: Inhalt und Sie sollten Vektoren zu Listen für Literale bevorzugen.

(. I eine umfassendere Antwort machen würde, aber Michal wie schon ziemlich gut geschrieben)

Eine Alternative zur Zip-Filter ist 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

Ich werde mich erst einmal genauer anschauen müssen, wenn ich [http://github.com/swannodette/enlive-tutorial/] betrachte ... – ivar