preg_replace_callback: regular expression search and replace - php

$details = "text...[book=123]...text...";
$details = preg_replace_callback(
"/\[book=(.+?)\]/smi",
function ($m) {
global $skip_books;
$book = $m[1]; // 123
$feed = $m[2]; // 456
return "<div id=\"view_book_".$book."_".$feed."\"></div>";
},
$details
);
With this pattern i can only get $book ($m[1]):
"/\[book=(.+?)\]/smi"`
But i want to get $feed ($m[2]) too, so i replace to this [book=123_456].
How to get "456" ($m[2]) after the underline?
"/\[book=(.+?)_(.+?)\]/smi" ???

Don't use global here; you're already using a closure, so use the use:
function ($m) use ($skip_books) {
// ...
}
Btw, you're not actually using $skip_books in the code you've shown so far, but I'm assuming that's because you've simplified it
If your arguments are always numbers, don't use something generic like (.+?) but be specific (the more the better):
/\[book=(\d+)_(\d+)\]/i
I've also removed the /s and /m modifiers, which are useless here.

Related

Filter array to keep values containing the search word using word boundaries

I know this type of question has been asked before and I also saw those working answers. However, it's not working when there are no space between the search string and the rest of the array value. Here is my code -
$example = array ( 'ext_03.jpg', 'int_01_headlight.jpg');
$searchword = 'int_';
$matches = array_filter($example, function($var) use ($searchword) {
return preg_match("/\b$searchword\b/i", $var);
});
echo array_values($matches)[0];`
The last value in the $example array doesn't have any space and this code doesn't work. However, if I put space after int_ it works. But I need it to work even if there are no spaces (should work when there is space too). How can I achieve that?
Here is the solution :
$example = array ( 'ext_03.jpg', 'int_01_headlight.jpg');
$searchword = 'int_';
$matches = array_filter($example, function($var) use ($searchword) { return
preg_match("/\b$searchword/i", $var); });
var_dump($matches);
Remove second \b : The \b in the pattern indicates a word boundary
Documentation : http://php.net/manual/en/function.preg-match.php
EDIT :
The better way is to use \A : Beginning of the string
$example = array ( 'ext_03.jpg', 'int_01_headlight.jpg', 'ext_ int_01_headlight.jpg');
$searchword = 'int_';
// Return 2 results (wrong way)
$matches = array_filter($example, function($var) use ($searchword) { return preg_match("/\b$searchword/i", $var); });
var_dump($matches);
// Return 1 result
$matches = array_filter($example, function($var) use ($searchword) { return preg_match("/\A$searchword/i", $var); });
var_dump($matches);
When you wish to use a regular expression to filter an array, it is suboptimal to call preg_match() within array_filter().
The most appropriate call is preg_grep().
Try something like this:
$example = preg_grep('~\bint_~', $example);
You can extend the pattern with a character class after _ if your logic requires it.
But if you are only interested in the first match ([0]), then you might as well run a foreach() with preg_match() and a break.

Find part of a string and output the whole string

