Reputation: 886
I want to do named string formatting, and I don't want to use interpolation because the string will be fetched from CSV.
Suppose I have string data from CSV that I parsed
str = [
"Hello %{name}, do you come from %{country} ?",
"Did %{name} just arrived from %{country} ?",
"Today, %{name} will go to %{country}",
"Today, %{name} will join us"
]
In Ruby I can do this:
data = {name: "Julio", country: "Nirvana"}
str.each do |s|
puts str % data
end
# Hello Julio, do you come from Nirvana ?
# Did Julio just arrived from Nirvana ?
# Today, Julio will go to Nirvana ?
# Today, Julio will join us
what is the equivalent in elixir?
I don't mind using hex library
Upvotes: 0
Views: 220
Reputation: 222060
You can use Regex.replace/3
to do the replacements in one go. This would be more efficient than @Adam's for
+ String.replace/3
solution, especially if there a large number of elements in data
:
strings = [
"Hello %{name}, do you come from %{country} ?",
"Did %{name} just arrived from %{country} ?",
"Today, %{name} will go to %{country}",
"Today, %{name} will join us"
]
data = %{"name" => "Julio", "country" => "Nirvana"}
for string <- strings do
Regex.replace(~r/%{(\w+)}/, string, fn _, key -> Map.fetch!(data, key) end)
end
|> IO.inspect()
Output:
["Hello Julio, do you come from Nirvana ?",
"Did Julio just arrived from Nirvana ?",
"Today, Julio will go to Nirvana",
"Today, Julio will join us"]
The regex /%{(\w+)}/
matches %{
followed by one or more letters or numbers (\w+
) followed by }
, capturing the letter/number sequence, which we use in the second argument of the replacement callback to fetch the right replacement value from data
.
One more difference compared to @Adam's solution is that this will raise an error if the string contains an interpolation that does not exist in data
instead of silently ignoring it:
strings = ["Today, %{name} will go to %{city}"]
raises
** (KeyError) key "city" not found in: %{"country" => "Nirvana", "name" => "Julio"}
:erlang.map_get("city", %{"country" => "Nirvana", "name" => "Julio"})
(You can handle such errors in a different way if you want by changing what the replacement function does.)
Upvotes: 2
Reputation: 23091
You can use a two-level comprehension
combined with String.replace/4
to replace the placeholders with their corresponding values.
strings = [
"Hello %{name}, do you come from %{country} ?",
"Did %{name} just arrived from %{country} ?",
"Today, %{name} will go to %{country}",
"Today, %{name} will join us"
]
data = %{name: "Julio", country: "Nirvana"}
for string <- strings do
for {key, value} <- data, reduce: string do
string -> String.replace(string, "%{#{key}}", value)
end
end
Output:
[
"Hello Julio, do you come from Nirvana ?",
"Did Julio just arrived from Nirvana ?",
"Today, Julio will go to Nirvana",
"Today, Julio will join us"
]
There is no problem using interpolation here, because we are only interpolating the map keys, not evaluating arbitrary code.
The same thing using Enum.map/2
and Enum.reduce/3
instead of comprehensions:
Enum.map(strings, fn string ->
Enum.reduce(data, string, fn {key, value}, string ->
String.replace(string, "%{#{key}}", value)
end)
end)
Upvotes: 2
Reputation: 3134
You can use EEx
which is part of Elixir and doesn't need to be installed from Hex:
strs = [
"Hello <%= name %>, do you come from <%=country %> ?",
"Did <%= name %> just arrived from <%= country %> ?",
"Today, <%= name %> will go to <%= country %>",
"Today, <%= name %> will join us"
]
data = [name: "Julio", country: "Nirvana"]
Enum.map(strs, fn str -> EEx.eval_string(str, data) end)
EEx
allows to execute arbitrary Elixir code though, so this would be a security risk if you need to work with user-provided CSV files.
Upvotes: 1