1<?php declare(strict_types=1); 2 3namespace PhpParser\Node\Scalar; 4 5use PhpParser\Error; 6use PhpParser\Node\Scalar; 7 8class 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 * @internal 47 * 48 * Parses a string token. 49 * 50 * @param string $str String token content 51 * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes 52 * 53 * @return string The parsed string 54 */ 55 public static function parse(string $str, bool $parseUnicodeEscape = true) : string { 56 $bLength = 0; 57 if ('b' === $str[0] || 'B' === $str[0]) { 58 $bLength = 1; 59 } 60 61 if ('\'' === $str[$bLength]) { 62 return str_replace( 63 ['\\\\', '\\\''], 64 ['\\', '\''], 65 substr($str, $bLength + 1, -1) 66 ); 67 } else { 68 return self::parseEscapeSequences( 69 substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape 70 ); 71 } 72 } 73 74 /** 75 * @internal 76 * 77 * Parses escape sequences in strings (all string types apart from single quoted). 78 * 79 * @param string $str String without quotes 80 * @param null|string $quote Quote type 81 * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes 82 * 83 * @return string String with escape sequences parsed 84 */ 85 public static function parseEscapeSequences(string $str, $quote, bool $parseUnicodeEscape = true) : string { 86 if (null !== $quote) { 87 $str = str_replace('\\' . $quote, $quote, $str); 88 } 89 90 $extra = ''; 91 if ($parseUnicodeEscape) { 92 $extra = '|u\{([0-9a-fA-F]+)\}'; 93 } 94 95 return preg_replace_callback( 96 '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~', 97 function($matches) { 98 $str = $matches[1]; 99 100 if (isset(self::$replacements[$str])) { 101 return self::$replacements[$str]; 102 } elseif ('x' === $str[0] || 'X' === $str[0]) { 103 return chr(hexdec(substr($str, 1))); 104 } elseif ('u' === $str[0]) { 105 return self::codePointToUtf8(hexdec($matches[2])); 106 } else { 107 return chr(octdec($str)); 108 } 109 }, 110 $str 111 ); 112 } 113 114 /** 115 * Converts a Unicode code point to its UTF-8 encoded representation. 116 * 117 * @param int $num Code point 118 * 119 * @return string UTF-8 representation of code point 120 */ 121 private static function codePointToUtf8(int $num) : string { 122 if ($num <= 0x7F) { 123 return chr($num); 124 } 125 if ($num <= 0x7FF) { 126 return chr(($num>>6) + 0xC0) . chr(($num&0x3F) + 0x80); 127 } 128 if ($num <= 0xFFFF) { 129 return chr(($num>>12) + 0xE0) . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80); 130 } 131 if ($num <= 0x1FFFFF) { 132 return chr(($num>>18) + 0xF0) . chr((($num>>12)&0x3F) + 0x80) 133 . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80); 134 } 135 throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large'); 136 } 137 138 public function getType() : string { 139 return 'Scalar_String'; 140 } 141} 142