• 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

    • Looking for a specific setting in Settings? Sorry, the option just doesn’t exist as you’d need to elevate for that. Want to do something quickly and efficiently? Nah, forced to use a “modern” interface which takes far longer to achieve what you’re looking to do. (Example: disable a NIC)
    • Yet the best laptop for all day battery life is a Mac, hands down, no contest. Windows is bloated and power management is rubbish. Search indexer. Defender. Malicious Software Removal Tool. Windows Update (+DISM). Office CTR. Telemetry. Disclaimer: I own a surface laptop studio, multiple gaming desktops, server, and a macbook pro.
    • And bring back taskbar deskbands which were removed, and full and total customization of notification area icons like earlier
    • This has all the markings of a thinly veiled mechanism for force users of older versions of Windows to upgrade...and trash perfectly good hardware. Microsoft, your history easily supports this line of reasoning.
    • Plasma 6.5 brings improved Emoji Selector, better performance in Activity manager, and more by David Uzondu This week saw the long-awaited release of KDE Plasma 6.4, bringing better window management and a whole lot of color management features. Apart from the release of 6.4, the KDE team was able to get other work done, and this was all outlined in the latest issue of This Week in Plasma, which details what is coming down the pipe for future versions. Looking ahead to Plasma 6.5, the developers are making some notable changes to improve performance and general usability. To prevent its database from growing endlessly and causing performance problems over time, the Activity Manager service will now only store the last four months of your history by default. The Emoji selector app is also getting a much-needed redesign that makes the window more compact and moves the sidebar button to the header for a cleaner look. Other little details for 6.5 are being polished up too. The unpopular vertical line separating the date and time on the horizontal Digital Clock widget is gone; if you want it back, you can add it yourself with a custom date format. The "Add New" button has also been moved to the top toolbar in the Shortcuts page within Settings, freeing up some valuable screen real estate. The devs also reduced the minimum size of custom tiling tiles, a small but significant fix for anyone with an ultrawide monitor. In addition to that, the Networks widget's captive portal banner now uses inline header styling, so it doesn't look like a bunch of frames stacked inside each other anymore. Of course, before we get to 6.5, the current release needs some attention. Plasma 6.4's first bug fix release, 6.4.1, addresses issues like broken item selection in the Folder View widget and a bug that could cause the system to lock or suspend faster than intended. Keyboard navigation in list views in Discover feels smoother now, and text is easier to read in certain list items in KRunner. The devs also cleaned up how list items look when you press or click them in Discover. 6.4.1 also fixes a bug where the clipboard history popup would fail to select the top item, and makes the Earth Science Picture of The Day wallpaper plugin work again after its data source changed. Here's the full list of fixes: Fixed several issues in the Folder View widget that caused selecting or opening items to not work when using certain non-default view settings, when the view was scrollable, or when using a touchscreen. Fixed a bug in the Meta+V clipboard popup that sometimes failed to pre-select the top-most item. The Clipboard settings window’s shortcuts page no longer shows columns for local shortcuts that don’t do anything, since the clipboard is global in scope. Fixed the Earth Science Picture of the Day wallpaper plugin after the source data changed formatting again. Made a few fixes to the “Missing Backends” section of Discover’s settings window that kept it from working properly. Fixed a bug that prevented direct scan-out (and its performance benefits) from activating on rotated screens. Fixed an issue where the system could lock or suspend sooner than expected after an app stopped blocking those actions. Installing a new wallpaper plugin no longer causes the plugin list combobox to appear blank. The team even went back to squash some bugs in the older 6.3.6, tackling an issue that could cause keyboard shortcuts to get lost during a system upgrade and fixing an overflow bug with KRunner's faded completion text. Plasma 6.4.1 is set to arrive on June 24th, with 6.3.6 following on July 8th.
  • Recent Achievements

    • One Month Later
      adxnksd42031 earned a badge
      One Month Later
    • Rising Star
      aphanic went up a rank
      Rising Star
    • Contributor
      GravityDead went up a rank
      Contributor
    • Week One Done
      BlakeBringer earned a badge
      Week One Done
    • Week One Done
      Helen Shafer earned a badge
      Week One Done
  • Popular Contributors

    1. 1
      +primortal
      664
    2. 2
      ATLien_0
      262
    3. 3
      Michael Scrip
      234
    4. 4
      Steven P.
      162
    5. 5
      +FloatingFatMan
      151
  • Tell a friend

    Love Neowin? Tell a friend!