Is there a faster way to write this code?

String.replace uses :binary.replace which in turn uses binary:matches with some recursion. By writing a custom version of :binary.replace we can do it all in one go.

I should say that :binary.replace almost works out of the box.

:binary.replace(body, ["&", "<", ">"], <<"&;">>, [:global, {:insert_replaced, 1}])

Unfortunately we need not just to wrap the replacement but substitute them with amp, lt and gt respectively.

Anyway. After a quick look at binary.replace implementation you can do the same with:

  def replace_char("&"), do: "&amp;"
  def replace_char(">"), do: "&gt;"
  def replace_char("<"), do: "&lt;"

  def escape_body(body) do
    match_list = :binary.matches(body, ["&", ">", "<"])
    IO.iodata_to_binary(do_replace(body, match_list, &replace_char/1, 0))
  end

  def do_replace(h, [], _, n) do
    [ :binary.part(h, {n, byte_size(h) - n}) ]
  end
  def do_replace(h, [{a, b} | t ], replace_fun, n) do
    [ :binary.part(h, {n, a - n}),
      replace_fun.(:binary.part(h, {a, b}))
      | do_replace(h, t, replace_fun, a  + b)
    ]
  end

This is basically the binary:replace/3 function with some options taken away and using a function to replace the matches rather than a plain substitution. I don’t know what the penalty of this is.

Obviously this is not more readable than your current example but if you generalize it you can likely have a function like this:

general_replace(body, [{"&", "&amp;"}, {"<", "&lt;"}, {">", "&gt;"}])

EDIT
Here is a the general substitution function:

  def substitute(data, subs) do
    match_list = :binary.matches(data, Map.keys(subs))
    IO.iodata_to_binary(do_replace(data, match_list, subs, 0))
  end

  def do_replace(h, [], _, n) do
    [ :binary.part(h, {n, byte_size(h) - n}) ]
  end
  def do_replace(h, [{a, b} | t ], subs, n) do
    [ :binary.part(h, {n, a - n}),
      Map.get(subs, :binary.part(h, {a, b}))
      | do_replace(h, t, subs, a  + b)
    ]
  end

It takes a body of text and a map where each key in the map will be replaced by its value.

7 Likes