8

I have tried about a million different regexes and I just can't wrap my head around this one (admittedly a lot of regex is out of my grasp).

In my text I have variables like this:

{{$one}}
{{$three.four.five}}
{{$six.seven}}

And I have an array with all the replaces for them (the index for 'one' is 'one' etc) but some may be missing.

I want to replace from the array if it exists and if not leave the text alone.

What regex can I use to preg_match_all of the variables in $text in the snippet below, replace from $replace where appropriate and echo out to the browser?

<?php
    $replaces = array('testa.testb.testc' => '1', 'testc.testa' => '2', 'testf' => '3');
    $text = '{{$testa.testb.testc}}<br>{{$testc.testa}}<br>{{$testf}}<br>{{$aaaaa}}<br>';

    preg_match_all('/\{\{\$(\w+)\}\}/e', $text, $matches);

    foreach($matches as $match)
    {
        $key = str_replace('{{$', '', $match);
        $key = str_replace('}}', '', $key);

        if(isset($replaces[$key]))
            $text = str_replace($match, $replaces[$key], $text);
    }

    // I want to end up echo'ing:
    //   1<br>2<br>3<br>{{$aaaaa}}<br>

    echo $text;
?>

http://codepad.viper-7.com/4INvEE

This:

'/\{\{\$(\w+)\}\}/e'

like in the snippet, is the closest I have gotten.

It has to work with the does in the variable names too.

Thanks in advance for all the help!

Jared Farrish
  • 48,585
  • 17
  • 95
  • 104
Alex Howe
  • 103
  • 1
  • 1
  • 8
  • Have you looked at what's inside `$matches`? If you look at the link I added to your question, you'll notice your `loop` isn't working as you imagine. You're also not allowing for `.` in the capture. – Jared Farrish Jul 28 '14 at 02:03
  • [This](http://codepad.viper-7.com/xw9j1u) gets you most of the way there; I'm not so great at Regex either, so incorporating the `\w+` with the `.` and allowing it to capture one or more (instead of only three) I'm not entirely sure how to do. – Jared Farrish Jul 28 '14 at 02:08

4 Answers4

8

This is a very good case for using preg_replace_callback() but first let's polish your regex:

  1. Get rid of the e modifier, it's deprecated and you don't need it since we're going to use preg_replace_callback()

    /\{\{\$(\w+)\}\}/
    
  2. We don't need to escape {{}} in this case, PCRE is smart enough to tell that they are not meant as quantifiers

    /{{\$(\w+)}}/
    
  3. Since you got dots in your input, we need to change \w otherwise it will never match. [^}] is perfect since it means match anything except }

    /{{\$([^}]+)}}/
    
  4. I tend to use different delimiters, this is not required:

    #{{\$([^}]+)}}#
    

Let's get to serious business, the use identifier is going to be of great help here:

$replaces = array('testa.testb.testc' => '1', 'testc.testa' => '2', 'testf' => '3');
$text = '{{$testa.testb.testc}}<br>{{$testc.testa}}<br>{{$testf}}<br>{{$aaaaa}}<br>';

$output = preg_replace_callback('#{{\$([^}]+)}}#', function($m) use ($replaces){
    if(isset($replaces[$m[1]])){ // If it exists in our array
        return $replaces[$m[1]]; // Then replace it from our array
    }else{
        return $m[0]; // Otherwise return the whole match (basically we won't change it)
    }
}, $text);

echo $output;

Online regex demo Online php demo

Community
  • 1
  • 1
HamZa
  • 14,671
  • 11
  • 54
  • 75
2

Well you are just using $matches and not $matches[0], that's why the code you posted does not work.

And your regex doesn't count the . with the \w, so let'z try with [\w.]+

(I used $matches[1] that contains directly the key we need, and then we don't need to use str_replace 2 times more)

$replaces = array('testa.testb.testc' => '1', 'testc.testa' => '2', 'testf' => '3');
$text = '{{$testa.testb.testc}}<br>{{$testc.testa}}<br>{{$testf}}<br>{{$aaaaa}}<br>';

preg_match_all('/\{\{\$([\w.]+)\}\}/', $text, $matches);

var_dump($matches[1]);

foreach($matches[1] as $match)
{
    if(isset($replaces[$match]))
    {
        $text = str_replace('{{$'.$match.'}}', $replaces[$match], $text);
    }
}

echo $text;

This works and returns :

1
2
3
{{$aaaaa}}
HamZa
  • 14,671
  • 11
  • 54
  • 75
Bobot
  • 1,118
  • 8
  • 19
  • just seen HamZa answer, i let my one cuz i find it less complicated :) – Bobot Jul 28 '14 at 02:28
  • 1
    Nice approach +1, I took the liberty to get rid of the `e` modifier and unescaped the dot. Basically you don't need to escape the dot `.` in a character class, it will lose its regex meaning. So `[.]` will match only a dot. – HamZa Jul 28 '14 at 02:48
  • 1
    ahh much thanks, im so lost with escape or not excape etc .. ^^ – Bobot Jul 28 '14 at 12:28
2

You don't need to use a regex for that since you are only dealing with fixed strings:

$replaces = array('testa.testb.testc' => 1, 'testc.testa' => 2, 'testf' => 3);
$keys = array_map(function ($item) {
    return '{{$' . $item . '}}'; }, array_keys($replaces));
$trans = array_combine($keys, $replaces);

$result = strtr($text, $trans);

Note that array_map is not needed if you write the replaces array like this:

$trans = array('{{$testa.testb.testc}}' => 1, '{{$testc.testa}}' => 2, '{{$testf}}' => 3);
Casimir et Hippolyte
  • 88,009
  • 5
  • 94
  • 125
  • Your answer reminded me of this [question](http://stackoverflow.com/questions/15773349/replacing-placeholder-variables-in-a-string). Basically +1 for simplicity – HamZa Jul 28 '14 at 02:46
0

You can also use T-Regx library which has automatic delimiters.

Use replace()->by()->mapIfExists():

$output = pattern('{{\$([^}]+)}}')
    ->replace('{{$testa.testb.testc}}<br>{{$testc.testa}}<br>{{$testf}}<br>{{$aaaaa}}<br>')
    ->all()
    ->by()
    ->group(1)
    ->mapIfExists([
       'testa.testb.testc' => '1', 
       'testc.testa' => '2',
       'testf' => '3'
    ]);
});

echo $output;

This code does exactly what @HamZa answer's, but with T-Regx :)

Danon
  • 2,771
  • 27
  • 37