String.replace with anonymous function with multiple matches

I wonder if I can ask a newbie question, maybe…I’m trying to replace

<source>SOMETHING</source>

with

<code><pre>SOMETHING</pre></code>

So I can replace the entire match

iex(14)> String.replace("<source>(aaaM)</source>", ~r/<source>(.*)<\/source>/, fn match-> "<code><pre>#{match}</pre></code>" end)

**"<code><pre><source>(aaaM)</source></pre></code>"** 

But I need access to the first matched element, so (aaaM). Partly because I also want to HTML Escape it as well. Ok, I need to use the match in anonymous function…but…sadly.

iex(15)> String.replace("<source>(aaaM)</source>", ~r/<source>(.*)<\/source>/, fn match, code-> "<code><pre>#{match}</pre></code>" end) 
warning: variable "code" is unused (if the variable is not meant to be used, prefix it with an underscore)
  iex:15

** (FunctionClauseError) no function clause matching in String.replace/4    
    
    The following arguments were given to String.replace/4:
    
        # 1
        "<source>(aaaM)</source>"
    
        # 2
        ~r/<source>(.*)<\/source>/
    
        # 3
        #Function<43.65746770/2 in :erl_eval.expr/5>
    
        # 4
        []
    
    Attempted function clauses (showing 1 out of 1):
    
        def replace(subject, pattern, replacement, options) when is_binary(subject) and is_binary(replacement) or is_function(replacement, 1) and is_list(options)
    
    (elixir 1.13.1) lib/string.ex:1477: String.replace/4

I’m not sure I follow, I’m now supplying 4 arguments. Even if I parenthesises the multiple arguments for the anonymous function. Anyone point out where I am being a dummy?

Many thanks

RobL

I’m not sure I fully understand what you want to do, but this example might give you a sense:

def replace_characters(string) do
    to_replace = ["&", "<", ">", "/", "'"]

    String.replace(string, to_replace, fn
      "&" -> "H"
      "<" -> "E"
      ">" -> "L"
      "/" -> "L"
      "'" -> "O"
    end)
  end
iex::1> Module.replace_characters("fdsfd &<>/' dsfds")                              
"fdsfd HELLO dsfds"

Anonimous function is arity 1, that’s why it complains.

1 Like

OK, that makes sense if the arity is 1. Is there no way to get access to the multiple matched elements in the regex if you use an anonymous function? I would like to HTML Escape the contents of the <source> tag

OK, looks like I got it. I needed Regex.replace rather than String.replace

  def standardize_code(body) do
    Regex.replace(~r/<source>(.*?)<\/source>/s, body, fn _, code  ->
      case Phoenix.HTML.html_escape(code) do
        {:safe, html} ->
          "<code><pre>#{html}</pre></code>"
        _ ->
          "NOT ITS NOT SAFE"
      end
    end)
  end

Hello, just a note. The function Phoenix.HTML.html_escape does always return {:safe, content} according to the spec. Because if it finds code, that needs to be escaped, it escapes it… That tuple is for phoenix rendering where you put this tuple into template, informing it that the given content is already escaped.
There is also {:raw, content} that tells phoenix that you insist on putting the content into final result as-is.

1 Like

There is the function raw/1, but that also just wraps data into a {:safe, …} tuple. There’s no :raw tagged tuple support afaik.

2 Likes

String.replace with a Regex uses Regex.replace under the hood, but the declaration of String.replace specifically narrows the allowed type of the function. Looks like it’s been that way since the feature was added:

Using Regex.replace directly should do what you want.

1 Like

Thank you all :slight_smile: Regex.replace was the thing thing,