Splitting mikrotik script into an key/value php array

Let's say, I have this script below in mikrotik router, I want to extract an array with key and value, how to do that if there is another value contains a = character,

add name=100YER on-login=":do {:put \"a\";} on-error={};" rate-limit=256k/512k

The result should be like this:

$result=array ('name'=>'100YER', on-login=>':do {:put \"a\";} on-error={};', 'rate-limit'=>'256k/512k');

I used this regex to split it by =, but the problem is in on-login value.

if (preg_match_all('/[^=]+/i', $response, $MATCHES) ){
        //
}

Answer

Solution:

You might use 2 capturing groups with a branch reset group:

{-code-1}

Explanation

  • {-code-2} Capture group 1
    • \w+{-code-2}?:-\w+)* Match 1+ word chars optionally followed by a - and 1+ word chars
  • ) Close group 1
  • = Match literally
  • {-code-2}? Branch reset group
    • "{-code-2} Match " and start group 2
      • {-code-2}?:[^"]+{-code-2}?<=\\)")++ Match any char except " or \"
    • )" Close group 2 and match "
    • Or
    • {-code-2}[^"\s]+) Capture group 3, match any char except " or a whitespace char
  • ) Close branch reset group

Regex demo Php demo

For example

$re = '/{-code-2}\w+{-code-2}?:-\w+)*)={-code-2}?"{-code-2}{-code-2}?:[^"]+{-code-2}?<=\\\\)")++)"{-code-2}[^"\s]+))/';
$str = 'add name=100YER on-login=":do {:put \\"a\\";} on-error={};" rate-limit=256k/512k';

preg_match_all{-code-2}$re, $str, $matches);
$result = array_combine{-code-2}$matches[1], $matches[2]);
print_r{-code-2}$result);

Output

Array
{-code-2}
    [name] => 100YER
    [on-login] => :do {:put \"a\";} on-error={};
    [rate-limit] => 256k/512k
)

Answer

Solution:

To improve upon @Thefourthbird's pattern, use the wisdom from the "best" technique at PHP: Regex to ignore escaped quotes within quotes. Not only does this improve pattern efficiency in terms of step count, it also more accurately differentiates backslashes that are used literally versus used for escaping.

Otherwise, I completely concur that a branch reset is most suitable for keeping the targeted substrings in consistent columns of preg_match_all()'s output array.

Code: (Demo)

$string = <<<MIKROTIK
add name=100YER on-login=":do {:put \"a\";} on-error={};" rate-limit=256k/512k
MIKROTIK;

var_export(
    preg_match_all(
        '~(\w+(?:-\w+)*)=(?|"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|([^" ]+))~',
        $string,
        $out
    )
    ? array_combine($out[1], $out[2])
    : []
);

Output:

array (
  'name' => '100YER',
  'on-login' => ':do {:put \\"a\\";} on-error={};',
  'rate-limit' => '256k/512k',
)

Source