1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Translation\Extractor; 13 14/* 15 * The following is derived from code at http://github.com/nikic/PHP-Parser 16 * 17 * Copyright (c) 2011 by Nikita Popov 18 * 19 * Some rights reserved. 20 * 21 * Redistribution and use in source and binary forms, with or without 22 * modification, are permitted provided that the following conditions are 23 * met: 24 * 25 * * Redistributions of source code must retain the above copyright 26 * notice, this list of conditions and the following disclaimer. 27 * 28 * * Redistributions in binary form must reproduce the above 29 * copyright notice, this list of conditions and the following 30 * disclaimer in the documentation and/or other materials provided 31 * with the distribution. 32 * 33 * * The names of the contributors may not be used to endorse or 34 * promote products derived from this software without specific 35 * prior written permission. 36 * 37 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 38 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 39 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 40 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 41 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 44 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 45 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 47 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 48 */ 49 50class PhpStringTokenParser 51{ 52 protected static $replacements = [ 53 '\\' => '\\', 54 '$' => '$', 55 'n' => "\n", 56 'r' => "\r", 57 't' => "\t", 58 'f' => "\f", 59 'v' => "\v", 60 'e' => "\x1B", 61 ]; 62 63 /** 64 * Parses a string token. 65 * 66 * @param string $str String token content 67 * 68 * @return string The parsed string 69 */ 70 public static function parse(string $str) 71 { 72 $bLength = 0; 73 if ('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(substr($str, $bLength + 1, -1), '"'); 85 } 86 } 87 88 /** 89 * Parses escape sequences in strings (all string types apart from single quoted). 90 * 91 * @param string $str String without quotes 92 * @param string|null $quote Quote type 93 * 94 * @return string String with escape sequences parsed 95 */ 96 public static function parseEscapeSequences(string $str, string $quote = null) 97 { 98 if (null !== $quote) { 99 $str = str_replace('\\'.$quote, $quote, $str); 100 } 101 102 return preg_replace_callback( 103 '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~', 104 [__CLASS__, 'parseCallback'], 105 $str 106 ); 107 } 108 109 private static function parseCallback(array $matches): string 110 { 111 $str = $matches[1]; 112 113 if (isset(self::$replacements[$str])) { 114 return self::$replacements[$str]; 115 } elseif ('x' === $str[0] || 'X' === $str[0]) { 116 return \chr(hexdec($str)); 117 } else { 118 return \chr(octdec($str)); 119 } 120 } 121 122 /** 123 * Parses a constant doc string. 124 * 125 * @param string $startToken Doc string start token content (<<<SMTHG) 126 * @param string $str String token content 127 * 128 * @return string Parsed string 129 */ 130 public static function parseDocString(string $startToken, string $str) 131 { 132 // strip last newline (thanks tokenizer for sticking it into the string!) 133 $str = preg_replace('~(\r\n|\n|\r)$~', '', $str); 134 135 // nowdoc string 136 if (false !== strpos($startToken, '\'')) { 137 return $str; 138 } 139 140 return self::parseEscapeSequences($str, null); 141 } 142} 143