Интерпретировать escape-символы в одиночной кавычки

Наличие строки с одной кавычкой :

$content = '\tThis variable is not set by me.\nCannot do anything about it.\n'; 

Я хотел бы inerpret / обрабатывать строку, как если бы она была двойным . Другими словами, я хотел бы заменить все возможные escape-символы (а не только табуляцию и перевод строки, как в этом примере), с реальными значениями, принимая во внимание, что обратная косая черта также может быть экранирована, поэтому «\\ n» необходимо заменить по '\ n'. eval () будет легко делать то, что мне нужно, но я не могу его использовать.

Есть ли какое-то простое решение?

( Аналогичный поток, который я нашел, касается расширения переменных в одной кавычки, в то время как я после замены escape-символов.)

Существует очень простой способ сделать это, основываясь на preg_replace Doc и stripcslashes , как встроенные:

 preg_replace( '/\\\\([nrtvf\\\\$"]|[0-7]{1,3}|\x[0-9A-Fa-f]{1,2})/e', 'stripcslashes("$0")', $content ); 

Это работает до тех пор, пока "\\n" должно стать "\n" и тому подобное. Демо .

Если вы хотите обработать эти строки буквально, см. Мой предыдущий ответ .

Редактировать: вы спросили в комментарии:

Я немного озадачен, какая разница между выходом этого и stripcslashes () непосредственно [?]

Разница не всегда видима, но есть одна: stripcslashes удалит \ chracter, если последующая escape-последовательность не будет следовать. В PHP-строках в этом случае косая черта не отбрасывается. Например, "\d" , d не является особым символом, поэтому PHP сохраняет косую черту:

 $content = '\d'; $content; # \d stripcslashes($content); # d preg_replace(..., $content); # \d 

Вот почему preg_replace здесь полезен, он будет применять только функцию на тех подстроках, где stripcslashes работает по назначению: все допустимые escape-последовательности.

Если вам нужно делать точные escape-последовательности, как это делает PHP, вам нужна длинная версия, которая является классом DoubleQuoted . Я немного расширил входную строку, чтобы покрыть больше escape-последовательностей, чем в вашем вопросе, чтобы сделать это более общим:

 $content = '\\\\t\tThis variable\\string is\x20not\40set by me.\nCannot \do anything about it.\n'; $dq = new DoubleQuoted($content); echo $dq; 

Вывод:

 \\t This variable\string is not set by me. Cannot \do anything about it. 

Однако, если вы хорошо stripcslashes к этому, есть функция PHP, называемая stripcslashes , для сравнения я добавил результат и строку с двойной stripcslashes PHP:

 echo stripcslashes($content), "\n"; $compare = "\\\\t\tThis variable\\string is\x20not\40set by me.\nCannot \do anything about it.\n"; echo $compare, "\n"; 

Вывод:

 \t This variablestring is not set by me. Cannot do anything about it. \\t This variable\string is not set by me. Cannot \do anything about it. 

Как видно, stripcslashes некоторые символы здесь по сравнению с stripcslashes выходом PHP.

( Edit: см. Также мой другой ответ, который предлагает что-то простое и приятное с cstripslashes и preg_replace .)

Если stripcslashes не подходит, есть DoubleQuoted . Конструктор принимает строку, которая обрабатывается как строка с двойными кавычками (минус подстановка переменных, только escape-последовательности символов).

Как и в плане руководства, существует несколько управляющих последовательностей. Они выглядят как регулярные выражения, и все начинаются с \ , поэтому он почти подходит для использования регулярных выражений для их замены.

Однако есть одно исключение: \\ пропустит escape-последовательность. Регулярное выражение должно иметь обратную связь и / или атомные группы, чтобы справиться с этим, и я не владею ими, поэтому я просто сделал простой трюк: я применял регулярные выражения только к тем частям строки, которые не содержат \\ просто вначале взломав строку, а затем снова ее развязав.

Две функции, основанные на регулярном выражении, заменяют функции preg_replace Doc и preg_replace_callback Doc , позволяют работать и с массивами, поэтому это довольно легко сделать.

