|
5 | 5 | * |
6 | 6 | */ |
7 | 7 | #include "stringutils.h" |
| 8 | +#include <array> |
8 | 9 | #include <cassert> |
9 | 10 | #include <climits> |
| 11 | +#include <cstdint> |
10 | 12 | #include <cstring> |
11 | 13 | #include <initializer_list> |
| 14 | +#include <limits> |
12 | 15 | #include <optional> |
13 | 16 | #include <string> |
14 | 17 | #include <string_view> |
@@ -78,6 +81,38 @@ std::string concatPathPieces( |
78 | 81 | } |
79 | 82 | } // namespace details |
80 | 83 |
|
| 84 | +namespace { |
| 85 | + |
| 86 | +constexpr std::array<char, std::numeric_limits<uint8_t>::max()> escapeMap = |
| 87 | + []() consteval { |
| 88 | + std::array<char, std::numeric_limits<uint8_t>::max()> table{}; |
| 89 | + table.fill('\0'); |
| 90 | + table['\\'] = '\\'; |
| 91 | + table['"'] = '"'; |
| 92 | + table['\n'] = 'n'; |
| 93 | + table['\f'] = 'f'; |
| 94 | + table['\r'] = 'r'; |
| 95 | + table['\t'] = 't'; |
| 96 | + table['\v'] = 'v'; |
| 97 | + return table; |
| 98 | + }(); |
| 99 | + |
| 100 | +constexpr std::array<char, std::numeric_limits<uint8_t>::max()> unescapeMap = |
| 101 | + []() consteval { |
| 102 | + std::array<char, std::numeric_limits<uint8_t>::max()> table{}; |
| 103 | + table.fill('\0'); |
| 104 | + table['\\'] = '\\'; |
| 105 | + table['"'] = '"'; |
| 106 | + table['n'] = '\n'; |
| 107 | + table['f'] = '\f'; |
| 108 | + table['r'] = '\r'; |
| 109 | + table['t'] = '\t'; |
| 110 | + table['v'] = '\v'; |
| 111 | + return table; |
| 112 | + }(); |
| 113 | + |
| 114 | +} // namespace |
| 115 | + |
81 | 116 | FCITXUTILS_DEPRECATED_EXPORT bool startsWith(const std::string &str, |
82 | 117 | const std::string &prefix) { |
83 | 118 | return str.starts_with(prefix); |
@@ -337,69 +372,52 @@ bool unescape(std::string &str, bool unescapeQuote) { |
337 | 372 | } |
338 | 373 | break; |
339 | 374 | case UnescapeState::ESCAPE: |
340 | | - if (str[i] == '\\') { |
341 | | - str[j] = '\\'; |
342 | | - j++; |
343 | | - } else if (str[i] == 'n') { |
344 | | - str[j] = '\n'; |
| 375 | + if (auto c = unescapeMap[str[i]]; |
| 376 | + c && (unescapeQuote || c != '"')) { |
| 377 | + str[j] = c; |
345 | 378 | j++; |
346 | | - } else if (str[i] == '\"' && unescapeQuote) { |
347 | | - str[j] = '\"'; |
348 | | - j++; |
349 | | - } else { |
350 | | - return false; |
| 379 | + state = UnescapeState::NORMAL; |
| 380 | + break; |
351 | 381 | } |
352 | | - state = UnescapeState::NORMAL; |
353 | | - break; |
| 382 | + // invalid escape sequence |
| 383 | + return false; |
354 | 384 | } |
355 | 385 | } while (str[i++]); |
356 | 386 | str.resize(j - 1); |
357 | 387 | return true; |
358 | 388 | } |
359 | 389 |
|
360 | 390 | std::optional<std::string> unescapeForValue(std::string_view str) { |
361 | | - bool unescapeQuote = false; |
362 | 391 | // having quote at beginning and end, escape |
363 | 392 | if (str.size() >= 2 && str.front() == '"' && str.back() == '"') { |
364 | | - unescapeQuote = true; |
365 | | - str = str.substr(1, str.size() - 2); |
366 | | - } |
367 | | - if (str.empty()) { |
368 | | - return std::string(); |
369 | | - } |
370 | | - |
371 | | - std::string value(str); |
372 | | - if (!stringutils::unescape(value, unescapeQuote)) { |
| 393 | + std::string result; |
| 394 | + auto originLength = str.size(); |
| 395 | + auto consumed = consumeMaybeEscapedValue(str, "", &result); |
| 396 | + if (consumed.size() == originLength) { |
| 397 | + return result; |
| 398 | + } |
373 | 399 | return std::nullopt; |
374 | 400 | } |
375 | | - return value; |
| 401 | + return std::string{str}; |
376 | 402 | } |
377 | 403 |
|
378 | 404 | std::string escapeForValue(std::string_view str) { |
379 | 405 | std::string value; |
380 | 406 | value.reserve(str.size()); |
381 | | - const bool needQuote = |
382 | | - str.find_first_of("\f\r\t\v \"") != std::string::npos; |
383 | | - if (needQuote) { |
| 407 | + const bool needEscape = |
| 408 | + str.find_first_of("\f\r\t\v \"\\\n") != std::string::npos; |
| 409 | + if (needEscape) { |
384 | 410 | value.push_back('"'); |
385 | 411 | } |
386 | 412 | for (char c : str) { |
387 | | - switch (c) { |
388 | | - case '\\': |
389 | | - value.append("\\\\"); |
390 | | - break; |
391 | | - case '\n': |
392 | | - value.append("\\n"); |
393 | | - break; |
394 | | - case '"': |
395 | | - value.append("\\\""); |
396 | | - break; |
397 | | - default: |
| 413 | + if (auto escape = escapeMap[static_cast<uint8_t>(c)]) { |
| 414 | + value.push_back('\\'); |
| 415 | + value.push_back(escape); |
| 416 | + } else { |
398 | 417 | value.push_back(c); |
399 | | - break; |
400 | 418 | } |
401 | 419 | } |
402 | | - if (needQuote) { |
| 420 | + if (needEscape) { |
403 | 421 | value.push_back('"'); |
404 | 422 | } |
405 | 423 |
|
@@ -450,14 +468,12 @@ std::string_view consumeMaybeEscapedValue(std::string_view &input, |
450 | 468 | } |
451 | 469 | break; |
452 | 470 | case UnescapeState::ESCAPE: |
453 | | - if (input[i] == '\\') { |
454 | | - result.push_back('\\'); |
455 | | - } else if (input[i] == 'n') { |
456 | | - result.push_back('\n'); |
457 | | - } else if (input[i] == '"') { |
458 | | - result.push_back('"'); |
| 471 | + if (auto c = unescapeMap[input[i]]) { |
| 472 | + result.push_back(c); |
459 | 473 | } else { |
460 | | - break; |
| 474 | + // invalid escape sequence |
| 475 | + // and treat it as normal character. |
| 476 | + result.push_back(input[i]); |
461 | 477 | } |
462 | 478 | state = UnescapeState::NORMAL; |
463 | 479 | break; |
|
0 commit comments