• 0

Recursive preg replace?


Question

Hey there,

I'm trying to use some BB code, however, i'm having a porblem when it comes to using it recursively (e.g. a quote within a quote).

It works fine if there is just one [ quote][ /quote], but if it was to look like this: [ quote][ quote][ /quote][ /quote], only the first [ quote][ /quote] gets formatted. Is there any way to make my preg_replace recursive?

Here is the code:

		$input = nl2br(htmlspecialchars($input));
		$input = str_replace(array('\r\n', '\r', '\n'), '<br />', $input);
		$find = array(  
			"'\[b\](.*?)\[/b\]'is",  
			"'\[i\](.*?)\[/i\]'is",  
			"'\[quote](.+)\[/quote\]'i",
		); 
		$replace = array(  
			"<strong>\\1</strong>",
			"<i>\\1</i>",
			"Quoting <div class=\"quote\">\\1</div>",
		); 
		$output = preg_replace($find, $replace, $input);

Link to comment
https://www.neowin.net/forum/topic/779498-recursive-preg-replace/
Share on other sites

12 answers to this question

Recommended Posts

  • 0

Here's some code to mull over (just change {code} to [ code] and {/code} to [ /code] with a find/replace):

<?php

$input = "[b]Hello[/b]\n\n{code}He said:{code}I'm here{/code}there{/code}\nBoo\n\n[quote]He said:[quote]I'm here[/quote]there[/quote]";

$input = nl2br(htmlspecialchars($input));
# Line break to br tag
$input = str_replace(array('\r\n', '\r', '\n'), '<br />', $input);

# Non-recursive tags
$find = array(  
	"'\[b\](.*?)\[/b\]'is",
	"'\[i\](.*?)\[/i\]'is",
); 
$replace = array(  
	"<strong>\\1</strong>",
	"<i>\\1</i>",
); 
$output = preg_replace($find, $replace, $input);

# Function to handle recursive tags - not sure how to pass parameters :/
function BBParse($input)
	{
	global $tag, $fPre, $fPos, $regex;
	if (is_array($input)) $input = $fPre . $input[1] . $fPos;
	return preg_replace_callback($regex, 'BBParse', $input);
	}

# Recursive quotes
$tag = 'quote';
$fPre = '<div style="border:1px solid red;padding:5px;"><strong>Quoting:</strong><br />';
$fPos = '</div>';
$regex = "#\[quote]((?:[^[]|\[(?!/?quote])|(?R))+)\[/quote]#i";
$output = BBParse($output);

# Recursive code tags
$tag = 'quote';
$fPre = '<div style="border:1px solid blue;padding:5px;"><strong>Code:</strong><br />';
$fPos = '</div>';
$regex = "#\{code}((?:[^[]|\[(?!/?code])|(?R))+)\{/code}#i";
$output = BBParse($output);

echo $output;

?>

If anyone knows how to pass parameters with preg_replace_callback then you don't need to set things up before calling it, just pass them as paramters. Otherwise it works, but is a little messy.

  • 0

	private static function QuoteTag($string) {

		preg_match_all('/(?<!\\\\)\[quote(?::\w+)?\]/i', $string, $quote_open);
		preg_match_all('/(?<!\\\\)\[quote(?::\w+)?=(?:"|"|\')?(.*?)["\']?(?:"|"|\')?\]/i', $string, $quote_opens);
		preg_match_all('/(?<!\\\\)\[\/quote(?::\w+)?\]/i', $string, $qe);

		$qopen = count($quote_open[0]) + count($quote_opens[0]);
		$qend = count($qe[0]);

			if ($qopen == $qend) {
				$string = str_replace('[quote]', '<blockquote><p>', $string);
				$string = preg_replace('/(?<!\\\\)\[quote(?::\w+)?=(?:"|"|\')?(.*?)["\']?(?:"|"|\')?\]/i', 
				"<blockquote><h3>\\1</h3><p>", $string);
				$string = str_replace('[/quote]', '</p></blockquote>', $string);
				$string = str_replace('[/QUOTE]', '</p></blockquote>', $string);
			}
		return $string;
	}

if open tag count is the same as close tag count, then do quotes.

  • 0

