1: <?php declare(strict_types=1);
2:
3: namespace PhpParser\Node\Scalar;
4:
5: use PhpParser\Error;
6: use PhpParser\Node\Scalar;
7:
8: class String_ extends Scalar
9: {
10: /* For use in "kind" attribute */
11: const KIND_SINGLE_QUOTED = 1;
12: const KIND_DOUBLE_QUOTED = 2;
13: const KIND_HEREDOC = 3;
14: const KIND_NOWDOC = 4;
15:
16: /** @var string String value */
17: public $value;
18:
19: protected static $replacements = [
20: '\\' => '\\',
21: '$' => '$',
22: 'n' => "\n",
23: 'r' => "\r",
24: 't' => "\t",
25: 'f' => "\f",
26: 'v' => "\v",
27: 'e' => "\x1B",
28: ];
29:
30: /**
31: * Constructs a string scalar node.
32: *
33: * @param string $value Value of the string
34: * @param array $attributes Additional attributes
35: */
36: public function __construct(string $value, array $attributes = []) {
37: $this->attributes = $attributes;
38: $this->value = $value;
39: }
40:
41: public function getSubNodeNames() : array {
42: return ['value'];
43: }
44:
45: /**
46: * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
47: */
48: public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self
49: {
50: $attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B')))
51: ? Scalar\String_::KIND_SINGLE_QUOTED
52: : Scalar\String_::KIND_DOUBLE_QUOTED;
53:
54: $attributes['rawValue'] = $str;
55:
56: $string = self::parse($str, $parseUnicodeEscape);
57:
58: return new self($string, $attributes);
59: }
60:
61: /**
62: * @internal
63: *
64: * Parses a string token.
65: *
66: * @param string $str String token content
67: * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
68: *
69: * @return string The parsed string
70: */
71: public static function parse(string $str, bool $parseUnicodeEscape = true) : string {
72: $bLength = 0;
73: if ('b' === $str[0] || 'B' === $str[0]) {
74: $bLength = 1;
75: }
76:
77: if ('\'' === $str[$bLength]) {
78: return str_replace(
79: ['\\\\', '\\\''],
80: ['\\', '\''],
81: substr($str, $bLength + 1, -1)
82: );
83: } else {
84: return self::parseEscapeSequences(
85: substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape
86: );
87: }
88: }
89:
90: /**
91: * @internal
92: *
93: * Parses escape sequences in strings (all string types apart from single quoted).
94: *
95: * @param string $str String without quotes
96: * @param null|string $quote Quote type
97: * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
98: *
99: * @return string String with escape sequences parsed
100: */
101: public static function parseEscapeSequences(string $str, $quote, bool $parseUnicodeEscape = true) : string {
102: if (null !== $quote) {
103: $str = str_replace('\\' . $quote, $quote, $str);
104: }
105:
106: $extra = '';
107: if ($parseUnicodeEscape) {
108: $extra = '|u\{([0-9a-fA-F]+)\}';
109: }
110:
111: return preg_replace_callback(
112: '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~',
113: function($matches) {
114: $str = $matches[1];
115:
116: if (isset(self::$replacements[$str])) {
117: return self::$replacements[$str];
118: } elseif ('x' === $str[0] || 'X' === $str[0]) {
119: return chr(hexdec(substr($str, 1)));
120: } elseif ('u' === $str[0]) {
121: return self::codePointToUtf8(hexdec($matches[2]));
122: } else {
123: return chr(octdec($str));
124: }
125: },
126: $str
127: );
128: }
129:
130: /**
131: * Converts a Unicode code point to its UTF-8 encoded representation.
132: *
133: * @param int $num Code point
134: *
135: * @return string UTF-8 representation of code point
136: */
137: private static function codePointToUtf8(int $num) : string {
138: if ($num <= 0x7F) {
139: return chr($num);
140: }
141: if ($num <= 0x7FF) {
142: return chr(($num>>6) + 0xC0) . chr(($num&0x3F) + 0x80);
143: }
144: if ($num <= 0xFFFF) {
145: return chr(($num>>12) + 0xE0) . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
146: }
147: if ($num <= 0x1FFFFF) {
148: return chr(($num>>18) + 0xF0) . chr((($num>>12)&0x3F) + 0x80)
149: . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
150: }
151: throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large');
152: }
153:
154: public function getType() : string {
155: return 'Scalar_String';
156: }
157: }
158: