PHP regex : How to use captured groups in a function - php

I'm building a hand-made function in php that searches for specific tags ( [b][/b] for bold, [i][/i] for italic and [img][/img] for pictures ) in a string, to replace them into their html equivalent. I also have to treat what's between [img] [/img] tags (can be more than one in a string) in a seperate function before the replacement, i've called it foo here :
<?php
function convert($txt){
$patterns = array( '/\[b\](.*?)\[\/b\]/' ,
'/\[i\](.*?)\[\/i\]/' ,
'/\[img\](.*?)\[\/img\]/');
$replace = array("<b>$1</b>" , "<i>$1</i>" , foo("$1") );
$res = preg_replace($patterns,$replace, $txt);
return $res;
}
It works correctly for the b and i tags, but not img.
The issue here is that : When i put the captured group (referenced by "$1" i think) in a function, it treats "$1" as a string, and not what is referenced by it.
For example, if foo is declared like so :
function foo($var){
echo $var;
}
If i put the string text1 [img]path[/img] text2 in convert()
Then "$1" will be echoed, instead of "path"
Therefore here is my question : How can i "evaluate" the string i've captured in a different function. In the previous example, to echo in foo what is between [img][/img] tags?
Thank you for anyone taking time to reply.

First and foremost, it is strongly recommended to use a legitimate BBCode parser (library) instead of a regex approach. A custom-developed parser should be expected to better handle fringe cases much better than a basic regex pattern.
Now that that disclaimer is given, the way to resolve your issue of calling a function from the replacement parameter of preg_replace() is to call preg_replace_callback(), or in your case, perhaps better coded via preg_replace_callback_array() since you are seeking different callbacks for different patterns.
Code: (Demo)
function convert(string $txt): string {
do {
$txt = preg_replace_callback_array(
[
'~\[([bi])](.*?)\[/\1]~' => fn($m) => sprintf('<%1$s>%2$s</%1$s>', $m[1], $m[2]),
'~\[img](.*?)\[/img]~' => 'foo',
],
$txt,
-1,
$count
);
} while ($count);
return $txt;
}
function foo(array $m): string {
return '<img src="' . $m[1] . '">';
}
echo convert("text1 [img]path/src[/img] text2 [b]bold [i]nested string[/i][/b] [img]another/path/to/file[/img] [b]nice[/b] lingering end bbtag [/b] and [b]unclosed");
Output:
text1 <img src="path/src"> text2 <b>bold <i>nested string</i></b> <img src="another/path/to/file"> <b>nice</b> lingering end bbtag [/b] and [b]unclosed
You will notice that calling foo() is done by using its string name as the callback value. The matches array is sent to the custom function despite not being explicitly mentioned in the 'foo' value.
I am calling preg_replace_callback_array() in a do-while() loop to ensure that nested bbcode tags are replaced (which otherwise would have been overlooked because their parent tag completely enclosed them).
If you wish to handle [u] tags, simply add u after bi in the first regex pattern.

