returning multiple values using clojure xml zipper
Asked Answered
T

2

4

Lets suppose we have some XML like so:

<a>
  <b>
    <c>text</c>
    <d>
      <e>text</e>
      <f>
        ... lots of cruft here ..
      </f>
    </d>
  </b>
  <b>
    ...
  </b>
  <!-- more b sub-trees --> 
</a>

Now, looking through the samples in zip_filter/xml.clj, I've figured out how to get to single values that I'm interested in.

I'm wondering how I would do something like return a list of pairs of text values of (c e).

EDIT:

Here is some working code, but it's pretty ugly. Not asking for trivial refactoring, but is there a nicer way that zippers give us to do this?

(defn extract-data [xml] 
  (let [items (x/xml-> xml zf/descendants :Item)     ;items not top-level
        getAttributes  #(x/xml1-> % :ItemAttributes) ;items have itemattributes
        getASIN        #(x/xml1-> % :ASIN x/text)    ;items have ASINs
        getTitle       #(x/xml1-> % :Title x/text)   ;itemattributes have Titles
        getAuthor      #(x/xml1-> % :Author x/text)] ;itemattributes have Authors
    (map 
       ;build a function to get everything we need from the items, and apply
      #(let [attributes (getAttributes %)] ;get the attributes, we'll use it twice
         (list 
           (getASIN %) 
           (getTitle attributes) 
           (getAuthor attributes)))
      items)))
Titus answered 9/1, 2010 at 19:4 Comment(0)
D
4

Depending on the clojure version you use, you might find the juxt function useful. Your posted code (only relevant parts):

(defn extract-data
  [xml] 
  (let [...]
    (map (juxt getASIN (comp getTitle getAttributes) (comp getAuthor getAttributes)) items))))
Deconsecrate answered 11/1, 2010 at 12:34 Comment(2)
+1 for showing me juxt and a cleaner way to lay this out. Will try it out soon.Titus
Cool. I can also do this to avoid 2 calls to getAttributes. (map (juxt getASIN (comp (juxt getTitle getAuthor) getAttributes)) items) but then I have to flatten out the Vector.Titus
C
2

I'm sure there is a nicer way, but this does the job:

(letfn [(get-tag [tag coll] (:content (first (filter #(= tag (:tag %)) coll))))]
  (map #(list (get-tag :c %) (get-tag :e (get-tag :d %)))
       (map :content (:content (clojure.xml/parse "foo.xml")))))

results in

((["ctext1"] ["etext1"]) (["ctext2"] ["etext2"]))
Cerated answered 11/1, 2010 at 4:26 Comment(1)
Thanks, I just posted some of the actual code for the real data structure I'm working with. +1 for showing me letfn and one way of doing this.Titus

© 2022 - 2024 — McMap. All rights reserved.