Fork me on GitHub
#specter
<
2018-07-24
>
steveb8n06:07:36

anyone ever seen an error like this before at compile time? java.lang.StackOverflowError, compiling:(com/rpl/specter/util_macros.clj:61:29 Iā€™m seeing it when using Specter in Datomic Ions

steveb8n07:07:42

scratch that. It appears to be a side-effect of a dependencies problem

Petrus Theron09:07:25

I'm learning Specter and trying to parse an XML response I got from Twilio. I'm not super-familiar with Clojure zippers in general, but I suspect it'll be cleaner & faster with Specter. I've stumbled onto the S/collect function, but I'm having a hard time fetching "sibling" nodes when dealing with a shape like this:

{:tag :TwilioResponse,
 :attrs nil,
 :content [{:tag :Call,
            :attrs nil,
            :content [{:tag :AnsweredBy, :attrs nil, :content nil}
                      {:tag :PriceUnit, :attrs nil, :content ["USD"]}
                      {:tag :From, :attrs nil, :content ["+2787..."]}
                      {:tag :To, :attrs nil, :content ["+2776..."]}
}]}]}
How do I ask Specter to collect the deepest :tag and :content values in the structure, but only if the parent :tag is :TwilioResponse, with a :Call parent under :tag? I managed to get what I want by doing the following, but it feels like there must be a cleaner way to refer to parents/siblings:
(->> my-parsed-xml (S/select-one [(S/collect-one :content S/FIRST :content)
                        :tag (S/pred= :TwilioResponse)])
         first
         (map (juxt :tag (comp first :content))))

nathanmarz12:07:03

@petrus not totally clear on what you want

Petrus Theron16:07:26

if I restructure the response as a nested map (assuming no dupes) {:TwilioResponse {:Call {:AnsweredBy nil :PriceUnit ["USD"] ...}} I'd be done with a (get-in [:TwilioResponse :Call]]), which I can do with with a few functions, but I'm hoping this can be cleanly done with a Specter select and XML zippers.

nathanmarz12:07:32

you want nodes in this tree that have a parent tag of :Call and grandparent tag of :TwilioResponse?

nathanmarz12:07:05

can this substructure exist at any depth, or only starting at the root?

Petrus Theron16:07:26

if I restructure the response as a nested map (assuming no dupes) {:TwilioResponse {:Call {:AnsweredBy nil :PriceUnit ["USD"] ...}} I'd be done with a (get-in [:TwilioResponse :Call]]), which I can do with with a few functions, but I'm hoping this can be cleanly done with a Specter select and XML zippers.

nathanmarz17:07:12

@petrus is this what you're looking for?

(select-first
  [#(= :TwilioResponse (:tag %))
   :content
   ALL
   #(= :Call (:tag %))
   :content]
  data)

nathanmarz17:07:28

or this?

(select
  [#(= :TwilioResponse (:tag %))
   :content
   ALL
   #(= :Call (:tag %))
   :content
   ALL
   (collect-one :tag)
   :content
   (view first)]
  data)

Petrus Theron17:07:41

yes, thanks @nathanmarz! šŸ™‚ exactly right after a call to (into {} ...) (assume no duplicate tags). Is there a cleaner way to "walk" the XML tree {:tag .. :content ...} layout so that {:tag :x :content :y} becomes [:x :y] or {:x :y}?

Petrus Theron17:07:41

yes, thanks @nathanmarz! šŸ™‚ exactly right after a call to (into {} ...) (assume no duplicate tags). Is there a cleaner way to "walk" the XML tree {:tag .. :content ...} layout so that {:tag :x :content :y} becomes [:x :y] or {:x :y}?

nathanmarz17:07:56

you can factor out any composition of navigators as its own navigator

nathanmarz17:07:06

e.g.

(def tag+content
  (path
    (collect-one :tag)
    :content
    (view first)))

(select
  [#(= :TwilioResponse (:tag %))
   :content
   ALL
   #(= :Call (:tag %))
   :content
   ALL
   tag+content]
  data)

Petrus Theron17:07:11

Would a recursive-path walker be cleaner for transforming the general {:tag :content [{:tag ... :content [...]]} shape returned by clojure.xml/parse in general, then selecting a simpler nested path from there?

nathanmarz17:07:40

if you want to navigate to the pair, you can use subselect + multi-path

nathanmarz17:07:50

(select
  [#(= :TwilioResponse (:tag %))
   :content
   ALL
   #(= :Call (:tag %))
   :content
   ALL
   (subselect (multi-path :tag [:content (view first)]))]
  data)

Petrus Theron17:07:11

Would a recursive-path walker be cleaner for transforming the general {:tag :content [{:tag ... :content [...]]} shape returned by clojure.xml/parse in general, then selecting a simpler nested path from there?

nathanmarz17:07:10

if you want to maintain the general structure, then yes