1: <?php
2: declare(strict_types=1);
3:
4: namespace om\ICal\Value;
5:
6: /**
7: * TEXT values (RFC 5545, section 3.3.11): escaping of backslash, semicolon, comma and newline.
8: */
9: final class Text {
10: private const array UNESCAPE = ['\\\\' => '\\', '\\N' => "\n", '\\n' => "\n", '\;' => ';', '\\,' => ','];
11:
12: public static function unescape(string $value): string {
13: return strtr($value, self::UNESCAPE);
14: }
15:
16: public static function escape(string $value): string {
17: return strtr($value, ['\\' => '\\\\', "\r\n" => '\n', "\n" => '\n', ';' => '\;', ',' => '\,']);
18: }
19:
20: /**
21: * Split a list of TEXT values on commas that are not escaped, and unescape them.
22: * Empty items are dropped.
23: *
24: * @return list<string>
25: */
26: public static function split(string $value): array {
27: $result = [];
28: foreach (self::splitEscaped($value) as $item) {
29: $item = trim(self::unescape($item));
30: if ($item !== '') {
31: $result[] = $item;
32: }
33: }
34: return $result;
35: }
36:
37: /**
38: * Split on a separator that is not escaped by a backslash; the parts stay escaped.
39: * "a\\,b" (an escaped backslash before the comma) gives "a\\" and "b".
40: *
41: * @return list<string>
42: */
43: public static function splitEscaped(string $value, string $separator = ','): array {
44: $parts = [];
45: $current = '';
46: $length = strlen($value);
47: for ($i = 0; $i < $length; $i++) {
48: $char = $value[$i];
49: if ($char === '\\' && $i + 1 < $length) {
50: $current .= $char . $value[++$i];
51: } elseif ($char === $separator) {
52: $parts[] = $current;
53: $current = '';
54: } else {
55: $current .= $char;
56: }
57: }
58: $parts[] = $current;
59: return $parts;
60: }
61: }
62: