Something like the following does the trick:
(defn substitute-mappings
[string mappings]
(let [substitute-mapping
(fn [string mapping]
(let [pattern (java.util.regex.Pattern/quote (name (key mapping)))
pattern (re-pattern pattern)
matcher (re-matcher pattern string)
replacement (java.util.regex.Matcher/quoteReplacement (str (val mapping)))]
(.replaceAll matcher replacement)))]
(reduce substitute-mapping string (seq mappings))))
It uses reduce with an local function so that the string gets its
replacements one at a time. Actually doing the replacement uses Java's
regular expressions.
Just a few notes about the above:
1. quote and quoteReplacement to take care of any special characters
that may otherwise muck up replacement process
2. The name function is used to convert a keyword to a string without
the initial colon.
3. For integers and the like, I use the str function to coerce them into
a string that can be used as a replacement pattern. So, you'd want
to make sure this works correctly with any data types you want to
use.
Sincerely,
Daniel Solano Gómez
Do we have a str function that will prevent the use of {:_ACCT-ID_
"9876; delete * from account;"}? ;)
Or are those maps only ever going to be visible to the application's internals?
There are several clojure libraries that exist to improve the ease and
safety of doing something like this. Amongst them are
clojure.contrib.sql and ClojureQL, which take different approaches.
They all should be sufficient to guard against SQL injection and
should probably be the first place you look.
For the more general question you were asking about how to generically
replace a map of matches-to-replacements though, Daniel did a good job
showing how to use a reduce over the map. That method will call
"replaceAll" once per entry in the map, which is probably fine if you
don't have many substitutions.
Another way to do it is using clojure.string.replace, which has an
often-overlooked third overload which matches with a regex and
replaces with a "mapping function."
Starting with a simple example:
user=>(require '[clojure.string :as s])
nil
user=>(s/replace "a b a" #"a|b" {"a" "1" "b" "2"})
"1 2 1"
In the example, the map was being used as a "replacement function".
---
If you're willing to change your map to use strings as keys and
values, then the previous example is good enough.
Otherwise, because you're wanting to use keywords as your keys, and
arbitratry values for your values, we'll need to use a slightly more
sophisticated replacement function.
(defn key-pattern
"Create a regex Pattern of the form '<key1>|<key2>', the key names
will be quoted in case they contain special regex characters"
[m]
(->> (keys m)
(map #(java.util.regex.Pattern/quote (name %)))
(s/join "|")
java.util.regex.Pattern/compile))
(defn replace-map [text m]
(s/replace text
(key-pattern m)
(fn [field-name]
(java.util.regex.Matcher/quoteReplacement (str (get m
(keyword field-name)))))))
--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
(defn map-replace [m text](reduce(fn [acc [k v]] (s/replace acc (str k) (str v)))text m))