It'll stop working in a class because of the "global $someVars" on the first line of the BBParse function. Global references outside a class.

For use in a class

<?php

class BBHandler
	{
	private $tag;
	private $fPre;
	private $fPos;
	private $regex;

	public function Parse ($input)
		{
		# Basic parsing
		$output = $this->StraightParse($input);
		# Quote tags
		$this->tag = 'quote';
		$this->fPre = '<div style="border:1px solid red;padding:5px;"><strong>Quoting:</strong><br />';
		$this->fPos = '</div>';
		$this->regex = "#\[quote]((?:[^[]|\[(?!/?quote])|(?R))+)\[/quote]#i";
		$output = $this->RecursiveParse($output);
		# Code tags
		$this->tag = 'code';
		$this->fPre = '<div style="border:1px solid blue;padding:5px;"><strong>Code:</strong><br />';
		$this->fPos = '</div>';
		$this->regex = "#\{code}((?:[^[]|\[(?!/?code])|(?R))+)\{/code}#i";
		$output = $this->RecursiveParse($output);

		return $output;
		}

	private function StraightParse ($input)
		{
		$input = nl2br(htmlspecialchars($input));
		# Line break to br tag
		$input = str_replace(array('\r\n', '\r', '\n'), '<br />', $input);

		# Non-recursive tags
		$find = array(  
			"'\[b\](.*?)\[/b\]'is",
			"'\[i\](.*?)\[/i\]'is",
		); 
		$replace = array(  
			"<strong>\\1</strong>",
			"<i>\\1</i>",
		); 
		$output = preg_replace($find, $replace, $input);
		return $output;
		}

	private function RecursiveParse ($input)
		{
		if (is_array($input)) $input = $this->fPre . $input[1] . $this->fPos;
		return preg_replace_callback($this->regex, array($this, 'RecursiveParse'), $input);
		}
	}

$BB = new BBHandler();

$input = "[b]Hello[/b]\n\n{code}He said:{code}I'm here{/code}there{/code}\nBoo\n\n[quote]He said:[quote]I'm here[/quote]there[/quote]";

echo '<pre>' . $BB->Parse($input) . '</pre>';

?>

  • 0

That works perfectly! I've just extended it from my framework.

Quick question, with the regex, i'm a little stumped, I want to have

[ quote name=Harry time=12th feb 2009]this is his quote[/ quote]

So I thought i'd add that to the regex:

$this->regex = "#\[ quote name=(.*?) time=(.*?)]((?:[^[]|\[(?!/?quote])|(?R))+)\[/ quote]#i"; // i also added //1, //2 and changed //1 to 3.

Then I wanted to be able to have it as options, so you could use [ quote] or [ quote name=Harry], [ quote time=12th feb 2009] or a match of them all.

So I changed it to [ quote( name=(.*?))?( time=(.*?))?]((?:[^[]|\[(?!/?quote])|(?R))+)\[/ quote]

But I couldn't manage to get it to work. Any help would be great!

  • 0

Use BBCode

http://uk3.php.net/manual/en/intro.bbcode.php

This extension aims to help parse BBCode text in order to convert it to HTML or another markup language. It uses one pass parsing and provides great speed improvement over the common approach based on regular expressions. Further more, it helps provide valid HTML by reordering open / close tags and by automatically closing unclosed tags.

Since 0.10.1 It supports argument quoting with single quotes, double quotes and HTML escaped double quotes.

Regular expressions are horrible for this type of thing.

  • 0

If you insist.

\[quote(?:\s*?name=([a-zA-Z0-9]++))?(?:\s*?time=([a-zA-Z0-9\s]++))?]((?:[^[]++|\[(?!\/quote]))+)\[\/quote]

[quote name=Harry time=12th feb 2009]this is his quote[/quote]

Group 1: Harry

Group 2: 12th feb 2009

Group 3: this is his quote

if (preg_match('%\[quote(?:\s*?name=([a-zA-Z0-9]++))?(?:\s*?time=([a-zA-Z0-9\s]++))?\]((?:[^[]++|\[(?!\/quote\]))+)\[\/quote\]%si', $subject))
{
	# Successful match
}
else
{
	# Match attempt failed
}

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.