@@ -308,7 +308,7 @@ defmodule Regex do
308
308
309
309
## Options
310
310
311
- * `:parts` - when specified, splits the string into the
311
+ * `:parts` - when specified, splits the string into the
312
312
given number of parts. If not specified, `:parts`
313
313
is defaulted to `:infinity`, which will split the
314
314
string into the maximum number of parts possible
@@ -362,10 +362,20 @@ defmodule Regex do
362
362
Receives a regex, a binary and a replacement, returns a new
363
363
binary where the all matches are replaced by replacement.
364
364
365
- Inside the replacement, you can either give `&` to access the
366
- whole regular expression or `\N`, where `N` is in integer to access
367
- a specific matching parens. You can also set `:global` to `false`
368
- if you want to replace just the first occurrence.
365
+ The replacement can be either a string or a function. The string
366
+ is used as a replacement for every match and it allows specific
367
+ captures to be accessed via `\N`, where `N` is the capture. In
368
+ case `\0` is used, the whole match is inserted.
369
+
370
+ When the replacement is a function, the function may have arity
371
+ N where each argument maps to a capture, with the first argument
372
+ being the whole match. If the function expects more arguments
373
+ than captures found, the remaining arguments will receive `""`.
374
+
375
+ ## Options
376
+
377
+ * `:global` - when `false`, replaces only the first occurrence
378
+ (defaults to true)
369
379
370
380
## Examples
371
381
@@ -375,22 +385,142 @@ defmodule Regex do
375
385
iex> Regex.replace(~r/b/, "abc", "d")
376
386
"adc"
377
387
378
- iex> Regex.replace(~r/b/, "abc", "[& ]")
388
+ iex> Regex.replace(~r/b/, "abc", "[\\0 ]")
379
389
"a[b]c"
380
390
381
- iex> Regex.replace(~r/b /, "abc ", "[\\& ]")
382
- "a[&]c "
391
+ iex> Regex.replace(~r/a(b|d)c /, "abcadc ", "[\\1 ]")
392
+ "[b][d] "
383
393
384
- iex> Regex.replace(~r/(b) /, "abc ", "[\\1]" )
385
- "a [b]c "
394
+ iex> Regex.replace(~r/a(b|d)c /, "abcadc ", fn _, x -> "[#{x}]" end )
395
+ "[b][d] "
386
396
387
397
"""
388
398
def replace ( regex , string , replacement , options \\ [ ] )
389
399
390
- def replace ( % Regex { re_pattern: compiled } , string , replacement , options ) when is_binary ( string ) do
400
+ def replace ( regex , string , replacement , options ) when is_binary ( replacement ) do
401
+ do_replace ( regex , string , precompile_replacement ( replacement ) , options )
402
+ end
403
+
404
+ def replace ( regex , string , replacement , options ) when is_function ( replacement ) do
405
+ { :arity , arity } = :erlang . fun_info ( replacement , :arity )
406
+ do_replace ( regex , string , { replacement , arity } , options )
407
+ end
408
+
409
+ defp do_replace ( % Regex { re_pattern: compiled } , string , replacement , options ) do
391
410
opts = if Keyword . get ( options , :global ) != false , do: [ :global ] , else: [ ]
392
- opts = [ { :return , :binary } | opts ]
393
- :re . replace ( string , compiled , replacement , opts )
411
+ opts = [ { :capture , :all , :index } | opts ]
412
+
413
+ case :re . run ( string , compiled , opts ) do
414
+ :nomatch ->
415
+ string
416
+ { :match , [ mlist | t ] } when is_list ( mlist ) ->
417
+ apply_list ( string , replacement , [ mlist | t ] ) |> iodata_to_binary
418
+ { :match , slist } ->
419
+ apply_list ( string , replacement , [ slist ] ) |> iodata_to_binary
420
+ end
421
+ end
422
+
423
+ defp precompile_replacement ( "" ) ,
424
+ do: [ ]
425
+
426
+ defp precompile_replacement ( << ?\\ , x , rest :: binary >> ) when x < ?0 or x > ?9 do
427
+ case precompile_replacement ( rest ) do
428
+ [ head | t ] when is_binary ( head ) ->
429
+ [ << x , head :: binary >> | t ]
430
+ other ->
431
+ [ << x >> | other ]
432
+ end
433
+ end
434
+
435
+ defp precompile_replacement ( << ?\\ , rest :: binary >> ) when byte_size ( rest ) > 0 do
436
+ { ns , rest } = pick_int ( rest )
437
+ [ list_to_integer ( ns ) | precompile_replacement ( rest ) ]
438
+ end
439
+
440
+ defp precompile_replacement ( << x , rest :: binary >> ) do
441
+ case precompile_replacement ( rest ) do
442
+ [ head | t ] when is_binary ( head ) ->
443
+ [ << x , head :: binary >> | t ]
444
+ other ->
445
+ [ << x >> | other ]
446
+ end
447
+ end
448
+
449
+ defp pick_int ( << x , rest :: binary >> ) when x in ?0 .. ?9 do
450
+ { found , rest } = pick_int ( rest )
451
+ { [ x | found ] , rest }
452
+ end
453
+
454
+ defp pick_int ( bin ) do
455
+ { [ ] , bin }
456
+ end
457
+
458
+ defp apply_list ( string , replacement , list ) do
459
+ apply_list ( string , string , 0 , replacement , list )
460
+ end
461
+
462
+ defp apply_list ( _ , "" , _ , _ , [ ] ) do
463
+ [ ]
464
+ end
465
+
466
+ defp apply_list ( _ , string , _ , _ , [ ] ) do
467
+ string
468
+ end
469
+
470
+ defp apply_list ( whole , string , pos , replacement , [ [ { mpos , _ } | _ ] | _ ] = list ) when mpos > pos do
471
+ length = mpos - pos
472
+ << untouched :: [ size ( length ) , binary ] , rest :: binary >> = string
473
+ [ untouched | apply_list ( whole , rest , mpos , replacement , list ) ]
474
+ end
475
+
476
+ defp apply_list ( whole , string , pos , replacement , [ [ { mpos , length } | _ ] = head | tail ] ) when mpos == pos do
477
+ << _ :: [ size ( length ) , binary ] , rest :: binary >> = string
478
+ new_data = apply_replace ( whole , replacement , head )
479
+ [ new_data | apply_list ( whole , rest , pos + length , replacement , tail ) ]
480
+ end
481
+
482
+ defp apply_replace ( string , { fun , arity } , indexes ) do
483
+ apply ( fun , get_indexes ( string , indexes , arity ) )
484
+ end
485
+
486
+ defp apply_replace ( _ , [ bin ] , _ ) when is_binary ( bin ) do
487
+ bin
488
+ end
489
+
490
+ defp apply_replace ( string , repl , indexes ) do
491
+ indexes = list_to_tuple ( indexes )
492
+
493
+ for part <- repl do
494
+ cond do
495
+ is_binary ( part ) ->
496
+ part
497
+ part > tuple_size ( indexes ) ->
498
+ ""
499
+ true ->
500
+ get_index ( string , elem ( indexes , part ) )
501
+ end
502
+ end
503
+ end
504
+
505
+ defp get_index ( _string , { pos , _len } ) when pos < 0 do
506
+ ""
507
+ end
508
+
509
+ defp get_index ( string , { pos , len } ) do
510
+ << _ :: [ size ( pos ) , binary ] , res :: [ size ( len ) , binary ] , _ :: binary >> = string
511
+ res
512
+ end
513
+
514
+ defp get_indexes ( _string , _ , 0 ) do
515
+ [ ]
516
+ end
517
+
518
+ defp get_indexes ( string , [ ] , arity ) do
519
+ [ "" | get_indexes ( string , [ ] , arity - 1 ) ]
520
+ end
521
+
522
+ defp get_indexes ( string , [ h | t ] , arity ) do
523
+ [ get_index ( string , h ) | get_indexes ( string , t , arity - 1 ) ]
394
524
end
395
525
396
526
{ :ok , pattern } = :re . compile ( ~S" [.^$*+?()[{\\\|\s#]" , [ :unicode ] )
0 commit comments