替换文字星(*)...但仅在括号内

this my next question for regex. Honestly, i want to build animation for math multiply , when their input some formula, their will get animation to describe where is this number will counting, this part of my code.

$pattern = '([*])';
$replace = '||';
$stringAsli = (123*1.23*(0.12/8))*(123*512+40);
$replaceResult= str_replace($pattern,$replace,$stringAsli);

but i got result :

(123||1.23||(0.12/8))||(123||512+40)

i want result like this

(123||1.23||(0.12/8))*(123||512+40);

Extended Requirements

In this special case with quoted strings:

("123*1.23*n"*"(0.12/8)")*("123*512"+40)

should become

("123||1.23||n"$"(0.12/8)")*("123||512"+40)

In other words, still replace the * inside the brackets, but if we're inside a quoted strings we replace with ||, and if not we replace with $

Recursive Regex and Lambda Replacement

To safely get into your parentheses, we need to use a recursive regex. Fortunately, PHP supports this. Do it like this (see the output of the online demo):

$mystring = "(123*1.23*(0.12/8))*(123*512+40)";
$nested_parentheses_regex="~\((?:[^()]++|(?R))*\)~";
$replaced = preg_replace_callback(
                    $nested_parentheses_regex,
                    function($m) { return str_replace("*","||",$m[0]);},
                    $mystring
                    );
echo $replaced;

See the output of the online demo.

Explanation

  • The recursive regex \((?:[^()]++|(?R))*\) matches (potentially) nested parentheses
  • The preg_replace_callback function calls an anonymous function which replaces every * with a || within the match (which is a set of nested parentheses.

Extended Requirements

You now asked that inside the parentheses, when the asterisk is not inside quotes, we return $

Use this:

$mystring = '("123*1.23*n"*"(0.12/8)")*("123*512"+40)';
$nested_parentheses_regex ="~\((?:[^()]++|(?R))*\)~"; 

$replaced = preg_replace_callback($nested_parentheses_regex,
    function($m) {
          return preg_replace_callback(
               '~"[^"]*"|(\*)~', // complete "quote" OR capture * to Group 1
               function($n) {
                   // Not inside quotes?
                   if(isset($n[1])) return "\$";
                   else {  // we matched a "full quote"  
                      return str_replace("*","||",$n[0]);
                   }
                   }, 
                $m[0] // this is a (full (set) of parentheses)
                ); // end inner callback
    },
    $mystring
); // end outer callback
echo $replaced;

If you are trying to replace only a literal * within parenthesis that will get tricky and require a recursive regex pattern.

A recursive regex pattern for nested parenthesis looks something like \((?>(?>[^()]+)|(?R))*\).

\(               # matches a literal (
(?>              # starts an atomic group
    (?>          # starts an atomic group
        [^()]+   # matches 1 or more characters that are not parenthesis
    )            # ends atomic group
    |            # allows an alternative pattern
    (?R)         # allows the pattern to be recursed
)                # ends atomic group
*                # signifies the pattern match 0 or more times
\)               # matches a literal )

The pattern will allow you to get all of the matches of everything within parenthesis.

$pattern = '/\((?>(?>[^()]+)|(?R))*\)/';
$subject  = '(123*1.23*(0.12/8))*(123*512+40)';

$replacements = $matches = array();

preg_match_all($pattern, $subject, $matches);

To do the additional replacements on all of these groups I decided to create an array of literal replacement pairs.

foreach($matches[0] as $match) {
    $replacements[$match] = preg_replace('/[*]/', '||', $match);
}

This gives you an array in $replacements that has a key of what to replace and a value of what to replace it with and you can use it in str_replace against your original string.

$result = str_replace(array_keys($replacements), array_values($replacements), $subject);

Implemented a regex-free solution just for fun:

It might be even faster than regex ;-D

$str = '(123*1.23*(0.12/8))*(123*512+40)';

$depth = 0;
for ($i = 0; $i < strlen($str); ++$i) {
    $char = $str[$i];

    switch ($char) {
        case '(':
            ++$depth;
            break;
        case ')':
            --$depth;
            break;
        case '*':
            if ($depth != 0) {
                $str = substr_replace($str, '||', $i, 1);
                ++$i;
            }
            break;
    }
}

echo $str;

Demo: http://ideone.com/IOpOl9