PHP nested templates in preg_replace
preg_replace("/\[b\](.*)\[\/b\]/Usi", "<strong>$1</strong>", "Some text here... [b][b]Hello, [b]PHP![/b][/b][/b] ... [b]and here[/b]"); returns
Some text here... <strong>[b]Hello, [b]PHP!</strong>[/b][/b] ... <strong>and here</strong> But I need to replace all the tags [b] ... [/ b]. Why does this not happen in my case?
The reason this doesn't work: you catch the first [b], then move on to the next [/ b] and leave something between unchanged. Those. you change the external [b] tags, but not the ones inside.
Your comment on @meza suggests that you want to replace the pseudo-tags in pairs or leave them untouched. The best way to do this is to use multiple passes, for example
$markup = "Some text here... [b][b]Hello, [b]PHP![/b][/b][/b] ... [b]and here[/b]"; $count = 0; do { $markup = preg_replace("/\[b\](.*?)\[\/b\]/usi", "<strong>$1</strong>", $markup, -1, $count ); } while ( $count > 0 ); print $markup; I'm not even sure that you can do this in a single line regex, but even if that were possible, it would be rather complicated and therefore it would be difficult to maintain.
Yes, a multi-pass approach is required if the elements are nested. This can be achieved in one of two ways; coincidence from inside or outside inside. Here are two tested scenarios with fully commented regular expressions that illustrate each technique:
1. Replace from the inside:
<?php // test.php Rev:20121016_0900 $re = '% # Match innermost [b]...[/b] structure. \[b\] # Literal start tag. ( # $1: Element contents. # Use Friedls "Unrolling-the-Loop" technique: # Begin: {normal* (special normal*)*} construct. [^[]* # {normal*} Zero or more non-"[". (?: # Begin {(special normal*)*}. \[ # {special} Tag open literal char, (?!/?b\]) # but only if NOT [b] or [/b]. [^[]* # More {normal*}. )* # Finish {(special normal*)*}. ) # $1: Element contents. \[/b\] # Literal end tag. %x'; printf("Replace matching tags from the inside out:\n"); $text = file_get_contents('testdata.txt'); $i=0; // Keep track of iteration number. printf("i[%d]=%s", $i++, $text); while(preg_match($re, $text)){ $text = preg_replace($re, '<strong>$1</strong>', $text); printf("i[%d]=%s", $i++, $text); } ?> Output:
''' Replace matching tags from the inside out: i[0]=Some text here... [b][b]Hello, [b]PHP![/b][/b][/b] ... [b]and here[/b] i[1]=Some text here... [b][b]Hello, <strong>PHP!</strong>[/b][/b] ... <strong>and here</strong> i[2]=Some text here... [b]<strong>Hello, <strong>PHP!</strong></strong>[/b] ... <strong>and here</strong> i[3]=Some text here... <strong><strong>Hello, <strong>PHP!</strong></strong></strong> ... <strong>and here</strong> ''' 2. Replace outside in:
<?php // test.php Rev:20121016_0901 $re = '% # Match outermost [b]...[/b] structure. \[b\] # Literal start tag. ( # $1: Element contents. (?: # Zero or more contents alternatives. [^[]* # Either non-[b]...[/b] stuff... (?: # Begin {(special normal*)*}. \[ # {special} Tag open literal char, (?!/?b\]) # but only if NOT [b] or [/b]. [^[]* # More {normal*}. )* # Finish {(special normal*)*}. | (?R) # Or a nested [b]...[/b] structure. )* # Zero or more contents alternatives. ) # $1: Element contents. \[/b\] # Literal end tag. %x'; printf("Replace matching tags from the outside in:\n"); $text = file_get_contents('testdata.txt'); $i=0; // Keep track of iteration number. printf("i[%d]=%s", $i++, $text); while(preg_match($re, $text)){ $text = preg_replace($re, '<strong>$1</strong>', $text); printf("i[%d]=%s", $i++, $text); } ?> Output:
''' Replace matching tags from the outside in: i[0]=Some text here... [b][b]Hello, [b]PHP![/b][/b][/b] ... [b]and here[/b] i[1]=Some text here... <strong>[b]Hello, [b]PHP![/b][/b]</strong> ... <strong>and here</strong> i[2]=Some text here... <strong><strong>Hello, [b]PHP![/b]</strong></strong> ... <strong>and here</strong> i[3]=Some text here... <strong><strong>Hello, <strong>PHP!</strong></strong></strong> ... <strong>and here</strong> ''' Note the recursive expression (?R) used in the second approach.
edit your Usi modifiers and replace it with sim .
EDIT:
Take a picture:
<?php function matchReplaceAll($reg, $replace, $str) { while (preg_match($reg, $str)) { $str = preg_replace($reg, $replace, $str); } return $str; } $str="Some text here... [b][b]Hello, [b]PHP![/b][/b][/b] ... and here"; $str=matchReplaceAll('/\[b\](.*?)\[\/b\]/sim', '<strong>$1</strong>', $str); echo $str;