specter

Latest version: 1.1.3
steveb8n 2018-07-24T06:55:36.000098Z

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

steveb8n 2018-07-24T07:25:42.000109Z

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

Petrus Theron 2018-07-24T09:59:25.000334Z

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

nathanmarz 2018-07-24T12:44:03.000266Z

@petrus not totally clear on what you want

nathanmarz 2018-07-24T12:44:32.000114Z

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

nathanmarz 2018-07-24T12:45:05.000030Z

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

nathanmarz 2018-07-24T17:05:12.000216Z

@petrus is this what you're looking for?

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

nathanmarz 2018-07-24T17:07:28.000491Z

or this?

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

nathanmarz 2018-07-24T17:16:56.000261Z

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

nathanmarz 2018-07-24T17:17:06.000355Z

e.g.

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

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

nathanmarz 2018-07-24T17:18:40.000118Z

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

nathanmarz 2018-07-24T17:18:50.000280Z

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

nathanmarz 2018-07-24T17:21:10.000167Z

if you want to maintain the general structure, then yes