11use std:: {
22 collections:: HashSet ,
3- fmt:: { Debug , Formatter } ,
3+ fmt:: { Debug , Formatter , Write } ,
44} ;
55
66use bimap:: BiMap ;
77use index_vec:: { IndexSlice , IndexVec } ;
88use itertools:: Itertools ;
99use regex:: Regex ;
10- use serde:: Deserialize ;
1110
1211use crate :: {
1312 char_set:: { self , CharSet } ,
@@ -195,13 +194,20 @@ impl Stringy {
195194 pub fn create_display_string ( & self , contents : & str ) -> String {
196195 let mut display_str = String :: with_capacity ( contents. len ( ) + 20 ) ;
197196 display_str. push_str ( & self . delim_start ) ;
198- for c in contents. chars ( ) {
199- // TODO: handle escaping
200- display_str. push ( c) ;
197+ for ch in contents. chars ( ) {
198+ self . write_escaped_char ( ch, & mut display_str) ;
201199 }
202200 display_str. push_str ( & self . delim_end ) ;
203201 display_str
204202 }
203+
204+ /// Writes the escaped version of `c` to a [`String`]
205+ pub fn write_escaped_char ( & self , ch : char , out : & mut String ) {
206+ match & self . escape_rules {
207+ Some ( r) => r. write_escaped_char ( ch, out) ,
208+ None => out. push ( ch) ,
209+ }
210+ }
205211}
206212
207213/// The [`Regex`]es required to specify the valid strings of a [`Stringy`] node
@@ -212,29 +218,49 @@ pub struct Regexes {
212218 pub ( crate ) anchored_both : Regex ,
213219}
214220
215- # [ derive ( Debug , Clone , Deserialize ) ]
216- #[ serde ( deny_unknown_fields ) ]
221+ /// Rules for how `char`s should be escaped
222+ #[ derive ( Debug , Clone ) ]
217223pub struct EscapeRules {
218224 /// A non-empty string that all escape sequences must start with. For example, in JSON strings
219225 /// this is `\`
220226 pub ( crate ) start_sequence : String ,
221- /// Maps escape sequences ( to go after `start_sequence`) to the de-escaped [`String`]. For
222- /// example, for JSON strings this is:
227+ /// Maps chars to their escape sequence (i.e. the string which goes after
228+ /// `self.start_sequence`). For example, for JSON strings this is:
223229 /// ```text
224- /// `\` -> '\\' (i.e. `\\` de-escapes to `\`)
225- /// `"` -> '"' (i.e. `\"` de-escapes to `"`)
226- /// `/` -> '/'
227- /// `n` -> '\n'
228- /// `t` -> '\t'
229- /// `b` -> '\u{8}'
230- /// `f` -> '\u{c}'
231- /// `r` -> '\r'
230+ /// '\\' < -> "\\" (i.e. `\\` de-escapes to `\`)
231+ /// '"' < -> "\"" (i.e. `\"` de-escapes to `"`)
232+ /// '/' < -> "/"
233+ /// '\n' < -> "n"
234+ /// '\t' < -> "t"
235+ /// '\u{8}' <-> "b"
236+ /// '\u{c}' <-> "f"
237+ /// '\r' < -> "r"
232238 /// ```
233- pub ( crate ) rules : BiMap < String , String > ,
239+ pub ( crate ) rules : BiMap < char , String > ,
234240 /// The prefix which takes 4 hex symbols and de-escapes them to that unicode code-point. For
235241 /// example, in JSON strings this is `u` (i.e. `\uABCD` would turn into the unicode code point
236242 /// `0xABCD`).
237243 pub ( crate ) unicode_hex_4 : Option < String > ,
244+ /// A set of `char`s which should not be escaped, even if a rule for them exists.
245+ pub ( crate ) dont_escape : CharSet ,
246+ }
247+
248+ impl EscapeRules {
249+ fn write_escaped_char ( & self , ch : char , out : & mut String ) {
250+ if self . dont_escape . contains ( ch) {
251+ // Char escaping overriden by `self.dont_escape`
252+ out. push ( ch) ;
253+ } else if let Some ( escape_seq) = self . rules . get_by_left ( & ch) {
254+ // Char can be escaped using a specific escape sequence
255+ out. push_str ( & self . start_sequence ) ;
256+ out. push_str ( escape_seq) ;
257+ } else if let Some ( unicode_hex_4_prefix) = & self . unicode_hex_4 {
258+ // If 4-digit hex encoding is defined, then it's valid for all chars
259+ out. push_str ( & self . start_sequence ) ;
260+ out. push_str ( unicode_hex_4_prefix) ;
261+ write ! ( out, "{:04X}" , ch as u32 ) . unwrap ( ) ;
262+ }
263+ }
238264}
239265
240266//////////////
0 commit comments