Chris@13: '\\', Chris@0: '$' => '$', Chris@0: 'n' => "\n", Chris@0: 'r' => "\r", Chris@0: 't' => "\t", Chris@0: 'f' => "\f", Chris@0: 'v' => "\v", Chris@0: 'e' => "\x1B", Chris@13: ]; Chris@0: Chris@0: /** Chris@0: * Constructs a string scalar node. Chris@0: * Chris@0: * @param string $value Value of the string Chris@0: * @param array $attributes Additional attributes Chris@0: */ Chris@13: public function __construct(string $value, array $attributes = []) { Chris@0: parent::__construct($attributes); Chris@0: $this->value = $value; Chris@0: } Chris@0: Chris@13: public function getSubNodeNames() : array { Chris@13: return ['value']; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @internal Chris@0: * Chris@0: * Parses a string token. Chris@0: * Chris@0: * @param string $str String token content Chris@0: * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes Chris@0: * Chris@0: * @return string The parsed string Chris@0: */ Chris@13: public static function parse(string $str, bool $parseUnicodeEscape = true) : string { Chris@0: $bLength = 0; Chris@0: if ('b' === $str[0] || 'B' === $str[0]) { Chris@0: $bLength = 1; Chris@0: } Chris@0: Chris@0: if ('\'' === $str[$bLength]) { Chris@0: return str_replace( Chris@13: ['\\\\', '\\\''], Chris@13: ['\\', '\''], Chris@0: substr($str, $bLength + 1, -1) Chris@0: ); Chris@0: } else { Chris@0: return self::parseEscapeSequences( Chris@0: substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * @internal Chris@0: * Chris@0: * Parses escape sequences in strings (all string types apart from single quoted). Chris@0: * Chris@0: * @param string $str String without quotes Chris@0: * @param null|string $quote Quote type Chris@0: * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes Chris@0: * Chris@0: * @return string String with escape sequences parsed Chris@0: */ Chris@13: public static function parseEscapeSequences(string $str, $quote, bool $parseUnicodeEscape = true) : string { Chris@0: if (null !== $quote) { Chris@0: $str = str_replace('\\' . $quote, $quote, $str); Chris@0: } Chris@0: Chris@0: $extra = ''; Chris@0: if ($parseUnicodeEscape) { Chris@0: $extra = '|u\{([0-9a-fA-F]+)\}'; Chris@0: } Chris@0: Chris@0: return preg_replace_callback( Chris@0: '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~', Chris@0: function($matches) { Chris@0: $str = $matches[1]; Chris@0: Chris@0: if (isset(self::$replacements[$str])) { Chris@0: return self::$replacements[$str]; Chris@0: } elseif ('x' === $str[0] || 'X' === $str[0]) { Chris@0: return chr(hexdec($str)); Chris@0: } elseif ('u' === $str[0]) { Chris@0: return self::codePointToUtf8(hexdec($matches[2])); Chris@0: } else { Chris@0: return chr(octdec($str)); Chris@0: } Chris@0: }, Chris@0: $str Chris@0: ); Chris@0: } Chris@0: Chris@13: /** Chris@13: * Converts a Unicode code point to its UTF-8 encoded representation. Chris@13: * Chris@13: * @param int $num Code point Chris@13: * Chris@13: * @return string UTF-8 representation of code point Chris@13: */ Chris@13: private static function codePointToUtf8(int $num) : string { Chris@0: if ($num <= 0x7F) { Chris@0: return chr($num); Chris@0: } Chris@0: if ($num <= 0x7FF) { Chris@0: return chr(($num>>6) + 0xC0) . chr(($num&0x3F) + 0x80); Chris@0: } Chris@0: if ($num <= 0xFFFF) { Chris@0: return chr(($num>>12) + 0xE0) . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80); Chris@0: } Chris@0: if ($num <= 0x1FFFFF) { Chris@0: return chr(($num>>18) + 0xF0) . chr((($num>>12)&0x3F) + 0x80) Chris@0: . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80); Chris@0: } Chris@0: throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large'); Chris@0: } Chris@13: Chris@13: public function getType() : string { Chris@13: return 'Scalar_String'; Chris@13: } Chris@0: }