Fork me on GitHub
#meander
<
2021-11-07
>
chaos17:11:43

Hi, what is the right way to rewrite map key/value pairs of a nested structures? I think I should be using a strategy, but can't seem to make it work. For example, I thought the following would rewrite any map :viewbox entries with :viewBox (capital b), but it loses the rest of the entries in the map (as if the substitution rule replaces the whole map rather than the matching entry):

(let [vb (m*/bottom-up 
            (m*/attempt
             (m*/match 
              {:viewbox (m/some ?vb)} {:viewBox ?vb})))]
    (vb [:svg
         {:xmlns "",
          :viewbox "0 0 24 24"}]))
;; => [:svg {:viewBox "0 0 24 24"}]
Thanks

Richie17:11:03

(let [vb (r/bottom-up
          (r/attempt
           (r/match
            {:viewbox (m/some ?vb)
             & ?rest} (into {:viewBox ?vb} ?rest))))]
  (vb [:svg
       {:xmlns "" ,
        :viewbox "0 0 24 24"}]))
[:svg {:viewBox "0 0 24 24", :xmlns ""}]

👍 1
chaos18:11:33

Great, it works, thanks!

noprompt19:11:18

You could also use

(r/rewrite
  {:viewbox (m/some ?vb) & ?rest}
  {:viewBox ?vb & ?rest})

🎉 1
chaos19:11:49

Thanks! unfortunately I have a second rule for naively splitting the style attribute, which I do not think it can be used for a rewrite substitution (or can it?):

(let [t1 (m*/bottom-up 
            (m*/attempt
             (m*/match 
              {:viewbox (m/some ?vb) & ?rest}
              (assoc ?rest :viewBox ?vb)

              {:style (m/some ?st) & ?rest}
              (assoc ?rest :style (let [[k v] (clojure.string/split ?st #": " 2)]
                                    {(keyword k) (str v)})))))]
    (t1 [:div
         {:style "width: 100px"}
         [:svg
          {:xmlns "",
           :viewbox "0 0 24 24"}]]))
;; => [:div
 {:style {:width "100px"}}
 [:svg {:xmlns "", :viewBox "0 0 24 24"}]]

noprompt19:11:02

Sure, you can use m/re for the splitting on the left side and m/keyword on the right:

(m*/rewrite 
 {:viewbox (m/some ?vb) & ?rest}
 {:viewBox ?vb & ?rest}

 {:style (m/re #"(?s)([^:])*:\s+(.*)" [_ ?k ?v]) & ?rest}
 {:style {(m/keyword ?k) ?v} & ?rest})

noprompt19:11:17

t1 produces

[:div
 {:style {:h "100px"}}
 [:svg {:xmlns "", :viewBox "0 0 24 24"}]]

noprompt19:11:45

If you don't need . to match newlines you can drop the (?s) modifier

chaos20:11:48

Thanks, pretty cool thinking only in terms of pure symbolic terms:

(let [t2 (m*/bottom-up 
            (m*/attempt
             (m*/rewrite
              {:viewbox (m/some ?vb) & ?rest}
              {:viewBox ?vb & ?rest}
              
              {:style (m/re #"(?s)([^:]+):\s+(.*)" [_ ?k ?v]) & ?rest}
              {:style {(m/keyword ?k) ?v} & ?rest}
              )))]
    (t2 [:div
         {:style "width: 100px"}
         [:svg
          {:xmlns "",
           :viewbox "0 0 24 24"}]]))
;; => [:div
 {:style {:width "100px"}}
 [:svg {:xmlns "", :viewBox "0 0 24 24"}]]

chaos20:11:33

Up to the next thing I'd need to consider, what if :style has more than one inline styles separated by ; that I'd like to collect in the final result, would this be something I could write with pure rewrite terms? for example I can quickly update match to further naively split on ; :

(let [t1 (m*/bottom-up 
            (m*/attempt
             (m*/match 
              {:viewbox (m/some ?vb) & ?rest}
              (assoc ?rest :viewBox ?vb)

              {:style (m/some ?st) & ?rest}
              (assoc ?rest :style (into {}
                                        (for [style (clojure.string/split ?st #"; ")]
                                          (let [[k v] (clojure.string/split style #": " 2)]
                                            {(keyword k) (str v)})))))))]
    (t1 [:div
         {:style "width: 100px; height: 200px"}
         [:svg
          {:xmlns "",
           :viewbox "0 0 24 24"}]]))
;; => [:div
 {:style {:width "100px", :height "200px"}}
 [:svg {:xmlns "", :viewBox "0 0 24 24"}]]

chaos20:11:30

(It is not important, I am just trying to see how far I could go by just using pure symbolic substitutions)

Richie21:11:09

I don't have experience with strategies. I can't run the code...

;; (:require [meander.epsilon :as m]
;;           [meander.strategy.epsilon :as r])
(let [t1 (r/rewrite
          {:viewbox (m/some ?vb) & ?rest}
          {:viewBox ?vb & ?rest}

          {:style (m/re #"(?s)([^:])*:\s+(.*)" [_ ?k ?v]) & ?rest}
          {:style {(m/keyword ?k) ?v} & ?rest}
          )]
  (t1 [:div
       {:style "width: 100px; height: 200px"}
       [:svg
        {:xmlns "",
         :viewbox "0 0 24 24"}]]))
gives me #meander.epsilon/fail[]

chaos21:11:10

perhaps this is because r/attempt is missing from the code and fails on the first mismatch?

Richie20:11:07

Oh, ok. I thought noprompt was suggesting rewrite instead of bottom-up+attempt+match. I guess it would actually be bottom-up+attempt+rewrite? Anyways, I think you can just write a function to do what you want and stick it in with app

(let [t1 (r/bottom-up
          (r/attempt
           (r/rewrite
            {:viewbox (m/some ?vb) & ?rest}
            {:viewBox ?vb & ?rest}

            {:style (m/app (constantly :whatever) ?a) & ?rest}
            {:style ?a & ?rest})))]
  (t1 [:div
       {:style "width: 100px; height: 200px"}
       [:svg
        {:xmlns "",
         :viewbox "0 0 24 24"}]]))
[:div {:style :whatever} [:svg {:xmlns "", :viewBox "0 0 24 24"}]]

noprompt23:11:39

This looks pretty clean, IMHO. Sorry I haven’t been able to participate in the thread since I first replied, I haven’t had a moment to focus here.

chaos18:11:47

Great thanks, you have both been very helpful. And the final rewrite version with the naive split:

(let [styles->map
        #(into {} (for [style (clojure.string/split % #"; ")]
                    (let [[k v] (clojure.string/split style #": " 2)]
                      {(keyword k) (str v)})))

        t4 (m*/bottom-up 
            (m*/attempt
             (m*/rewrite
              {:viewbox (m/some ?vb) & ?rest}
              {:viewBox ?vb & ?rest}
              
              {:style (m/app styles->map ?styles) & ?rest}
              {:style ?styles & ?rest}
              )))]
    (t4 [:div
         {:style "width: 100px; height: 200px"}
         [:svg
          {:xmlns "",
           :viewbox "0 0 24 24"}]]))
;; => [:div
;;     {:style {:width "100px", :height "200px"}}
;;     [:svg {:xmlns "", :viewBox "0 0 24 24"}]]

chaos18:11:46

I think I have enough now to get going with rewrite