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.
Related
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;
}
I want to make links using shortcuts following the pattern: controller/#/id. For example: a#3 must be rewritten to /actions/view/3, and t#28 must be a link to /tasks/view/28. I think preg_replace is an "easy" way to achieve this, but I'm not that good with regular expressions and I don't know how to "reuse" the digits from the search-string within the result. I think I need something like this:
$search = array('/a#\d/', '/t#\d/');
$replace = array('/actions/view/$1', '/tasks/view/$1');
$text = preg_replace($search, $replace, $text);
Can someone point me in the right direction?
You can "reuse" the numbers from the search strings using capturing groups, denoted by brackets ().
Try this -
$text = "a#2 a#3 a#5 a#2 t#34 t#34 t#33 t#36";
$search = array('/\ba#(\d+)\b/', '/\bt#(\d+)\b/');
$replace = array('/actions/view/$1', '/tasks/view/$1');
$text = preg_replace($search, $replace, $text);
var_dump($text);
/**
OUTPUT-
string '/actions/view/2 /actions/view/3 /actions/view/5 /actions/view/2 /tasks/view/34 /tasks/view/34 /tasks/view/33 /tasks/view/36' (length=123)
**/
The above answer works, but if you need to add more of those search values, you can store those keys in separate array and you can use preg_replace_callback.This also does the same thing, but now, you only need to add more (alphabets)keys in the array and it will replace it accordingly. Try something like this-
$arr = Array(
"a"=> "/actions/view/",
"t"=> "/tasks/view/"
);
$text = preg_replace_callback("/\b([a-z]+)#(\d+)\b/", function($matches) use($arr){
var_dump($matches);
return $arr[$matches[1]].$matches[2];
},$text);
var_dump($text);
/**
OUTPUT-
string '/actions/view/2 /actions/view/3 /actions/view/5 /actions/view/2 /tasks/view/34 /tasks/view/34 /tasks/view/33 /tasks/view/36' (length=123)
**/
Since the number is not replaced you can use strtr (if it is not too ambigous) :
$trans = array('a#' => '/actions/view/', 't#' => '/tasks/view/');
$text = strtr($text, $trans);
if you can use this, it will be faster than processing a string two times with a regex.
I'm still totally lost when it comes to preg_replace function so I would be very happy if someone helped me with this one.
I have string which can contain a call to function like: Published("today")and I need to convert it through regular expression to Published("today", 1)
I basically need to add a second parameter to the function via regular expression.
I cant use str_replace because the first parameter can be (has to be) alphanumeric text.
$string = 'Published("today")';
$foo = preg_replace('/Published\("(\w+)"\)/', 'Published("$1", 1)', $string);
preg_replace_callback should do the job I reckon.
<?php
$string = 'Published("today"); Published("yesterday"); Published("5 days ago");';
$callback = function($match) {
return sprintf('%s, 1', $match[0]);
};
$string = preg_replace_callback(
'~(?<=Published\()"[^"]+"(?=\))~',
$callback,
$string
);
echo $string;
/*
Published("today", 1); Published("yesterday", 1); Published("5 days ago", 1);
*/
I have a function which uses preg_replace() where the pattern and replacements are arrays. I need a counter to track the replacements, so I am converting the function to use preg_replace_callback with a closure, but I can't seem to find a way to distinguish which pattern the match being passed to the callback matches. Is there a way to do array => array replacement using preg_replace_callback?
Ideally this is what I'd like to work, but obviously it won't since $pattern and $replace are evaluated going into the call and not inbetween each replacement
function replaceTags($text)
{
$i = 1;
$pattern = array(
'/\[d\](.*?)\[\/d\]/',
'/\[s\](.*?)\[\/s\]/',
);
$replace = array(
'<div id="'.($i++).'">$1</div>',
'<span id="'.($i++).'">$1</span>',
);
return preg_replace($pattern, $replace, $text);
}
If I understand it correctly, you just need to maintain state between calls to your callback function. The ideal way to do this is using a member function. The state is stored in the object instance. Each time you call, you can modify the object, changing your state.
I also added an extra capture to your patterns in order to differentiate between the patterns in the callback.
<?php
class TagReplacer {
private $counter = 0;
public function replacer($matches) {
// modify the state
$this->counter++;
// return the replacement text using the state
if ($matches[1] === "d")
$tag = 'div';
else
$tag = 'span';
return "<{$tag} id=\"{$this->counter}\">{$matches[2]}</{$tag}>";
}
}
function replaceTags($text) {
$stateObject = new TagReplacer();
$patterns = array(
'/\[(d)\](.*?)\[\/d\]/',
'/\[(s)\](.*?)\[\/s\]/',
);
return preg_replace_callback(
$patterns,
array(&$stateObject, "replacer"),
$text);
}
echo replaceTags("zzz[d]123[/d]zzz[s]456[/s]zzz[d]78[/d]zzz[s]90[/s]zzz");
?>
The output is
zzz<div id="1">123</div>zzz<span id="3">456</span>zzz<div id="2">78</div>zzz<span id="4">90</span>zzz
I was surprised that the ids are not in numerical order. My guess is that preg_replace_callback iterates through the pattern array doing all the replacements at once for each pattern.
I have a little problem with my function:
function swear_filter($string){
$search = array(
'bad-word',
);
$replace = array(
'****',
);
return preg_replace($search , $replace, $string);
}
It should transform "bad-word" to "**" but the problem is the case sensivity
eg. if the user type "baD-word" it doesn't work.
The values in your $search array are not regular expressions.
First, fix that:
$search = array(
'/bad-word/',
);
Then, you can apply the i flag for case-insensitivity:
$search = array(
'/bad-word/i',
);
You don't need the g flag to match globally (i.e. more than once each) because preg_replace will handle that for you.
However, you could probably do with using the word boundary metacharacter \b to avoid matching your "bad-word" string inside another word. This may have consequences on how you form your list of "bad words".
$search = array(
'/\bbad-word\b/i',
);
Live demo.
If you don't want to pollute $search with these implementation details, then you can do the same thing a bit more programmatically:
$search = array_map(
create_function('$str', 'return "/\b" . preg_quote($str, "/") . "\b/i";'),
$search
);
(I've not used the recent PHP lambda syntax because codepad doesn't support it; look it up if you are interested!)
Live demo.
Update Full code:
function swear_filter($string){
$search = array(
'bad-word',
);
$replace = array(
'****',
);
// regex-ise input
$search = array_map(
create_function('$str', 'return "/\b" . preg_quote($str, "/") . "\b/i";'),
$search
);
return preg_replace($search, $replace, $string);
}
I think you mean
'/bad-word/i',
Do you even need to use regex?
function swear_filter($string){
$search = array(
'bad-word',
);
if (in_array(strtolower($string), $search){
return '****';
}
return $search
}
makes the following assumptions.
1) $string contains characters acceptable in the current local
2) all contents of the $search array are lowercase
edit: 3) Entire string consists of bad word
I suppose this would only work if the string was split and evaluated on a per word basis.