@@ -77,7 +77,7 @@ export const stringify = (config: Properties): string => {
77
77
*/
78
78
export function * list ( config : Properties ) : Generator < KeyValuePair > {
79
79
for ( const { key, rawValue} of listPairs ( config . lines ) ) {
80
- yield { key, value : unescapeValue ( rawValue ) }
80
+ yield { key, value : unescape ( rawValue ) }
81
81
}
82
82
}
83
83
@@ -91,7 +91,7 @@ export function* list(config: Properties): Generator<KeyValuePair> {
91
91
export const get = ( config : Properties , key : string ) : string | undefined => {
92
92
// Find existing
93
93
const { rawValue} = findValue ( config . lines , key )
94
- return typeof rawValue === 'string' ? unescapeValue ( rawValue ) : undefined
94
+ return typeof rawValue === 'string' ? unescape ( rawValue ) : undefined
95
95
}
96
96
97
97
/**
@@ -105,7 +105,7 @@ export const toMap = (config: Properties): Map<string, string> => {
105
105
const result = new Map < string , string > ( )
106
106
107
107
for ( const { key, rawValue} of listPairs ( config . lines ) ) {
108
- result . set ( key , unescapeValue ( rawValue ) )
108
+ result . set ( key , unescape ( rawValue ) )
109
109
}
110
110
111
111
return result
@@ -308,52 +308,105 @@ const unescapeChar = (c: string): string => {
308
308
}
309
309
}
310
310
311
- const unescapeValue = ( str : string ) : string =>
311
+ /**
312
+ * Unescape key or value.
313
+ *
314
+ * @param str Escaped string.
315
+ * @return Actual string.
316
+ */
317
+ export const unescape = ( str : string ) : string =>
312
318
str . replace ( / \\ ( .) / g, s => unescapeChar ( s [ 1 ] ) )
313
319
314
- // Very simple implementation, does not handle unicode etc
315
- const escapeValue = ( str : string , escapeChars = '' ) : string => {
320
+ /**
321
+ * Escape property key.
322
+ *
323
+ * @param unescapedKey Property key to be escaped.
324
+ * @param escapeUnicode Escape unicode chars (below 0x0020 and above 0x007e). Default is true.
325
+ * @return Escaped string.
326
+ */
327
+ export const escapeKey = ( unescapedKey : string , escapeUnicode = true ) : string => {
328
+ return escape ( unescapedKey , true , escapeUnicode )
329
+ }
330
+
331
+ /**
332
+ * Escape property value.
333
+ *
334
+ * @param unescapedValue Property value to be escaped.
335
+ * @param escapeUnicode Escape unicode chars (below 0x0020 and above 0x007e). Default is true.
336
+ * @return Escaped string.
337
+ */
338
+ export const escapeValue = ( unescapedValue : string , escapeUnicode = true ) : string => {
339
+ return escape ( unescapedValue , false , escapeUnicode )
340
+ }
341
+
342
+ /**
343
+ * Internal escape method.
344
+ *
345
+ * @param unescapedContent Text to be escaped.
346
+ * @param escapeSpace Whether all spaces should be escaped
347
+ * @param escapeUnicode Whether unicode chars should be escaped
348
+ * @return Escaped string.
349
+ */
350
+ const escape = (
351
+ unescapedContent : string ,
352
+ escapeSpace : boolean ,
353
+ escapeUnicode : boolean
354
+ ) : string => {
316
355
const result : string [ ] = [ ]
317
- let escapeNext = str . startsWith ( ' ' ) // always escape space at beginning
318
356
319
- for ( let index = 0 ; index < str . length ; index ++ ) {
320
- const char = str [ index ]
357
+ // eslint-disable-next-line unicorn/no-for-loop
358
+ for ( let index = 0 ; index < unescapedContent . length ; index ++ ) {
359
+ const char = unescapedContent [ index ]
321
360
switch ( char ) {
361
+ case ' ' : {
362
+ // Escape space if required, or if it is first character
363
+ if ( escapeSpace || index === 0 ) {
364
+ result . push ( '\\ ' )
365
+ } else {
366
+ result . push ( ' ' )
367
+ }
368
+ break
369
+ }
322
370
case '\\' : {
323
371
result . push ( '\\\\' )
324
372
break
325
373
}
326
374
case '\f' : {
327
- // Formfeed/
375
+ // Form-feed
328
376
result . push ( '\\f' )
329
377
break
330
378
}
331
379
case '\n' : {
332
- // Newline.
380
+ // Newline
333
381
result . push ( '\\n' )
334
382
break
335
383
}
336
384
case '\r' : {
337
- // Carriage return.
385
+ // Carriage return
338
386
result . push ( '\\r' )
339
387
break
340
388
}
341
389
case '\t' : {
342
- // Tab.
390
+ // Tab
343
391
result . push ( '\\t' )
344
392
break
345
393
}
394
+ case '=' : // Fall through
395
+ case ':' : // Fall through
396
+ case '#' : // Fall through
397
+ case '!' : {
398
+ result . push ( '\\' , char )
399
+ break
400
+ }
346
401
default : {
347
- // Escape trailing space
348
- if ( index === str . length - 1 && char === ' ' ) {
349
- escapeNext = true
402
+ if ( escapeUnicode ) {
403
+ const codePoint : number = char . codePointAt ( 0 ) as number // can never be undefined
404
+ if ( codePoint < 0x0020 || codePoint > 0x007e ) {
405
+ result . push ( '\\u' , codePoint . toString ( 16 ) . padStart ( 4 , '0' ) )
406
+ break
407
+ }
350
408
}
351
- // Escape if required
352
- if ( escapeNext || escapeChars . includes ( char ) ) {
353
- result . push ( '\\' )
354
- escapeNext = false
355
- }
356
-
409
+ // Normal char
357
410
result . push ( char )
358
411
break
359
412
}
@@ -362,5 +415,3 @@ const escapeValue = (str: string, escapeChars = ''): string => {
362
415
363
416
return result . join ( '' )
364
417
}
365
-
366
- const escapeKey = ( str : string ) : string => escapeValue ( str , ' #!:=' )
0 commit comments