@@ -1278,8 +1278,13 @@ defmodule String do
1278
1278
Returns a new string created by replacing occurrences of `pattern` in
1279
1279
`subject` with `replacement`.
1280
1280
1281
+ The `subject` is always a string.
1282
+
1281
1283
The `pattern` may be a string, a regular expression, or a compiled pattern.
1282
1284
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
+
1283
1288
By default it replaces all occurrences but this behaviour can be controlled
1284
1289
through the `:global` option; see the "Options" section below.
1285
1290
@@ -1289,12 +1294,6 @@ defmodule String do
1289
1294
with `replacement`, otherwise only the first occurrence is
1290
1295
replaced. Defaults to `true`
1291
1296
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
-
1298
1297
## Examples
1299
1298
1300
1299
iex> String.replace("a,b,c", ",", "-")
@@ -1303,6 +1302,12 @@ defmodule String do
1303
1302
iex> String.replace("a,b,c", ",", "-", global: false)
1304
1303
"a-b,c"
1305
1304
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
+
1306
1311
When the pattern is a regular expression, one can give `\N` or
1307
1312
`\g{N}` in the `replacement` string to access a specific capture in the
1308
1313
regular expression:
@@ -1315,25 +1320,11 @@ defmodule String do
1315
1320
giving `\0`, one can inject the whole matched pattern in the replacement
1316
1321
string.
1317
1322
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
-
1332
1323
A compiled pattern can also be given:
1333
1324
1334
1325
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"
1337
1328
1338
1329
When an empty string is provided as a `pattern`, the function will treat it as
1339
1330
an implicit empty string between each grapheme and the string will be
@@ -1347,43 +1338,89 @@ defmodule String do
1347
1338
"ELIXIR"
1348
1339
1349
1340
"""
1350
- @ spec replace ( t , pattern | Regex . t ( ) , t , keyword ) :: t
1341
+ @ spec replace ( t , pattern | Regex . t ( ) , t | ( t -> t | iodata ) , keyword ) :: t
1351
1342
def replace ( subject , pattern , replacement , options \\ [ ] )
1352
- def replace ( subject , "" , "" , _ ) , do: subject
1353
1343
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
1355
1355
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 ) ] )
1357
1357
else
1358
1358
replacement <> subject
1359
1359
end
1360
1360
end
1361
1361
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 ) ] )
1365
1366
else
1366
- opts = translate_replace_options ( options )
1367
- :binary . replace ( subject , pattern , replacement , opts )
1367
+ IO . iodata_to_binary ( [ replacement . ( "" ) | subject ] )
1368
1368
end
1369
1369
end
1370
1370
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
1372
1403
case next_grapheme ( subject ) do
1373
- { current , rest } -> [ current , replacement | intersperse ( rest , replacement ) ]
1404
+ { current , rest } -> [ current , replacement . ( "" ) | intersperse_fun ( rest , replacement ) ]
1374
1405
nil -> [ ]
1375
1406
end
1376
1407
end
1377
1408
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 )
1380
1415
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
1385
1422
1386
- global ++ insert
1423
+ [ prefix , middle | do_replace ( subject , matches , replacement , start + length ) ]
1387
1424
end
1388
1425
1389
1426
@ doc ~S"""
0 commit comments