2010-05-20 6 views
9

Sono confuso su come modificare in modo idiomatico un albero xml a cui si accede tramite zip-filter.xml di clojure.contrib. Dovresti provare a farlo, o c'è un modo migliore?Inserimenti in alberi Zipper su file XML in Clojure

dire che ho un po 'di file XML fittizio "itemdb.xml" come questo:

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

E ho un po' di codice:

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

Sono poco chiare su come utilizzare il clojure. zip funziona correttamente in questo caso e in che modo interagisce con il filtro zip.

Se si individua qualcosa di particolarmente strano in questo piccolo esempio, si prega di indicarlo.

+0

il contrib.zip filtro .xml è ora spostato in https://github.com/clojure/data.zip/ – claj

risposta

8

In primo luogo, è necessario utilizzare :content (e non :contents) nella definizione di Fred.

Con questo cambiamento in atto, il seguente sembra funzionare:

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

tuo append-item2 è molto simile, ci sono solo due correzioni da fare:

  1. zf/xml-> restituisce una sequenza di cerniera locs; zip/rightmost accetta solo uno, quindi devi pescarne uno per primo (da qui il first in quanto sopra);

  2. dopo aver terminato la modifica della cerniera, è necessario utilizzare zip/root per tornare alla (la versione modificata) dell'albero sottostante.

Come nota finale sullo stile, print + format = printf. :-)

+0

Grazie per le correzioni! C'è ancora qualche strano comportamento qui che sembra essere risolto aggiungendo zip/xml-zip dopo l'ultimo zip/root nel codice. La mancata aggiunta di quella linea aggiuntiva ti impedisce di fare più di un'appendice ... apparentemente zip/root non restituisce una cerniera, solo un'altra posizione, quindi il tipo restituito da append-item non sarà ancora abbastanza corretto, ma buono abbastanza per il lxml/emettere alla fine ... Dovevi ridere della nota printf ... incredibile quante volte puoi fissare qualcosa senza vedere l'ovvio. ;-) – ivar

5

In create-item hai sbagliato a digitare: contenuto per: contenuto e dovresti preferire i vettori agli elenchi per i letterali.

(. Stavo andando a fare una risposta più completa, ma Michal come già scritto una abbastanza buona)

Un'alternativa a zip-filtro è 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

Dovrò dare un'occhiata ravvicinata dopo aver consultato [http://github.com/swannodette/enlive-tutorial/] ... – ivar