Need to count matches and replace "nth" match using preg_replace_callback - php

I need to search through some content and enclose certain words with HTML. The words to search for are in an array, and I want to only replace the 2nd to last occurrence of each set of matches. So, as an example, if there are 5 matches, I want to replace the 4th match. If there are 3 matches, replace the 2nd. If there are 2 matches, replace the 2nd/last match, and if only one match then use that one.
The callback function is external to the function using the callback.
I'm thinking I need an array count -- minus one -- and then pass that number to the callback function and use it there, but maybe there's an easier way to do this?
Simplified functions are:
function repWords($content) {
foreach ($words_array as $w) {
$content = preg_replace_callback('/\b('.$w.'[s]?)\b/S', array(&$this,'addHtml'), $content);
}
return $content;
}
function addHtml($format){
return '<span>' . $format[1] . '</span>'
}
This works as desired to replaced all words it finds, and I can set it up to randomly replace some, but I want to either replace the nth occurrence and/or replace only one occurrence (not always the first occurrence).

Based on your requirements, here is what I came up with:
// needs to be global given that you are using non-anonymous function syntax
$count = 0;
function repWords($content) {
global $count;
$words_array = array('some','content','foobar');
foreach ($words_array as $w) {
$pattern = '~\b('.$w.'s?)\b~';
$count=0;
preg_match_all($pattern,$content,$matches);
$count=count($matches[0]);
if ($count>0)
$content = preg_replace_callback($pattern,'addHtml',$content);
}
return $content;
}
function addHtml ($format) {
global $count;
static $cc=1;
static $pf='';
$val=$format[1];
$cc = ($pf!=$val) ? 1 : ++$cc;
if ((1==$count)||($cc==($count-1)))
$format[1]= '<span>' . $val . '</span>';
$pf=$val;
return $format[1];
}
$content = <<<EOC
this is some content
some more content
this content foobar
EOC;
echo repWords($content);
output
this is <span>some</span> content
some more <span>content</span>
this content <span>foobar</span>
the first "some" is wrapped because there are 2 instances of it
the 2nd "content" is wrapped because there are 3 instances of it
"foobar" is wrapped because there is only 1 instance of it
Note: this utilizes a global variable $count. In general, it's a bad idea to use global variables. But this is one of the few examples where it's grudgingly accepted, due to limitation of php and preg_replace_callback (or any function that you can specify a callback) when you opt to define the callback separately instead of use an anonymous function. If you are on php5.3+ and are willing to make addHtml an anonymous function instead, $count can be passed to the anonymous function with use. Alternatively, if all this is actually in a class, make it a class property and use $this->count instead.
Another note: The very last thing you said was "(not always the first occurrence)" I was somewhat confused by this and took it to possibly mean that you want to somehow be able to specify the nth* instead of it always being "2nd to last (or first if there's only 1" e.g. in my code example perhaps you want to change it to be every 3rd to last word instead of 2nd to last. I asked you to clarify but you didn't respond by the time I posted this solution, so I did not write the code to be flexible for that. However, hopefully you should be able to take this and alter it to suit you if that's what you want. Basically it will involve passing another arg to repWords and it will also involve using another global variable to use in addHtml, where $cc==($count-1) is used.

Related

How to solve this almost easy create_function for PHP?

How do I convert another create_function. The one below
return create_function('$f,$e=null', "return ($parsed_tpl);");
to
$e=null;
return function($f,$e) { return ($parsed_tpl); };
or
return function($f,$e=null;) { return ($parsed_tpl); };
But neither of them are working.
I have tried everything above.
The key part to notice on the original code is this:
"return ($parsed_tpl);"
Note the double-quotes, meaning the variable will be expanded in the string before passing it to create_function. Then the expanded form will form the body of the function.
In other words, the actual code of the created function will be completely different every time - it won't just return the string, it will execute it as PHP code.
To achieve the same effect in current PHP, you need to use the eval function:
return eval("return ($parsed_tpl);");
You also have two mistakes in your attempt:
$e=null isn't an assignment, it's a default value for the parameter; it doesn't need a semicolon
You have to "capture" the $parsed_tpl variable with the use keyword to use it inside the function
The format is this:
$my_anonymous_function =
function($some_param, $next_param='default value', $another='another default')
use ($something_captured, $something_else) {
/* body of function using those 5 variables*/
};

Using anonymous function in php to change multiple variables in outer function

