D. van Tol
D. van Tol

Reputation: 23

PHP regular expression in parentheses outside quotes

In php I want to replace the $ in {{ $here $their '$literal' }} with $this->. Which means I would like the result {{ $this->here $this->their '$literal' }}, but what I get instead is: {{ $this->here $their '$literal' }}

this is my code:

<?php

    $str = '{{ $here $their "$literal" }}';
    $search = '{{(?:\'[^\']+\'|"[^"]+")(*SKIP)(*F)|\$([0-9a-zA-Z_]++)}}';
    $replace = '$this->$1';

    // result is {{ $hello $this->by "$literal" }}
    $result = preg_replace('/' . $search . '/U', $replace, $str);

?>

Does anyone know how to replace multiple occurrences within the {{ }} that are not quoted?

Upvotes: 2

Views: 88

Answers (3)

Casimir et Hippolyte
Casimir et Hippolyte

Reputation: 89557

If your string is already enclosed between double curly brackets, you only have to write this:

$pattern = '~[^"\'$]*+(?:"[^"]*"[^"\'$]*|\'[^\']*\'[^"\'$]*)*+\$\b\K~A';
$str = preg_replace($pattern, 'this->', $str);

But if your double curly brackets enclosed part is in a larger text and you don't want to replace other $var outside curly brackets, you need this one:

$pattern = '~(?:\G(?!\A)|{{)[^"\'$}]*+(?:"[^"]*"[^"\'$}]*|\'[^\']*\'[^"\'$}]*)*+\$\b\K~';

with the same replacement string.


Pattern 1 details:

This pattern matches all characters until a $ (included). The easiest way to avoid quoted parts is to match all of them before a dollar and to remove all from the match result using \K. The A modifier (similar of \G at the start of the pattern) ensures that all matches are contiguous (without gaps).

~ # pattern delimiter
[^"'$]*+ #"# all characters except quotes and dollars
(?: # quoted parts
    "[^"]*" [^'"$]*  
  |
    '[^']*' [^"'$]*  
)*+
\$
\b  # no need to write `[a-zA-Z0-9_]+` and to capture it
    # after a literal "$" a word boundary suffices.
\K  # KEEP: characters before this token are removed from the match result
~A  # ANCHORED: force the matches to be contiguous from the start 
    # of the string until the last match. If one position fails, no
    # more match are possible

Upvotes: 1

Pedro Lobito
Pedro Lobito

Reputation: 98901

You probably need something like:

$str = '{{ $here $their "$literal" }}';
$result = preg_replace('/(\$.*?)\s(\$.*?)/i', '$this->$1 $this->$2', $str);

DEMO
http://ideone.com/DO45Se

Upvotes: 0

Wiktor Stribiżew
Wiktor Stribiżew

Reputation: 626748

Match the {{...}} substrings within preg_replace_callback and then use your preg_replace inside the anonynous method with a bit adjusted regex:

$str = '{{ $here $their "$literal" }}';
echo preg_replace_callback('~{{.*?}}~s', function($m) {
    return preg_replace('~(?:\'[^\']+\'|"[^"]+")(*SKIP)(*F)|\$\b~', '$this->$0', $m[0]);
}, $str);

See the PHP demo

The '~{{.*?}}~s' regex will match non-overlapping occurrences of {{, any 0+ chars as few as possible up to the first }}. The regex inside the preg_replace is a bit more complex:

  • (?:\'[^\']+\'|"[^"]+")(*SKIP)(*F) - ' followed with 1+ chars other than ' and then ' (due to \'[^\']+\'), or " followed with 1+ chars other than " and then " (due to "[^"]+"), and once that substring is matched, it is discarded and the regex engine proceeds to the next match (due to (*SKIP)(*F))
  • | - or
  • \$\b - a $ symbol that is followed with a word character.

Upvotes: 1

Related Questions