• 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

    • Free Download Manager 6.28.1.6321 by Razvan Serea Free Download Manager is a powerful, easy-to-use and absolutely free download accelerator and manager. FDM accelerates downloads by splitting files into sections and then downloading them simultaneously. As a result download speed increases up to 600%, or even more! FDM can also resume broken downloads so you needn`t start downloading from the beginning after casual interruption. FDM lets you download files and whole web sites from any remote server via HTTP, HTTPS and FTP. You can also download files using BitTorrent protocol. In addition, Free Download Manager allows you to: adjust traffic usage; to organize and schedule downloads; download video from video sites; download whole web sites with HTML Spider; operate the program remotely, via the internet, and more! Free Download Manager is compatible with the most popular browsers Google Chrome, Firefox, Microsoft Edge, Internet Explorer and Safari. Free Download Manager 6.28.1.6321 changelog: Improved add-ons support. Improved M3U support. Fixed: crash bug in BitTorrent module. Fixed: minor bugs. Windows: a bit improved installer. Windows: Firefox bug workaround. Android: Qt updated to 6.9.1. Download: Free Download Manager (64-bit) | 45.8 MB (Freeware) Links: Home Page | Linux, Mac, Android | MS Store | Screenshot Get alerted to all of our Software updates on Twitter at @NeowinSoftware
    • Tariffs have nothing to do with this pricing. It was always intended to be slightly more expensive then the S25+
    • Hello, The static link still downloads 10.3.2040.0 from May 22, 2025. The 10.3.2412.0 version can be downloaded directly from emclient.com/dist/v10.3.2412/setup.msi. Regards, Aryeh Goretsky
    • Hello, Yes, and yes. More specifically, there are lots of features in Windows that I do not use--I cannot recall the last time I needed to run EUDCEDIT.EXE or ODBCAD32.EXE on a computer I own, but I'm sure that for some people they are useful, and for a smaller set of people they might even be indispensable. I don't begrudge Microsoft for including them as part of the standard Windows installation nor the people who need such tools; sometimes it is convenient to have some little utility or feature readily available. One thing I do begrudge is Microsoft's over-reliance on its own telemetry, and perhaps surpisingly on the flip side, customers who disable it. Collecting telemetry is generally a good thing, if it is done for good reasons and does not include any customer PII. However, how you interpret that telemetry is even more important, as that can lead to all sorts of disastrous decisions. On the customer side of things, telemetry is your "vote:" it's how you tell companies what features you use in the program, and lets them prioritize things appropriately. One glaring example is Windows 8, which shipped with the full-screen Start Screen because Microsoft's telemetry told them the average Windows user pressed the Windows key to bring up the Start Menu less than once a day. I have often wondered how many "power users" of previous versions of Windows (XP, Vista, and 7) that relied on the Start Menu disabled the telemetry that would have told Microsoft a difference story about its usage. More recently, I came across a young lady who had a problem with a third-party sync program on her computer running Windows 7. An update for the utility removed Windows 7 compatibility, and broke her backup process. Now, support for Windows 7 ended over 5 years ago in 2020, but there are ISVs who still support their software on it, but decisions about stuff like that are made, in part, by knowing what percentage of your customer base is on what operating system version. When I asked about that, she mentioned she had specifically disabled the telemetry from the sync program to its developers, which was optional to begin with. What made things even worse was that this was an open source utility, and its authors had a very clear, well-designed and scoped policy on the telemetry they collected, the pains they went through to avoid collecting any PII, and even other ancillary risks involving information disclosure (like just using of the software) because of the network connection made for the checks. Yet, she took herself out of telling the project maintainers "Hey, I use your software and I'm running Windows 7" by disabling the telemetry checks, which could have let them know they needed to continue supporting it. In a sense, sending telemetry is just like voting: Individually, you may not think it matters much, but it is often the basis for very important decisions. Regards, Aryeh Goretsky
    • Hello, My thoughts on this are mixed. Microsoft has hosted malicious code in the Microsoft Update Catalog where third party device drivers are stored; I wrote about one such incident about fifteen years ago, so if there are any other old malicious drivers floating around in the catalog, this will be a good step towards preventing any infestations from reoccurring. Another thing, which surprisingly is not mentioned in Microsoft's announcement, is that this helps protect against BYOVD (Bring Your Own Vulnerable Driver) attacks, where malware either comes with or downloads an older device drivers with vulnerabilities in it that can be exploited to gain access to kernel memory. Removing all those old device drivers from the Windows Update Catalog, potentially with all sorts of undisclosed vulnerabilities in them, means an attacker can no longer leisurely count on being able to download them from Microsoft's servers--something that may go unnoticed or ignored by security analysts. This makes the adversary attack a little more noisy, since they have to either include the device driver with the rest of their initial payload or download it from a third-party site at some point prior to beginning their BYOVD attack. On the other hand, it means that people who are looking for a specific version of an older device driver for whatever legitimate reasons, like compatibility, performance or stability, may end up going to dodgy third-party sites in search of older drivers, which increases the risk of exposure to everything from nuisance advertisements and unwanted software to actual malicious code. As for me, I have keeping copies of all the device drivers, firmware updates, etc. I have downloaded over the years, some dating back to DOS and Windows 3.x era, not just for hardware I won, but popular things like unified chipset and video card drivers, just in case I ever needed it. It might seem silly to collect such a thing, but the hardware drivers, firmware updates, and documentation are just about 2 TB in size. From my perspective, it is an inexpensive form of insurance, especially given that disk space is always getting cheaper over time. Regards, Aryeh Goretsky
  • Recent Achievements

    • 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
    • First Post
      emptyother earned a badge
      First Post
    • Week One Done
      Crunchy6 earned a badge
      Week One Done
  • Popular Contributors

    1. 1
      +primortal
      660
    2. 2
      ATLien_0
      266
    3. 3
      Michael Scrip
      235
    4. 4
      Steven P.
      164
    5. 5
      +FloatingFatMan
      149
  • Tell a friend

    Love Neowin? Tell a friend!