@@ -1278,8 +1278,13 @@ defmodule String do
12781278 Returns a new string created by replacing occurrences of `pattern` in
12791279 `subject` with `replacement`.
12801280
1281+ The `subject` is always a string.
1282+
12811283 The `pattern` may be a string, a regular expression, or a compiled pattern.
12821284
1285+ The `replacement` may be a string or a function that receives the matched
1286+ pattern and must return the replacement as a string or iodata.
1287+
12831288 By default it replaces all occurrences but this behaviour can be controlled
12841289 through the `:global` option; see the "Options" section below.
12851290
@@ -1289,12 +1294,6 @@ defmodule String do
12891294 with `replacement`, otherwise only the first occurrence is
12901295 replaced. Defaults to `true`
12911296
1292- * `:insert_replaced` - (integer or list of integers) specifies the position
1293- where to insert the replaced part inside the `replacement`. If any
1294- position given in the `:insert_replaced` option is larger than the
1295- replacement string, or is negative, an `ArgumentError` is raised. See the
1296- examples below
1297-
12981297 ## Examples
12991298
13001299 iex> String.replace("a,b,c", ",", "-")
@@ -1303,6 +1302,12 @@ defmodule String do
13031302 iex> String.replace("a,b,c", ",", "-", global: false)
13041303 "a-b,c"
13051304
1305+ The pattern may also be a list of strings and the replacement may also
1306+ be a function that receives the matched patterns:
1307+
1308+ iex> String.replace("a,b,c", ["a", "c"], fn <<char>> -> <<char + 1>> end)
1309+ "b,b,d"
1310+
13061311 When the pattern is a regular expression, one can give `\N` or
13071312 `\g{N}` in the `replacement` string to access a specific capture in the
13081313 regular expression:
@@ -1315,25 +1320,11 @@ defmodule String do
13151320 giving `\0`, one can inject the whole matched pattern in the replacement
13161321 string.
13171322
1318- When the pattern is a string, a developer can use the replaced part inside
1319- the `replacement` by using the `:insert_replaced` option and specifying the
1320- position(s) inside the `replacement` where the string pattern will be
1321- inserted:
1322-
1323- iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
1324- "a,[b],c"
1325-
1326- iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
1327- "a[],b[],c"
1328-
1329- iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
1330- "a[,,]b[,,]c"
1331-
13321323 A compiled pattern can also be given:
13331324
13341325 iex> pattern = :binary.compile_pattern(",")
1335- iex> String.replace("a,b,c", pattern, "[]", insert_replaced: 2 )
1336- "a[], b[], c"
1326+ iex> String.replace("a,b,c", pattern, "[]")
1327+ "a[]b[]c"
13371328
13381329 When an empty string is provided as a `pattern`, the function will treat it as
13391330 an implicit empty string between each grapheme and the string will be
@@ -1347,43 +1338,89 @@ defmodule String do
13471338 "ELIXIR"
13481339
13491340 """
1350- @ spec replace ( t , pattern | Regex . t ( ) , t , keyword ) :: t
1341+ @ spec replace ( t , pattern | Regex . t ( ) , t | ( t -> t | iodata ) , keyword ) :: t
13511342 def replace ( subject , pattern , replacement , options \\ [ ] )
1352- def replace ( subject , "" , "" , _ ) , do: subject
13531343
1354- def replace ( subject , "" , replacement , options ) do
1344+ def replace ( subject , % { __struct__: Regex } = regex , replacement , options )
1345+ when is_binary ( replacement ) or is_function ( replacement , 1 ) do
1346+ Regex . replace ( regex , subject , replacement , options )
1347+ end
1348+
1349+ def replace ( subject , "" , "" , _ ) when is_binary ( subject ) do
1350+ subject
1351+ end
1352+
1353+ def replace ( subject , "" , replacement , options )
1354+ when is_binary ( subject ) and is_binary ( replacement ) do
13551355 if Keyword . get ( options , :global , true ) do
1356- IO . iodata_to_binary ( [ replacement | intersperse ( subject , replacement ) ] )
1356+ IO . iodata_to_binary ( [ replacement | intersperse_bin ( subject , replacement ) ] )
13571357 else
13581358 replacement <> subject
13591359 end
13601360 end
13611361
1362- def replace ( subject , pattern , replacement , options ) when is_binary ( replacement ) do
1363- if Regex . regex? ( pattern ) do
1364- Regex . replace ( pattern , subject , replacement , global: options [ :global ] )
1362+ def replace ( subject , "" , replacement , options )
1363+ when is_binary ( subject ) and is_function ( replacement , 1 ) do
1364+ if Keyword . get ( options , :global , true ) do
1365+ IO . iodata_to_binary ( [ replacement . ( "" ) | intersperse_fun ( subject , replacement ) ] )
13651366 else
1366- opts = translate_replace_options ( options )
1367- :binary . replace ( subject , pattern , replacement , opts )
1367+ IO . iodata_to_binary ( [ replacement . ( "" ) | subject ] )
13681368 end
13691369 end
13701370
1371- defp intersperse ( subject , replacement ) do
1371+ def replace ( subject , pattern , replacement , options ) when is_binary ( subject ) do
1372+ if insert = Keyword . get ( options , :insert_replaced ) do
1373+ IO . warn (
1374+ "String.replace/4 with :insert_replaced option is deprecated. " <>
1375+ "Please use :binary.replace/4 instead or pass an anonymous function as replacement"
1376+ )
1377+
1378+ binary_options = if Keyword . get ( options , :global ) != false , do: [ :global ] , else: [ ]
1379+ :binary . replace ( subject , pattern , replacement , [ insert_replaced: insert ] ++ binary_options )
1380+ else
1381+ matches =
1382+ if Keyword . get ( options , :global , true ) do
1383+ :binary . matches ( subject , pattern )
1384+ else
1385+ case :binary . match ( subject , pattern ) do
1386+ :nomatch -> [ ]
1387+ match -> [ match ]
1388+ end
1389+ end
1390+
1391+ IO . iodata_to_binary ( do_replace ( subject , matches , replacement , 0 ) )
1392+ end
1393+ end
1394+
1395+ defp intersperse_bin ( subject , replacement ) do
1396+ case next_grapheme ( subject ) do
1397+ { current , rest } -> [ current , replacement | intersperse_bin ( rest , replacement ) ]
1398+ nil -> [ ]
1399+ end
1400+ end
1401+
1402+ defp intersperse_fun ( subject , replacement ) do
13721403 case next_grapheme ( subject ) do
1373- { current , rest } -> [ current , replacement | intersperse ( rest , replacement ) ]
1404+ { current , rest } -> [ current , replacement . ( "" ) | intersperse_fun ( rest , replacement ) ]
13741405 nil -> [ ]
13751406 end
13761407 end
13771408
1378- defp translate_replace_options ( options ) do
1379- global = if Keyword . get ( options , :global ) != false , do: [ :global ] , else: [ ]
1409+ defp do_replace ( subject , [ ] , _ , n ) do
1410+ [ binary_part ( subject , n , byte_size ( subject ) - n ) ]
1411+ end
1412+
1413+ defp do_replace ( subject , [ { start , length } | matches ] , replacement , n ) do
1414+ prefix = binary_part ( subject , n , start - n )
13801415
1381- insert =
1382- if insert = Keyword . get ( options , :insert_replaced ) ,
1383- do: [ { :insert_replaced , insert } ] ,
1384- else: [ ]
1416+ middle =
1417+ if is_binary ( replacement ) do
1418+ replacement
1419+ else
1420+ replacement . ( binary_part ( subject , start , length ) )
1421+ end
13851422
1386- global ++ insert
1423+ [ prefix , middle | do_replace ( subject , matches , replacement , start + length ) ]
13871424 end
13881425
13891426 @ doc ~S"""
0 commit comments