Nathanael Demacon
Nathanael Demacon

Reputation: 1217

Regex: How to not include a regex if is in another regex

Here is my problem: I have a php file which contain this "html" code:

<div>
    {{ '{# Hi :D #}' }} {# Hello #}
    {{ $model }}
</div>

In my code I want to get the {# #} and the {{ }} to do different regex replacement but not the one that are in {{ }}.

The problem here is that if I do in the same (in PHP) preg_replace for the two possible match, this doesn't take the {{ }} found (That's what I want) but the replacement will be the same for the two:

/{{(.*?)}}|{#(.*?)#}/

This is logic but I want to do different replacements for each $1 and $2 match.

I thought to those possibilities :

After 2 days trying to create this magic regex I finally ask a question in stackoverflow, hope someone will found the answer to this regex

Thanks,

Upvotes: 1

Views: 71

Answers (1)

Francis Eytan Dortort
Francis Eytan Dortort

Reputation: 1447

EDITED

Here's an approach using preg_split() with PREG_SPLIT_DELIM_CAPTURE flag in order to tokenize the string and account for nested tags.

$tokens = [
  // token definition as [open_tag, close_tag, replacement]
  ['{{', '}}', '<?php echo \1; ?>'],
  ['{#', '#}', '<?php //\1 ?>']
];

$open_tags = array_column($tokens, 0);
$close_tags = array_column($tokens, 1, 0); // mapped by open tag
$replacements = array_column($tokens, 2, 0); // mapped by open tag

$token_regex = '/(' . implode('|', array_map('preg_quote', $open_tags + $close_tags)) . ')/';

$parts = preg_split($token_regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE);

$output = '';
while (null !== ($part = array_shift($parts))) {

  // open tag found...
  if (in_array($part, $open_tags)) {
    // ...start building string of full tag
    $tag_body = $part;
    // ...watch for corresponding close tag
    $close_at = [ $close_tags[$part] ];
    while (0 < count($close_at)) {
      $inner_part = array_shift($parts);
      if (in_array($inner_part, $open_tags)) {
        // nested tag found, add its closing to watchlist
        array_unshift($close_at, $close_tags[$inner_part]);
      } else {
        // close tag found, remove from watchlist
        if (reset($close_at) === $inner_part) {
          array_shift($close_at);
        }
      }
      $tag_body .= $inner_part;
    }

    // substitute full tag with replacement
    $tag_regex = '/^' . preg_quote($part) . '\s*(.*?)\s*' . preg_quote($close_tags[$part]) . '$/';
    $part = preg_replace($tag_regex, $replacements[$part], $tag_body);
  }

  $output .= $part;
}

echo $output;

You can try it out here.

Upvotes: 2

Related Questions