• 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

  Quote
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.
  • Posts

    • Report: Apple's new features for Messages and Phone apps revealed ahead of WWDC 2025 by Hamid Ganji Piles of rumors and speculations about Apple's upcoming UI overhaul across its operating systems have been released, though only some of them have given us a sneak peek into Apple's next OS for iPhones, dubbed iOS 26. However, a new report by Bloomberg's Mark Gurman gives us a clear idea of what to expect from the upcoming iOS 26 and its various applications. As the report reads, the iOS UI overhaul also affects the Messages and Phone apps, two of Apple customers' most used applications. According to Gurman, the Messages app on iOS 26 allows users to create polls and set a background image for a specific conversation. Interestingly, the background image set by one side of the conversation will be visible to the other side as well. With the recent changes to its Messages app, Gurman says Apple aims to compete with Meta's WhatsApp and other messaging platforms. He writes: "The two main changes are the ability to create polls and set a background image. The backgrounds will sync between devices, including those of other users, meaning that you and the people you are chatting with have the same look." However, the scope of changes on the Phone app is limited. Gurman says the Phone app in iOS 26 gets a new view that combines favorite contacts, recent calls, and voicemails into a single, scrollable window. Of course, the new design is optional for users, and they can still opt for the legacy interface. Changes to the Messages and Phone apps on iOS 26 focus on providing a personalized experience to iPhone users and moving them away from rival messaging apps. Apple's 2025 Worldwide Developers Conference kicks off on June 9. The Cupertino tech giant is gearing up to unveil its revamped operating systems with a glass-like UI and new names, representing 2025-2026 release cycle.
    • I've read this truck was mainly his design, he had his hands all over it and the final say.
    • Here's how to watch Summer Game Fest 2025 showcase today and what to expect by Pulasthi Ariyasinghe E3 as we know it might be dead, but Summer Game Fest is still going strong with its scheduled events in June. For five years now, Geoff Keighley has been organizing and hosting the video-game-centered platform to get developers and publishers a central location to have their reveal events. Summer Game Fest itself has its own massive show, and tonight, that kickoff event is going live with a whole bunch of announcements, updates, and more. The Summer Game Fest showcase hosted by Keighley is scheduled to go live at 2 PM PT | 5 PM ET | 10 PM BST later today, June 6. The live show is being hosted at the YouTube Theater in Los Angeles and is slated to run around two hours. To watch it online, viewers can tune into the show's YouTube (4K at 60 FPS), Twitch, Twitter, and Steam pages. Separate streams featuring American Sign Language and Descriptive Audio are available on YouTube as well. Not much information has been released regarding what gaming fans can expect to see at the event, but there are some clues and teasers to follow. Hideo Kojima is attending the event like always, meaning Death Stranding 2: On The Beach should get a brand-new trailer, unless it's a misdirect of some kind. The next entry in the Mafia franchise, The Old Country, the Soulslike action RPG Wuchang: Fallen Feathers, a surprise from the Splitgate 2 developer 1047 Games, a Fortnite-focused event from Epic Games, and new content for Atomic Heart should appear during the show. Being a rather long showcase, there should be plenty of other surprise reveals and announcements on stage, especially from major publishers like Ubisoft and 2K, which do not have their own dedicated summer events. Right after the main kickoff event, the Day of the Devs showcase will begin its own festivities at 4pm PT | 7pm ET. This is focused entirely on upcoming indie games. Later in the day, Devolver Digital and IO Interactive are hosting their own showcases. Here's the full calendar.
    • TIL you can report articles. reported as ad lol.
  • Recent Achievements

    • One Month Later
      EdwardFranciscoVilla earned a badge
      One Month Later
    • One Month Later
      MoyaM earned a badge
      One Month Later
    • One Month Later
      qology earned a badge
      One Month Later
    • One Year In
      Frinco90 earned a badge
      One Year In
    • Apprentice
      Frinco90 went up a rank
      Apprentice
  • Popular Contributors

    1. 1
      +primortal
      453
    2. 2
      +FloatingFatMan
      247
    3. 3
      snowy owl
      240
    4. 4
      ATLien_0
      196
    5. 5
      Xenon
      143
  • Tell a friend

    Love Neowin? Tell a friend!