2012-07-18 18 views
10

ho il seguente codice XML di esempio:Ricerca XML in Clojure

<data> 
    <products> 
    <product> 
     <section>Red Section</section> 
     <images> 
     <image>img.jpg</image> 
     <image>img2.jpg</image> 
     </images> 
    </product> 
    <product> 
     <section>Blue Section</section> 
     <images> 
     <image>img.jpg</image> 
     <image>img3.jpg</image> 
     </images> 
    </product> 
    <product> 
     <section>Green Section</section> 
     <images> 
     <image>img.jpg</image> 
     <image>img2.jpg</image> 
     </images> 
    </product> 
    </products> 
</data> 

so come analizzare in Clojure

(require '[clojure.xml :as xml]) 
(def x (xml/parse 'location/of/that/xml')) 

Ciò restituisce una mappa nidificato che descrive il xml

{:tag :data, 
:attrs nil, 
:content [ 
    {:tag :products, 
     :attrs nil, 
     :content [ 
      {:tag :product, 
      :attrs nil, 
      :content [] .. 

Ovviamente questa struttura può essere attraversata con le funzioni Clojure standard, ma potrebbe rivelarsi davvero prolissa, specialmente se confrontata con o, ad esempio, interrogandolo con XPath. C'è qualche aiuto per attraversare e cercare una tale struttura? Come posso, per esempio

  • ottenere un elenco di tutti <product>
  • ottenere solo il prodotto la cui <images> tag contiene un <image> con il testo "img2.jpg"
  • ottenere il prodotto il cui section è "Sezione Red "

Grazie

risposta

3

È possibile utilizzare una libreria come clj-xpath

012.
+1

Sarebbe disposto a modificare la risposta e aggiungere un esempio? – octopusgrabbus

9

Uso Zippers da data.zip qui è una soluzione per il caso secondo utilizzo:

(ns core 
    (:use clojure.data.zip.xml) 
    (:require [clojure.zip :as zip] 
      [clojure.xml :as xml])) 

(def data (zip/xml-zip (xml/parse PATH))) 
(def products (xml-> data :products :product)) 

(for [product products :let [image (xml-> product :images :image)] 
         :when (some (text= "img2.jpg") image)] 
    {:section (xml1-> product :section text) 
    :images (map text image)}) 
=> ({:section "Red Section", :images ("img.jpg" "img2.jpg")} 
    {:section "Green Section", :images ("img.jpg" "img2.jpg")}) 
0

in molti casi il macro-filo prima lungo con clojures mappa e vettoriali semantica è una sintassi adeguata per l'accesso XML. Ci sono molti casi in cui si desidera qualcosa di più specifico per xml (come una libreria xpath) anche se in molti casi la lingua esistente è quasi concisa con l'aggiunta di eventuali dipendenze.

(pprint (-> (xml/parse "/tmp/xml") 
     :content first :content second :content first :content first)) 
"Blue Section" 
3

Ecco una versione alternativa utilizzando data.zip, per tutti e tre i casi d'uso. Ho trovato che xml-> e xml1-> ha una navigazione di navigazione piuttosto potente, con sottoquery nei vettori.

;; [org.clojure/data.zip "0.1.1"] 

(ns example.core 
    (:require 
    [clojure.zip :as zip] 
    [clojure.xml :as xml] 
    [clojure.data.zip.xml :refer [text xml-> xml1->]])) 

(def data (zip/xml-zip (xml/parse "/tmp/products.xml"))) 

(let [all-products (xml-> data :products :product) 
     red-section (xml1-> data :products :product [:section "Red Section"]) 
     img2 (xml-> data :products :product [:images [:image "img2.jpg"]])] 
    {:all-products (map (fn [product] (xml1-> product :section text)) all-products) 
    :red-section (xml1-> red-section :section text) 
    :img2 (map (fn [product] (xml1-> product :section text)) img2)}) 

=> {:all-products ("Red Section" "Blue Section" "Green Section"), 
    :red-section "Red Section", 
    :img2 ("Red Section" "Green Section")} 
+0

+1 So che hai risposto più tardi ma hai l'unica risposta a tutte e 3 le domande e hai ben separato la navigazione e la segnalazione dei risultati –

1

The Tupelo library può facilmente risolvere i problemi di questo tipo utilizzando struttura dati tupelo.forest albero. Si prega di see this question for more information. Documenti API can be found here.

Qui cariciamo i dati xml e lo convertiamo prima in enlive e quindi nella struttura ad albero nativa utilizzata da tupelo.forest. Libs dati & DEF:

(ns tst.tupelo.forest-examples 
    (:use tupelo.forest tupelo.test) 
    (:require 
    [clojure.data.xml :as dx] 
    [clojure.java.io :as io] 
    [clojure.set :as cs] 
    [net.cgrand.enlive-html :as en-html] 
    [schema.core :as s] 
    [tupelo.core :as t] 
    [tupelo.string :as ts])) 
(t/refer-tupelo) 

(def xml-str-prod "<data> 
        <products> 
         <product> 
         <section>Red Section</section> 
         <images> 
          <image>img.jpg</image> 
          <image>img2.jpg</image> 
         </images> 
         </product> 
         <product> 
         <section>Blue Section</section> 
         <images> 
          <image>img.jpg</image> 
          <image>img3.jpg</image> 
         </images> 
         </product> 
         <product> 
         <section>Green Section</section> 
         <images> 
          <image>img.jpg</image> 
          <image>img2.jpg</image> 
         </images> 
         </product> 
        </products> 
        </data> ") 

e l'inizializzazione di codice:

(dotest 
    (with-forest (new-forest) 
    (let [enlive-tree   (->> xml-str-prod 
           java.io.StringReader. 
           en-html/html-resource 
           first) 
      root-hid    (add-tree-enlive enlive-tree) 
      tree-1    (hid->hiccup root-hid) 

Il suffisso HID sta per "Hex ID", che è il valore esadecimale unico che si comporta come un puntatore a un nodo/foglia nell'albero .In questa fase abbiamo appena caricato i dati nella struttura dati forestali, la creazione di albero-1 che assomiglia a:

[:data 
[:tupelo.forest/raw "\n     "] 
[:products 
    [:tupelo.forest/raw "\n      "] 
    [:product 
    [:tupelo.forest/raw "\n      "] 
    [:section "Red Section"] 
    [:tupelo.forest/raw "\n      "] 
    [:images 
    [:tupelo.forest/raw "\n       "] 
    [:image "img.jpg"] 
    [:tupelo.forest/raw "\n       "] 
    [:image "img2.jpg"] 
    [:tupelo.forest/raw "\n      "]] 
    [:tupelo.forest/raw "\n      "]] 
    [:tupelo.forest/raw "\n      "] 
    [:product 
    [:tupelo.forest/raw "\n      "] 
    [:section "Blue Section"] 
    [:tupelo.forest/raw "\n      "] 
    [:images 
    [:tupelo.forest/raw "\n       "] 
    [:image "img.jpg"] 
    [:tupelo.forest/raw "\n       "] 
    [:image "img3.jpg"] 
    [:tupelo.forest/raw "\n      "]] 
    [:tupelo.forest/raw "\n      "]] 
    [:tupelo.forest/raw "\n      "] 
    [:product 
    [:tupelo.forest/raw "\n      "] 
    [:section "Green Section"] 
    [:tupelo.forest/raw "\n      "] 
    [:images 
    [:tupelo.forest/raw "\n       "] 
    [:image "img.jpg"] 
    [:tupelo.forest/raw "\n       "] 
    [:image "img2.jpg"] 
    [:tupelo.forest/raw "\n      "]] 
    [:tupelo.forest/raw "\n      "]] 
    [:tupelo.forest/raw "\n     "]] 
[:tupelo.forest/raw "\n     "]] 

Togliamo prossimo tutte le stringhe vuote con questo codice:

blank-leaf-hid?  (fn [hid] (and (leaf-hid? hid) ; ensure it is a leaf node 
           (let [value (hid->value hid)] 
             (and (string? value) 
             (or (zero? (count value)) ; empty string 
              (ts/whitespace? value)))))) ; all whitespace string 

blank-leaf-hids  (keep-if blank-leaf-hid? (all-hids)) 
>>     (apply remove-hid blank-leaf-hids) 
tree-2    (hid->hiccup root-hid) 

per produrre un molto più bello albero risultato (formato singhiozzo)

[:data 
[:products 
    [:product 
    [:section "Red Section"] 
    [:images [:image "img.jpg"] [:image "img2.jpg"]]] 
    [:product 
    [:section "Blue Section"] 
    [:images [:image "img.jpg"] [:image "img3.jpg"]]] 
    [:product 
    [:section "Green Section"] 
    [:images [:image "img.jpg"] [:image "img2.jpg"]]]]] 

Il codice seguente calcola quindi le risposte alle tre domande di cui sopra:

product-hids   (find-hids root-hid [:** :product]) 
product-trees-hiccup (mapv hid->hiccup product-hids) 

img2-paths   (find-paths-leaf root-hid [:data :products :product :images :image] "img2.jpg") 
img2-prod-paths  (mapv #(drop-last 2 %) img2-paths) 
img2-prod-hids  (mapv last img2-prod-paths) 
img2-trees-hiccup (mapv hid->hiccup img2-prod-hids) 

red-sect-paths  (find-paths-leaf root-hid [:data :products :product :section] "Red Section") 
red-prod-paths  (mapv #(drop-last 1 %) red-sect-paths) 
red-prod-hids  (mapv last red-prod-paths) 
red-trees-hiccup  (mapv hid->hiccup red-prod-hids)] 

con i risultati:

(is= product-trees-hiccup 
    [[:product 
    [:section "Red Section"] 
    [:images 
     [:image "img.jpg"] 
     [:image "img2.jpg"]]] 
    [:product 
    [:section "Blue Section"] 
    [:images 
     [:image "img.jpg"] 
     [:image "img3.jpg"]]] 
    [:product 
    [:section "Green Section"] 
    [:images 
     [:image "img.jpg"] 
     [:image "img2.jpg"]]]]) 

(is= img2-trees-hiccup 
    [[:product 
    [:section "Red Section"] 
    [:images 
    [:image "img.jpg"] 
    [:image "img2.jpg"]]] 
    [:product 
    [:section "Green Section"] 
    [:images 
    [:image "img.jpg"] 
    [:image "img2.jpg"]]]]) 

(is= red-trees-hiccup 
    [[:product 
    [:section "Red Section"] 
    [:images 
    [:image "img.jpg"] 
    [:image "img2.jpg"]]]])))) 

L'esempio completo può essere trovato in the forest-examples unit test.