The way to find and replace text in javascript from regex is like so:
"ab".replace(/./g, (match) => match + "&");
// outputs: a&b&
What is the equivalent function or functions in gdscript?
In Godot we use the RegEx
class, and it has a sub
method to replace matches, however it does not take a callback. Callable
s are relatively recent addition in Godot, and many APIs has not been updated to use them. That does not mean we can't do it.
Furthermore, we cannot specify global (g
) when compiling the RegEx
(Godot does not support the delimiter and modifier syntax). However the g
modifier means it will not stop on one match... Which is what search_all
will do. So we build our code on top of it.
The method search_all
returns an Array
of RegExMatch
from which we can get the start and end indexes of each match... To replace the text we can use get_slice
and build a new String
from the pieces (No, Godot does not have an equivalent of splice
). And since the replacement might not have the same length, the indexes would be wrong after the first replacement... So, we will keep track of an offset to make it work.
This is working Godot 4 code:
var subject := "ab"
var pattern := "."
var callback := func(text:String) -> String: return text + "&"
var regex := RegEx.new()
regex.compile(pattern)
var regex_matches := regex.search_all(subject)
var offset := 0
for regex_match in regex_matches:
var start := regex_match.get_start()
var end := regex_match.get_end()
var length := end - start
var text := subject.substr(start + offset, length)
var replacement := str(callback.call(text))
subject = (
subject.substr(0, start + offset)
+ replacement
+ subject.substr(end + offset)
)
offset += replacement.length() - text.length()
prints(subject)
Output:
a&b&
For Godot 3, we cannot write an inline anonymous method like I did for callback
above. And Godot 3 didn't have typed Array
s, so we cannot infer the type of regex_match
thus some implicit typing won't work (I mean you would have to use =
instead of :=
).
Addendum:
For your case, you could use a capture group in your pattern (i.e. "(.)"
) so you can use a backreference in sub
. Which results in less code - assuming you can express the replacement you want in this form.
This is working code (it should work in both Godot 3 and Godot 4):
var subject := "ab"
var pattern := "(.)"
var regex = RegEx.new()
regex.compile(pattern)
subject = regex.sub(subject, "$1&", true)
prints(subject)
Output:
a&b&
Note that I'm passing true
as third parameter of sub
, that is telling it to replace all matches, without it the output is a&b
(i.e. it only replaces the first match).
Based off the awesome answer by @Theraot, here's a utility function which can be added to an autoload script.
func str_replace(target: String, regex: RegEx, cb: Callable) -> String:
var out = ""
var last_pos = 0
for regex_match in regex.search_all(target):
var start := regex_match.get_start()
out += target.substr(last_pos, start - last_pos)
out += str(cb.call(regex_match.get_string()))
last_pos = regex_match.get_end()
out += target.substr(last_pos)
return out
Usage:
# outputs: "alad"
print(Globals.str_replace(
"abcd",
RegEx.create_from_string("bc"),
func(text: String): return "la"
))
static func
and put on a script with a class_name
.
\$\endgroup\$
sub
method. What do you need that isn't already documented there? \$\endgroup\$