Here is my anonymous function:
$step = function() use ($text,$count,$new_text) {
$new_text .= $text[$count];
$count++;
I'm reading a long text value and scanning for bad characters. If the value of $text[$count] is ok, I want to add it to the new text variable and increase the count by calling $step(). Sure, I could just repeat the two lines over and over in my code, but using an anonymous function seemed so much simpler. The only problem is that it doesn't work. The variables aren't changing in the outer function.
What am I doing wrong. Alternatively, what's a different way to do this if there is one? There has to be a way to abstract a few lines of repeated code throughout a function.
You MUST pass by reference if want a modified version of variable after function is performed, like this:
<?php
$text = 'Some text';
$anon = function() use (&$text) {
$text .= ' and more...' ;
};
$anon();
print $text; // returns: Some text and more...
The use statement just inherit variables from the parent scope.

Accessing parent function scope from anonymous function in PHP

I made this awesome plugin for wordpress to easily add references to blog posts using latex-like tags. It works really well, but there's one nasty detail: I'm using global variables, as I need to change a variable in an anonymous function of which I can't change the passed parameters (it's a callback function).
I tried the use syntax and it works but the variable gets copied into the anonymous function.
Here's the code, shortened to give a quick overview of what I want to do:
// Global variables, ugh...
// I don't want to declare $reflist here!
$reflist = array();
// Implementation of reference parsing.
function parse_references( $str ) {
// Clear array
$reflist = array();
// I want to declare $reflist here!
// Replace all tags
$newstr = preg_replace_callback( "/{.*}/",
function ( $matches ) {
// Find out the tag number to substitute
$tag_number = 5;
// Add to $reflist array
global $reflist;
// I don't want to have to use a global here!
$reflist[] = $tag_number;
return "[$tag_number]";
}, $str );
return $newstr;
}
So does anyone know how to solve this elegantly?
Pass the variable by reference with the use construct. This way, modifying the value of $reflist inside the anonymous function does have an external effect, meaning the original variable's value changes.
$newstr = preg_replace_callback("/{.*}/", function($matches) use (&$reflist) {
$tag_number = 5; // important ----^
$reflist[] = $tag_number;
return "[$tag_number]";
}, $a);

passing indefinite number of arguments to function without using an array

