cgrand / enlive

a selector-based (à la CSS) templating and transformation system for Clojure
http://wiki.github.com/cgrand/enlive
1.62k stars 152 forks source link

Support for or? #3

Closed swannodette closed 15 years ago

swannodette commented 15 years ago

I'm actually using Enlive to grab data from Amazon Web Services. One thing that would be nice would be something like the or operator.

[[:some :selector] :or [:another :selector]]

if the first selector returns nodes, that is the result. Otherwise move on to the following selector. This would be useful when some selector for some reason doesn't match and you want to fall back on another selector. This is very useful if you are explicitly using the select macro to pull in portions of an xml document.

cgrand commented 15 years ago

Github really need to provide some kind of preview feature...

{[:some :selector] [:another :selector]}

swannodette commented 15 years ago

this is the grouping selector though right? I want to shortcut if the first selector matches, does this make sense?

cgrand commented 15 years ago

Yep, after having slept on it, it occurred to me that what you are asking for is a short-circuiting or. Real short-circuiting is nearly impossible because selection is normally performed on a single pass over the whole tree, and to short-circuit you need to know if the first part of the selector matches something.

I said "normally" because (has xxx) introduce a second selection and, hence, a second pass.

The simplest thing to do is to write a macro on select which outputs: (let [YYY XXX](or %28seq %28select YYY [:some :selector]%29%29 %28select YYY [:another :selector]%29))

I'm thinking on it.

cgrand commented 15 years ago
(defn select-else* [nodes & states] ; for completeness only
  (some #(seq (select* nodes %)) states))

(defmacro select-else [nodes & selectors]
  (let [nodes-sym (gensym "nodes")]
    `(let [~nodes-sym ~nodes]
       (or
         ~@(map (fn [sel]
                  `(seq (select ~nodes-sym ~sel)))
             selectors)))))

I tried to think of a (or-else) combinator (to write (or-else [:some :selector] [:another :selector])), it's doable but it really goes against the grain.

Does select-else (or select-else*) do the trick?

swannodette commented 15 years ago
(select xml #{[:ASIN], [:title], 
                [:Item :> :MediumImage :URL],
                [:ListPrice :Amount]
                [:ListPrice :FormattedPrice]
                [:Publisher]}

Not really. Here's a real example. [:ListPrice :FormattedPrice] doesn't always match. Sometimes the formatted price appears elsewhere. I want to be able to provide an ordered list of possible selectors and short circuit soon as we find one.

If this isn't possible I can come up with a work around.

For XML data sources that are smart enough not to bother with namespacing all their elements, Enlive is a EXCELLENT tool for extracting data.

(def *aws-base-url* "http://ecs.amazonaws.com/onca/xml")

(def *aws-url-params* {:Service        "AWSECommerceService", 
               :AWSAccessKeyId "AKIAI7BLMMASTXEDYLWA",
               :Operation      "ItemLookup",
               :ResponseGroup  "Medium"})

(defn aws-url [itemid]
  (str *aws-base-url* "?"
       (to-url-params 
    (assoc *aws-url-params* :ItemId itemid))))

(defn amazon-data [itemid]
  (html-resource 
    (java.net.URL. (aws-url itemid))))

(defn item-data [xml]
  (let [item-data (select xml #{[:ASIN], [:title], 
                [:Item :> :MediumImage :URL],
                [:ListPrice :Amount]
                [:ListPrice :FormattedPrice]
                [:Publisher]})]
    (apply hash-map 
       (flatten 
        (map #(vector (:tag %) (:content %)) item-data)))))

(defn amazon-info [itemid]
  (item-data (amazon-data itemid)))

Is all I need to extract info from Amazon on any item. Enlive is a killer app ;)

cgrand commented 15 years ago

namespace support is on my todo-list.

completely untested (I'll have more time in the afternoon (CEST)): (defmacro or-else ([sel](selector ~sel)) ([sel & sels] `(sm/union (selector ~sel) (sm/intersection (sm/complement-next (has* (selector ~sel1))) (or-else ~@sels)))))

(btw in which TZ are you? I thought you were on the east coast)

cgrand commented 15 years ago

Closing the issue. Performing multiple passes is the way to go.