diff --git a/NEWS.md b/NEWS.md index eaebcb398bee7..c619d63364620 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,8 @@ Julia v1.7 Release Notes New language features --------------------- * `count` and `findall` now accept an `AbstractChar` argument to search for a character in a string ([#38675]). +* `escape_string` can now receive a collection of characters in the keyword + `keep` that are to be kept as they are. ([#38597]) Language changes ---------------- diff --git a/base/strings/io.jl b/base/strings/io.jl index dda567414f073..1ce005bcb4d2a 100644 --- a/base/strings/io.jl +++ b/base/strings/io.jl @@ -310,8 +310,8 @@ escape_nul(c::Union{Nothing, AbstractChar}) = (c !== nothing && '0' <= c <= '7') ? "\\x00" : "\\0" """ - escape_string(str::AbstractString[, esc])::AbstractString - escape_string(io, str::AbstractString[, esc::])::Nothing + escape_string(str::AbstractString[, esc]; keep = ())::AbstractString + escape_string(io, str::AbstractString[, esc]; keep = ())::Nothing General escaping of traditional C and Unicode escape sequences. The first form returns the escaped string, the second prints the result to `io`. @@ -323,11 +323,20 @@ unambiguous), unicode code point (`"\\u"` prefix) or hex (`"\\x"` prefix). The optional `esc` argument specifies any additional characters that should also be escaped by a prepending backslash (`\"` is also escaped by default in the first form). +The argument `keep` specifies a collection of characters which are to be kept as +they are. Notice that `esc` has precedence here. + +!!! compat "Julia 1.7" + The `keep` argument is available as of Julia 1.7. + # Examples ```jldoctest julia> escape_string("aaa\\nbbb") "aaa\\\\nbbb" +julia> escape_string("aaa\\nbbb"; keep = '\\n') +"aaa\\nbbb" + julia> escape_string("\\xfe\\xff") # invalid utf-8 "\\\\xfe\\\\xff" @@ -341,11 +350,13 @@ julia> escape_string(string('\\u2135','\\0','0')) # \\0 would be ambiguous ## See also [`unescape_string`](@ref) for the reverse operation. """ -function escape_string(io::IO, s::AbstractString, esc="") +function escape_string(io::IO, s::AbstractString, esc=""; keep = ()) a = Iterators.Stateful(s) for c::AbstractChar in a if c in esc print(io, '\\', c) + elseif c in keep + print(io, c) elseif isascii(c) c == '\0' ? print(io, escape_nul(peek(a)::Union{AbstractChar,Nothing})) : c == '\e' ? print(io, "\\e") : @@ -368,7 +379,8 @@ function escape_string(io::IO, s::AbstractString, esc="") end end -escape_string(s::AbstractString, esc=('\"',)) = sprint(escape_string, s, esc, sizehint=lastindex(s)) +escape_string(s::AbstractString, esc=('\"',); keep = ()) = + sprint((io)->escape_string(io, s, esc; keep = keep), sizehint=lastindex(s)) function print_quoted(io, s::AbstractString) print(io, '"') diff --git a/test/strings/io.jl b/test/strings/io.jl index 9fd36d565408e..210503bb65832 100644 --- a/test/strings/io.jl +++ b/test/strings/io.jl @@ -64,6 +64,13 @@ @test typeof(escape_string("test", "t")) == String @test escape_string("test", "t") == "\\tes\\t" + @test escape_string("\\cdot") == "\\\\cdot" + @test escape_string("\\cdot"; keep = '\\') == "\\cdot" + @test escape_string("\\cdot", '\\'; keep = '\\') == "\\\\cdot" + @test escape_string("\\cdot\n"; keep = "\\\n") == "\\cdot\n" + @test escape_string("\\cdot\n", '\n'; keep = "\\\n") == "\\cdot\\\n" + @test escape_string("\\cdot\n", "\\\n"; keep = "\\\n") == "\\\\cdot\\\n" + for i = 1:size(cx,1) cp, ch, st = cx[i,:] @test cp == convert(UInt32, ch)