I would like to find part of a string and if true I want to ouput the whole of the string that it finds.
Below is an example:
$Towns = "Eccleston, Aberdeen, Glasgow";
$Find = "Eccle";
if(strstr($Find, $Towns)){
echo outputWholeString($Find, $Towns); // Result: Eccleston.
}
If anyone can shed some light on how to do this as well, and bare in mind that it will not be static values; the $Towns and $Find variables will be dynamically assigned on my live script.
Use explode() and strpos() as
$Towns = "Eccleston, Aberdeen, Glasgow";
$data=explode(",",$Towns);//
$Find = "Eccle";
foreach ($data as $town)
if (strpos($town, $Find) !== false) {
echo $town;
}
DEMO
You have to use strpos() to search for a string inside another one:
if( strpos($Towns, $Find) === false ) {
echo $Towns;
}
Note that you have to use "===" to know if strpos() returned false or 0.
The solution using preg_match function:
$Towns = "Eccleston, Aberdeen, Glasgow";
$Find = "Eccle";
preg_match("/\b\w*?$Find\w*?\b/", $Towns, $match);
$result = (!empty($match))? $match[0] : "";
print_r($result); // "Eccleston"
Assuming that you will always have $Towns separated by ", " then you could do something like this
$Towns = "Eccleston, Aberdeen, Glasgow";
$Find = "Eccle";
$TownsArray = explode(", ", $Towns);
foreach($TownsArray as $Town)
{
if(stristr($Find, $Town))
{
echo $Town; break;
}
}
The above code will output the Town once it finds the needle and exit the foreach loop. You could remove the "break;" to continue letting the script run to see if it finds more results.
Using preg_match(), it is possible to search for Eccle and return the Eccleston word.
I use the Pearl Compatible Regular Expression (PCRE) '#\w*' . $Find . '\w*#' in the code below and the demo code.
The # characters are PCRE delimiters. The pattern searched is inside these delimiters. Some people prefer / as delimiter.
The \w indicates word characters.
The * indicates 0 or more repetions of the previous character.
So, the #\w*Eccle\w*# PCRE searches for an string containing Eccle surrounded by one or more word characters (letters)
<?php
$Towns = "Eccleston, Aberdeen, Glasgow";
$Find = "Eccle";
if (preg_match('#\w*' . $Find . '\w*#', $Towns, $matches)) {
print_r($matches[0]);
}
?>
Running code: http://sandbox.onlinephpfunctions.com/code/4e4026cbbd93deaf8fef0365a7bc6cf6eacc2014
Note: '#\w*' . $Find . '\w*#' is the same as "#\w*$Find\w*#" (note the surrounding single or double quotes). See this.
You were nearly there...
This is probably what you are looking for:
<?php
$Towns = "Eccleston, Aberdeen, Glasgow";
$Find = "Eccle";
if(stripos($Towns, $Find)) {
echo $Towns;
}
The output is: Eccleston, Aberdeen, Glasgow which is what I would call "the whole string".
If however you only want to output that partly matched part of "the whole string", then take a look at that example:
<?php
$Towns = "Eccleston, Aberdeen, Glasgow";
$Find = "Eccle";
foreach (explode(',', $Towns) as $Town) {
if(stripos($Town, $Find)) {
echo trim($Town);
}
}
The output of that obviously is: Eccleston...
Two general remarks:
the strpos() / stripos() functions are better suited here, since they return only a position instead of the whole matched string which is enough for the given purpose.
the usage of stripos() instead of strpos() performs a case insensitive search, which probably makes sense for the task...

PHP Regex match string but exclude a certain word