Это делается в функции __toString() Doc :

 class DoubleQuoted { ... private $string; public function __construct($string) { $this->string = $string; } ... public function __toString() { $this->exception = NULL; $patterns = $this->getPatterns(); $callback = $this->getCallback(); $parts = explode('\\\\', $this->string); try { $parts = preg_replace_callback($patterns, $callback, $parts); } catch(Exception $e) { $this->exception = $e; return FALSE; # provoke exception } return implode('\\\\', $parts); } ... 

См. explode Doc и implode Doc . Те, кто заботится о том, что preg_replace_callback не работает ни с одной строкой, содержащей \\ . Таким образом, операция замены была освобождена от бремени для решения этих особых случаев. Это функция обратного вызова, которая вызывается preg_replace_callback для каждого совпадения шаблонов. Я завернул его в закрытие, чтобы он не был общедоступным:

 private function getCallback() { $map = $this->map; return function($matches) use ($map) { list($full, $type, $number) = $matches += array('', NULL, NULL); if (NULL === $type) throw new UnexpectedValueException(sprintf('Match was %s', $full)) ; if (NULL === $number) return isset($map[$type]) ? $map[$type] : '\\'.$type ; switch($type) { case 'x': return chr(hexdec($number)); case '': return chr(octdec($number)); default: throw new UnexpectedValueException(sprintf('Match was %s', $full)); } }; } 

Вам нужна дополнительная информация, чтобы понять это, поскольку это уже не полный класс. Я просматриваю недостающие точки и добавляю недостающий код:

Все шаблоны класса «ищет» содержат подгруппы, по крайней мере один. Он переходит в $type и является либо одиночным символом для перевода, либо пустой строкой для восьмеричных, и x для шестнадцатеричных чисел.

Необязательный второй номер группы $number либо не установлен ( NULL ), либо содержит восьмеричное / шестнадцатеричное число. Входные данные $matches matches нормированы на только что названные переменные в этой строке:

 list($full, $type, $number) = $matches += array('', NULL, NULL); 

Шаблоны определяются заранее как последовательности в частной переменной-члене:

 private $sequences = array( '(n|r|t|v|f|\\$|")', # single escape characters '()([0-7]{1,3})', # octal '(x)([0-9A-Fa-f]{1,2})', # hex ); 

Функция getPatterns() просто переносит эти определения в допустимые регулярные выражения PCRE:

 /\\(n|r|t|v|f|\$|")/ # single escape characters /\\()([0-7]{1,3})/ # octal /\\(x)([0-9A-Fa-f]{1,2})/ # hex 

Это довольно просто:

 private function getPatterns() { foreach($this->sequences as $sequence) $patterns[] = sprintf('/\\\\%s/', $sequence) ; return $patterns; } 

Теперь, когда шаблоны обрисованы в общих чертах, это объясняет, какие $matches содержат, когда вызывается функция обратного вызова.

Другая вещь, которую вам нужно знать, чтобы понять, как работает обратный вызов, – это $map . Это всего лишь массив, содержащий одиночные символы замены:

 private $map = array( 'n' => "\n", 'r' => "\r", 't' => "\t", 'v' => "\v", 'f' => "\f", '$' => '$', '"' => '"', ); 

И это уже в значительной степени для класса. Существует еще одна приватная переменная $this->exception которая используется для хранения, если было __toString() исключение, поскольку __toString() не может генерировать исключения и приведет к фатальной ошибке, если это произойдет в функции обратного вызова. Таким образом, он попадает и сохраняется в переменной частного класса, и здесь эта часть кода:

  ... public function __toString() { $this->exception = NULL; ... try { $parts = preg_replace_callback($patterns, $callback, $parts); } catch(Exception $e) { $this->exception = $e; return FALSE; # provoke exception } ... 

В случае исключения при замене функция существует с FALSE что приведет к захватывающему исключению. Функция getter делает доступным внутреннее исключение:

 private $exception; ... public function getException() { return $this->exception; } 

Так как приятно получить доступ к исходной строке, вы можете добавить еще один получатель, чтобы получить это:

 public function getString() { return $this->string; } 

И это весь класс. Надеюсь, это полезно.

Решение на основе регулярных выражений, вероятно, будет наиболее пригодным для обслуживания здесь (определения допустимых управляющих последовательностей в строках даже предоставляются в виде регулярных выражений в документации):

 $content = '\tThis variable is not set by me.\nCannot do anything about it.\n'; $replaced = preg_replace_callback( '/\\\\(\\\\|n|r|t|v|f|"|[0-7]{1,3}|\x[0-9A-Fa-f]{1,2})/', 'replacer', $content); var_dump($replaced); function replacer($match) { $map = array( '\\\\' => "\\", '\\n' => "\n", '\\r' => "\r", '\\t' => "\t", '\\v' => "\v", // etc for \f \$ \" ); $match = $match[0]; // So that $match is a scalar, the full matched pattern if (!empty($map[$match])) { return $map[$match]; } // Otherwise it's octal or hex notation if ($match[1] == 'x') { return chr(hexdec(substr($match, 2))); } else { return chr(octdec(substr($match, 1))); } } 

Вышеупомянутое может также (и действительно должно) быть улучшено:

  • Вместо этого замените функцию замены на анонимную функцию
  • Возможно, замените $map switch на бесплатное увеличение производительности