Try this
<?php
function convert($txt){
$pattern = array('/\[b\](.*?)\[\/b\]/' => function($matches) { return "
<b>$matches[1]</b>"; },
'/\[i\](.*?)\[\/i\]/' => function($matches) { return "
<i>$matches[1]</i>"; },
'/\[img\](.*?)\[\/img\]/' => function($matches) { echo
$matches[1]; return "<img>$matches[1]</img>"; });
$res = preg_replace_callback_array($pattern, $txt);
return $res;
}
$result = convert("text1 [img]path[/img] text2");
echo "\n$result\n";
Output:
path
text1 <img>path</img> text2

You can grab the string first and then run the function:
<?php
function convert($txt){
preg_match('/\[img\](.*?)\[\/img\]/', $txt, $match);
$patterns = array( '/\[b\](.*?)\[\/b\]/' ,
'/\[i\](.*?)\[\/i\]/' ,
'/\[img\](.*?)\[\/img\]/');
$replace = array("<b>$1</b>" , "<i>$1</i>" , foo($match[1]) );
$res = preg_replace($patterns,$replace, $txt);
return $res;
}

Related

Making a simple templating engine in PHP

I need to write a simple PHP function to replace text between {{ }} characters with their respective data.
Example:
String: "and with strange aeons even {{noun}} may {{verb}}"
$data = ['noun' => 'bird', 'verb' => 'fly'];
Result:
"and with strange aeons even bird may fly"
I have it almost working with the following code based on preg_replace_callback
function compile($str,$data){
foreach ($data as $k => $v) {
$pattern = '/\b(?<!\-)(' . $k . ')\b(?!-)/i';
$str = preg_replace_callback($pattern, function($m) use($v){
return $v;
}, $str);
}
return $str;
}
But I cant seem to account for the {{ }}.
The result looks like this:
"and with strange aeons even {{bird}} may {{fly}}"
How can I adjust the regex and/or code to account for the double curly brackets?
Also, before anyone asks why I'm trying to do this manually rather than use PHP itself or the Smarty plugin -- its too narrow a use case to install a plugin and I cannot use PHP itself because the input string is coming in as raw text from a database. I need to compile that raw text with data stored in a PHP array.
Since you're looping anyway, keep it simple:
foreach ($data as $k => $v) {
$str = str_ireplace('{{'.$k.'}}', $v, $str);
}
You can add a space before {{ and after }} if needed.
You can use
$str = "and with strange aeons even {{noun}} may {{verb}}";
$data = ['noun' => 'bird', 'verb' => 'fly'];
$pattern = '/{{(' . implode('|', array_keys($data)) . ')}}/i';
echo preg_replace_callback($pattern, function($m) use($data){
return $data[strtolower($m[1])];
}, $str);
// => and with strange aeons even bird may fly
See the PHP demo.
The $pattern will look like /{{(noun|verb)}}/i, and will match noun or verb inside double braces while capturing the word itself. The replacement will be the corresponding key value of the $data array. Turning the Group 1 value to lower case with strtolower($m[1]) is required since the keys in the $data array are all lowercase, and the $pattern can match uppercase variants, too.
Make use of strtr() and call it a day:
$string = 'and with strange aeons even {{noun}} may {{verb}}';
$data = ['{{noun}}' => 'bird', '{{verb}}' => 'fly'];
echo strtr( $string, $data );
produces:
and with strange aeons even bird may fly
strtr() is nice because it won't mess up the string in the event of:
$data = ['{{noun}}' => 'bi{{verb}}rd', '{{verb}}' => 'fly'];

PHP match and replace whole word

Hi I am replacing certain names with different value . Here is values I am replacing "#size-name" and "#size" .But the problem is my code replacing only size first and note name for example
#size = "replaceword"
#size-name = "replaceword2"
But its replacing
#size ="replaceword"
#size-name = "replaceword2-name"
How can I replace whole word not part of it here is my code
$tempOutQuery = preg_replace("/(\b($key)\b)/i" , $value , $tempOutQuery);
$tempOutQuery= str_replace("#".$key ,$value ,$tempOutQuery);
both codes are not working
My Full Code
$val= "Hi I want #size dress which is #size-name";
$tempOutQuery = preg_replace("/(\b(size)\b)/i" ,"replaceword", $tempOutQuery);
$tempOutQuery = preg_replace("/(\b(size-name)\b)/i" ,"replaceword2", $tempOutQuery);
If you could make replace without using regulat expressions, then I would suggest using standart str_replace() with arrays:
$val= "Hi i want #size dress which is #size-name";
$search = array('size-name', 'size');
$replace = array('replaceword2', 'replaceword');
$result = str_replace($search, $replace, $val);
The order of search and replace Strings is important!
You should take care that you replace long search-strings first, and the short strings later.
Here's another option for you, using preg_replace_callback. It's actually very similar to Gennadiy's method. The only real difference is that it's using the preg aspect of PHP (and it's a lot more work). But it's another way to skin the proverbial cat.
<?php
// SET OUR DEFAULT STRING
$string = 'Hi I want #size suit which is #size-name';
// LOOK FOR EITHER size-name OR size AND IF YOU FIND IT ...
// RUN THE FUNCTION 'replace_sizes'
$string = preg_replace_callback('~#(size-name|size)~', 'replace_sizes', $string);
// PRINT OUT OUR MODIFIED STRING
print $string;
// THIS IS THE FUNCTION THAT WILL BE RUN EVERY TIME A MATCH IS FOUND
// EITHER 'size' OR 'size-name' WILL BE STORED IN $m[1]
function replace_sizes($m) {
// SET UP AN ARRAY THAT HAS OUR POTENTIAL MATCHES AS KEYS
// AND THE TEXT WE WANT TO REPLACE WITH AS THE VALUE
$size_text_array = array('size-name' => 'replaceword2', 'size' => 'replaceword');
// RETURN WHATEVER THE VALUE IS BASED ON THE KEY
return $size_text_array[$m[1]];
}
This will print out:
Hi I want replaceword suit which is replaceword2
Here is a working demo:
http://ideone.com/njNTbB
You can try pre_replace() to replace whole word from an item of an array in PHP a shown below.
<?PHP
function removePrepositions($text){
$propositions=array('/\bfor\b/i','/\band\b/i');
if( count($propositions) > 0 ) {
foreach($propositions as $exceptionPhrase) {
$text = preg_replace($exceptionPhrase, '', trim($text));
}
$retval = trim($text);
}
return $retval;
}
?>
See the entire post here

How to create an hyperlink in a paragraph using php

I would like to create hyperlinks to the words in a paragraph.
For instance if the "Jim Carrey" name is in the array matches the word in string, then the Jim Carrey name should be in Hyperlink of "www.domian.net/name(Jim Carrey)" .
If the "mask" word in the array matches the word in string then it should be replace with corresponding url like "www.domian.net/mask"
<?php
$string="Jim Carrey found the new Mask";
$array=array("Jim Carrey","mask");
echo preg_replace( '/\b('.implode( '|', $array ).')\b/i', '$1', $string );
?>
You seem to have the right idea about how to put a link around the chosen text, but you seem to have not even tried to put in an href. Which is a shame, since it's as simple as typing in the URL with whatever parameter you want.
However, it does get a little complicated because you don't want the same thing both times (you want the literal word in one, but you want name(WORD) in the other). You could try this:
$array = array("Jim Carrey"=>"name(Jim Carrey)","mask"=>"mask");
echo preg_replace_callback("/\b".implode("|",array_keys($array))."\b/i",
function($m) use ($array) {
return "".$m."";
},$string);
<?php
$string="Jim Carrey found the new Mask";
$arr=array("Jim Carrey","Mask");
foreach($arr as $val)
$string = str_replace($val, '' . $val . '', $string);
echo $string;
?>

PHP RegExp Variable in Function

I'm using a Function to parse UBBC and I want to use a function to find data from a database to replace text (a [user] kind of function). However the code is ignoring the RegExp Variable. Is there any way I can get it to recognise the RegExp variable?
PHP Function:
function parse_ubbc($string){
$string = $string;
$tags = array(
"user" => "#\[user\](.*?)\[/user\]#is"
);
$html = array(
"user" => user_to_display("$1", 0)
);
return preg_replace($tags, $html, $string);
}
My function uses the username of the user to get their display name, 0 denotes that it is the username being used and can be ignored for the sake of this.
Any help would be greatly appreciated.
You either rewrite your code to use preg_replace_callback, as advised.
Or your rewrite the regex to use the #e flag:
function parse_ubbc($string){
$string = $string;
$tags = array(
"user" => "#\[user\](.*?)\[/user\]#ise"
);
$html = array(
"user" => 'user_to_display("$1", 0)'
);
return preg_replace($tags, $html, $string);
}
For that it's important that PHP does not execute the function in the replacement array immediately. That's why you have to put the function call into 'user_to_display("$1", 0)' single quotes. So preg_replace executes it later with the #e flag.
A significant gotcha here is, that the username may never contain " double quotes which would allow the regex placeholder $0 to break up the evaluated function call (cause havoc). Hencewhy you have to rewrite the regex itself to use \w+ instead of .*?. Or again just use preg_replace_callback for safety.
You need to use preg_replace_callback if you want to source replacements from a database.
function parse_ubbc($string){
$string = $string;
function get_user_to_display($m){
user_to_display($m[1], 0);
}
return preg_replace_callback('#\[user\](.*?)\[/user\]#is', 'get_user_to_display', $string);
}
You're calling user_to_display() with the string '$1', not the actual found string. Try:
function parse_ubbc($string){
$string = $string;
$tags = array(
"user" => "#\[user\](.*?)\[/user\]#ise"
);
$html = array(
"user" => 'user_to_display("$1", 0)'
);
return preg_replace($tags, $html, $string);
}
The changes are adding 'e' to the end of the regexp string, and putting the function call in quotes.

PHP Regular expression: exclude href anchor tags

I'm creating a simple search for my application.
I'm using PHP regular expression replacement (preg_replace) to look for a search term (case insensitive) and add <strong> tags around the search term.
preg_replace('/'.$query.'/i', '<strong>$0</strong>', $content);
Now I'm not the greatest with regular expressions. So what would I add to the regular expression to not replace search terms that are in a href of an anchor tag?
That way if someone searched "info" it wouldn't change a link to "http://something.com/this_<strong>info</strong>/index.html"
I believe you will need conditional subpatterns] for this purpose:
$query = "link";
$query = preg_quote($query, '/');
$p = '/((<)(?(2)[^>]*>)(?:.*?))*?(' . $query . ')/smi';
$r = "$1<strong>$3</strong>";
$str = ''."\n".'A Link'; // multi-line text
$nstr = preg_replace($p, $r, $str);
var_dump( $nstr );
$str = 'Its not a Link'; // non-link text
$nstr = preg_replace($p, $r, $str);
var_dump( $nstr );
Output: (view source)
string(61) "<a href="/Link/foo/the_link.htm">
A <strong>Link</strong></a>"
string(31) "Its not a <strong>Link</strong>"
PS: Above regex also takes care of multi-line replacement and more importantly it ignores matching not only href but any other HTML entity enclosed in < and >.
EDIT: If you just want to exclude hrefs and not all html entities then use this pattern instead of above in my answer:
$p = '/((<)(?(2).*?href=[^>]*>)(?:.*?))*?(' . $query . ')/smi';
I'm not 100% what you are ultimately after here, but from what I can, it's a sort of "search phrase" highlighting facility, which highlights keywords so to speak. If so, I suggest having a look at the Text Helper in CodeIgniter. It provides a nice little function called highlight_phrase and this could do what you are looking for.
The function is as follows.
function highlight_phrase($str, $phrase, $tag_open = '<strong>', $tag_close = '</strong>')
{
if ($str == '')
{
return '';
}
if ($phrase != '')
{
return preg_replace('/('.preg_quote($phrase, '/').')/i', $tag_open."\\1".$tag_close, $str);
}
return $str;
}
You may use conditional subpatterns, see explanation here: http://cz.php.net/manual/en/regexp.reference.conditional.php
preg_replace("/(?(?<=href=\")([^\"]*\")|($query))/i","\\1<strong>\\2</strong>",$x);
In your case, if you have whole HTML, not just href="", there is an easier solution using 'e' modifier, which enables you using PHP code in replacing matches
function termReplacer($found) {
$found = stripslashes($found);
if(substr($found,0,5)=="href=") return $found;
return "<strong>$found</strong>";
}
echo preg_replace("/(?:href=)?\S*$query/e","termReplacer('\\0')",$x);
See example #4 here http://cz.php.net/manual/en/function.preg-replace.php
If your expression is even more complex, you can use regExp even inside termReplacer().
There is a minor bug in PHP: the $found parameter in termReplacer() needs to be stripslashed!

Categories