I am having situation where i want to pass variables in php function.
The number of arguments are indefinite. I have to pass in the function without using the array.
Just like normal approach. Comma Separated.
like test(argum1,argum2,argum3.....,..,,.,.,.....);
How i will call the function? Suppose i have an array array(1,2,3,4,5) containing 5 parameters. i want to call the function like func(1,2,3,4,5) . But the question is that, How i will run the loop of arguments , When calling the function. I tried func(implode(',',array)); But it is taking all return string as a one parameters
In the definition, I also want the same format.
I can pass variable number of arguments via array but i have to pass comma separated.
I have to pass comma separated. But at the time of passing i don't know the number of arguments , They are in array.
At the calling side, use call_user_func_array.
Inside the function, use func_get_args.
Since this way you're just turning an array into arguments into an array, I doubt the wisdom of this though. Either function is fine if you have an unknown number of parameters either when calling or receiving. If it's dynamic on both ends, why not just pass the array directly?!
you can use :
$function_args = func_get_args();
inside your test() function definition .
You can just define your function as
function test ()
then use the func_get_args function in php.
Then you can deal with the arguments as an array.
Example
function reverseConcat(){
return implode (" ", array_reverse(func_get_args()));
}
echo reverseConcat("World", "Hello"); // echos Hello World
If you truely want to deal with them as though they where named parameters you could do something like this.
function getDistance(){
$params = array("x1", "y1", "x2", "y2");
$args = func_get_args();
// trim excess params
if (count($args) > count($params) {
$args = array_slice(0, count($params));
} elseif (count($args) < count($params)){
// define missing parameters as empty string
$args = array_pad($args, count($params), "");
}
extract (array_combine($params, $args));
return sqrt(pow(abs($x1-$x2),2) + pow(abs($y1-$y2),2));
}
use this function:
function test() {
$args = func_get_args();
foreach ($args as $arg) {
echo "Arg: $arg\n";
}
}
I'm not sure what you mean by "same format." Do you mean same type, like they all have to be a string? Or do you mean they need to all have to meet some criteria, like if it's a list of phone numbers they need to be (ddd) ddd-dddd?
If it's the latter, you'll have just as much trouble with pre-defined arguments, so I'll assume you mean you want them all to be the same type.
So, going off of the already suggested solution of using func_get_args(), I would also apply array_filter() to ensure the type:
function set_names() {
function string_only($arg) {
return(is_string($arg));
}
$names_provided = func_get_args();
// Now you have an array of the args provided
$names_provided_clean = array_filter($names_provided, "string_only");
// This pulls out any non-string args
$names = array_values($names_provided_clean);
// Because array_filter doesn't reindex, this will reset numbering for array.
foreach($names as $name) {
echo $name;
echo PHP_EOL;
}
}
set_names("Joe", "Bill", 45, array(1,2,3), "Jane");
Notice that I don't do any deeper sanity-checks, so there could be issues if no values are passed in, etc.
You can use array also using explode http://www.php.net/manual/en/function.explode.php.
$separator = ",";
$prepareArray = explode ( $separator , '$argum1,$argum2,$argum3');
but be careful, $argum1,$argum2, etc should not contain , in value. You can overcome this by adding any separator. $separator = "VeryUniqueSeparator";
I don't have code so can't tell exact code. But manipulating this will work as your requirements.

PHP Command Line Arguments and Options

I am writing a small command line application in php.
What is the correct way to handle command line arguments and options?
There seems to be the argv array, $_SERVER['argv'], and getopt but its confusing when to use each?
Also with regards to options i.e. "argument --option" what is the best way to get these?
Arguments, made easy
One day I decided to defeat this monster once and for all. I forged a secret weapon - a function that acts as a storage, a parser and a query function for arguments.
// You can initialize it with a multiline string:
arg("
-a --alpha bool Some explanation about this option
-b --beta bool Beta has some notes too
-n --number int Some number you need for the script
- --douglas int There is no short form of this
-o --others str A string of other things
");
// ... and now you have your arguments nicely wrapped up:
print arg("alpha"); // returns the value of -a or --alpha
print arg("a"); // same thing
print arg(); // returns the whole parsed array
print arg(1); // returns the first unnamed argument
print arg(2); // returns the second unnamed argument
print arg("douglas",42); // value of "douglas", or a reasonable default
Explanation
All you need to do is write the argument list as a multiline string. Four columns, looks like a help, but arg() parses your lines and finds out the arguments automatically.
Separate columns by two or more spaces - just like you would anyway.
Once parsed, each item will be represented by an array of fields, named char, word, type and help, respectively. If there's no short (char) or long (word) version for a parameter, just use a dash. Not for both, obviously.
Types are what they seem: bool means there's no value after the parameter; it's false if missing, true if present. The int and str types mean there must be a value, and int makes sure it's an integer. Optional parameters are not supported. Values can be separated by space or equal sign (i.e. "-a=4" or "-a 4")
After this first call, you have all your arguments neatly organized in a structure (dump it, you'll see) and you can query their values by name or number.
Function arg() has a second parameter for defaults so you'll never have to worry about missing values.
The arg() function itself
function arg($x="",$default=null) {
static $arginfo = [];
/* helper */ $contains = function($h,$n) {return (false!==strpos($h,$n));};
/* helper */ $valuesOf = function($s) {return explode(",",$s);};
// called with a multiline string --> parse arguments
if($contains($x,"\n")) {
// parse multiline text input
$args = $GLOBALS["argv"] ?: [];
$rows = preg_split('/\s*\n\s*/',trim($x));
$data = $valuesOf("char,word,type,help");
foreach($rows as $row) {
list($char,$word,$type,$help) = preg_split('/\s\s+/',$row);
$char = trim($char,"-");
$word = trim($word,"-");
$key = $word ?: $char ?: ""; if($key==="") continue;
$arginfo[$key] = compact($data);
$arginfo[$key]["value"] = null;
}
$nr = 0;
while($args) {
$x = array_shift($args); if($x[0]<>"-") {$arginfo[$nr++]["value"]=$x;continue;}
$x = ltrim($x,"-");
$v = null; if($contains($x,"=")) list($x,$v) = explode("=",$x,2);
$k = "";foreach($arginfo as $k=>$arg) if(($arg["char"]==$x)||($arg["word"]==$x)) break;
$t = $arginfo[$k]["type"];
switch($t) {
case "bool" : $v = true; break;
case "str" : if(is_null($v)) $v = array_shift($args); break;
case "int" : if(is_null($v)) $v = array_shift($args); $v = intval($v); break;
}
$arginfo[$k]["value"] = $v;
}
return $arginfo;
}
// called with a question --> read argument value
if($x==="") return $arginfo;
if(isset($arginfo[$x]["value"])) return $arginfo[$x]["value"];
return $default;
}
I hope this helps a lot of lost souls out there, like I was. May this little function shed a light upon the beauty of not having to write a help AND a parser and keeping them in sync... Also, once parsed, this approach is lightning fast since it caches the variables so you can call it as many times as you want. It acts like a superglobal.
Also available on my GitHub Gist.
You can retrieve the "raw" arguments using $argv.
See also: http://www.php.net/manual/de/reserved.variables.argv.php
Example: php file.php a b c
$argv will contain "file.php", "a", "b" and "c".
Use getopts to get the parameters "parsed", PHP will do the dirty job for you. So it's probably the best way to go in your case as you want to pass the parameters with --options.
Have a close look at http://www.php.net/manual/de/function.getopt.php
It describes the function well.

Categories