This question has been asked multiple times, but I didn't find a working solution for my needs.
I've created a function to check for the URLs on the output of the Google Ajax API:
https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=site%3Awww.bierdopje.com%2Fusers%2F+%22Gebruikersprofiel+van+%22+Stevo
I want to exclude the word "profile" from the output. So that if the string contains that word, skip the whole string.
This is the function I've created so far:
function getUrls($data)
{
$regex = '/https?\:\/\/www.bierdopje.com[^\" ]+/i';
preg_match_all($regex, $data, $matches);
return ($matches[0]);
}
$urls = getUrls($data);
$filteredurls = array_unique($urls);
I've created a sample to make clear what I mean exactly:
http://rubular.com/r/1U9YfxdQoU
In the sample you can see 4 strings selected from which I only need the upper 2 strings.
How can I accomplish this?
function getUrls($data)
{
$regex = '#"(https?://www\\.bierdopje\\.com[^"]*+(?<!/profile))"#';
return preg_match_all($regex, $data, $matches) ?
array_unique($matches[1]) : array();
}
$urls = getUrls($data);
Result: http://ideone.com/dblvpA
vs json_decode: http://ideone.com/O8ZixJ
But generally you should use json_decode.
Don't use regular expressions to parse JSON data. What you want to do is parse the JSON and loop over it to find the correct matching elements.
Sample code:
$input = file_get_contents('https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=site%3Awww.bierdopje.com%2Fusers%2F+%22Gebruikersprofiel+van+%22+Stevo');
$parsed = json_decode($input);
$cnt = 0;
foreach($parsed->responseData->results as $response)
{
// Skip strings with 'profile' in there
if(strpos($response->url, 'profile') !== false)
continue;
echo "Result ".++$cnt."\n\n";
echo 'URL: '.$response->url."\n";
echo 'Shown: '.$response->visibleUrl."\n";
echo 'Cache: '.$response->cacheUrl."\n\n\n";
}
Sample on CodePad (since it doesn't support loading external files the string is inlined there)

preg_replace_callback with array pattern and replacement

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.

mb_eregi_replace multiple matches get them

$string = 'test check one two test3';
$result = mb_eregi_replace ( 'test|test2|test3' , '<$1>' ,$string ,'i');
echo $result;
This should deliver: <test> check one two <test3>
Is it possible to get, that test and test3 was found, without using another match function ?
You can use preg_replace_callback instead:
$string = 'test check one two test3';
$matches = array();
$result = preg_replace_callback('/test|test2|test3/i' , function($match) use ($matches) {
$matches[] = $match;
return '<'.$match[0].'>';
}, $string);
echo $result;
Here preg_replace_callback will call the passed callback function for each match of the pattern (note that its syntax differs from POSIX). In this case the callback function is an anonymous function that adds the match to the $matches array and returns the substitution string that the matches are to be replaced by.
Another approach would be to use preg_split to split the string at the matched delimiters while also capturing the delimiters:
$parts = preg_split('/test|test2|test3/i', $string, null, PREG_SPLIT_DELIM_CAPTURE);
The result is an array of alternating non-matching and matching parts.
As far as I know, eregi is deprecated.
You could do something like this:
<?php
$str = 'test check one two test3';
$to_match = array("test", "test2", "test3");
$rep = array();
foreach($to_match as $val){
$rep[$val] = "<$val>";
}
echo strtr($str, $rep);
?>
This too allows you to easily add more strings to replace.
Hi following function used to found the any word from string
<?php
function searchword($string, $words)
{
$matchFound = count($words);// use tha no of word you want to search
$tempMatch = 0;
foreach ( $words as $word )
{
preg_match('/'.$word.'/',$string,$matches);
//print_r($matches);
if(!empty($matches))
{
$tempMatch++;
}
}
if($tempMatch==$matchFound)
{
return "found";
}
else
{
return "notFound";
}
}
$string = "test check one two test3";
/*** an array of words to highlight ***/
$words = array('test', 'test3');
$string = searchword($string, $words);
echo $string;
?>
If your string is utf-8, you could use preg_replace instead
$string = 'test check one two test3';
$result = preg_replace('/(test3)|(test2)|(test)/ui' , '<$1>' ,$string);
echo $result;
Oviously with this kind of data to match the result will be suboptimal
<test> check one two <test>3
You'll need a longer approach than a direct search and replace with regular expressions (surely if your patterns are prefixes of other patterns)
To begin with, the code you want to enhance does not seem to comply with its initial purpose (not at least in my computer). You can try something like this:
$string = 'test check one two test3';
$result = mb_eregi_replace('(test|test2|test3)', '<\1>', $string);
echo $result;
I've removed the i flag (which of course makes little sense here). Still, you'd still need to make the expression greedy.
As for the original question, here's a little proof of concept:
function replace($match){
$GLOBALS['matches'][] = $match;
return "<$match>";
}
$string = 'test check one two test3';
$matches = array();
$result = mb_eregi_replace('(test|test2|test3)', 'replace(\'\1\')', $string, 'e');
var_dump($result, $matches);
Please note this code is horrible and potentially insecure. I'd honestly go with the preg_replace_callback() solution proposed by Gumbo.

Categories