Интерпретировать escape-символы в одинарных кавычках - PullRequest
9 голосов
/ 29 ноября 2011

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

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

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

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

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

Ответы [ 3 ]

5 голосов
/ 29 ноября 2011

Если вам нужно выполнить точные 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.

Однако, если вы можете прийтиблизко к этому есть функция PHP 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 отбрасывает некоторые символы здесь по сравнению с нативным выводом PHP.

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

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

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

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

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

Это делается в__toString() Документ Функция:

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 Документ и implode Док звонков.Они позаботятся о том, чтобы 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 нормализован для только что названных переменных в этой строке:

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() не может генерировать исключения и приведет к фатальной ошибке, если это произойдет в функции обратного вызова.Таким образом, он перехватывается и сохраняется в закрытой переменной класса, и здесь эта часть кода:

    ...
    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, что приведет к перехватываемому исключению.Функция получения делает внутреннее исключение доступным, тогда:

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

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

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

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

5 голосов
/ 29 ноября 2011

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

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

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

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

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

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

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

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

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

0 голосов
/ 29 ноября 2011

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

$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 для бесплатного увеличения производительности
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...