PHP preg_replace limit not working? - php

I have a file that contains several colors as hexadecimals (i.e. #000 or #ffffff) and I'm trying to replace each of them with #varN where N is a number that increments with each replacement done. I believed my code does that, but $count always returns 196 after doing the placement even though I put the limit to 1 so count should never goes past 1. This results an endless loop. Why isn't it the limit working and what I can do to get the desired output?
$list = file($filepath.$file, FILE_USE_INCLUDE_PATH);
$pattern = "/#((([a-f]|[A-F]|[0-9]){6})|(([a-f]|[A-F]|[0-9]){3}))/";
$replaceVar = "#var";
$replaceNum = 0;
$count = 1;
while($count != 0){ //never ends
$replacement = $replaceVar.$replaceNum;
$output = preg_replace($pattern,$replacement,$list,1,$count);
$replaceNum++;
echo $replaceNum." ".$count."\n"; //returns $replaceNum and 196
}
file_put_contents($filepath.'/new'.$file,$output,FILE_USE_INCLUDE_PATH);
Example input file:
#000000
#111111
#123456
#abcdef
#123
#abc
Example output file:
#var1
#var2
#var3
#var4
#var5
#var6

You don't use the good function to do that for several reasons, I suggest you to use preg_replace_callback instead. Example:
$source = file_get_contents($yourfile);
$pattern = '~#[[:xdigit:]]{3}(?:[[:xdigit:]]{3})?~';
$count = 0;
$output = preg_replace_callback($pattern,
function ($m) use (&$count) { return '#var' . ++$count; },
$source);

You are always replacing on $list, but writing the result of the replacement to $output:
$output = preg_replace($pattern,$replacement,$list,1,$count);
This means, your $list will ALWAYS contain 1 match, when it starts with 1 - its never modified. Therefore your while will run without an end. You have to REPLACE it inside the same string you are scanning for more hits:
$list = preg_replace($pattern,$replacement,$list,1,$count);
now - at one point - $count should become zero, and $list contain no more unwanted matches.

Related

Issue with searching for a word from 2 differents text files

I've been pulling my hair for the couple last hours, I can't figure this out, all I'm trying to do is to take 1 line from list.txt file then search for a match in source.txt file, here is my code
<?php
//Let's open the file
$list = #fopen("files/list.txt", "r");
$source = #fopen("files/source.txt", "r");
//I'm calculating number of lines in list.txt
$no_of_lines = count(file('files/list.txt'));
//I created 2 loops
//The first loop is to repeat the process based on the total number of lines in list.txt
//the second loop is the extract only 1 entry from the list.txt and search for a match in source.txt
for ($x=1; $x <= $no_of_lines ; $x++) {
for ($i=1; $i <= 1 ; $i++) {
$getLine = fgets($list);
$matches = array();
if ($source)
{
while (!feof($source))
{
$buffer = fgets($source);
if(strpos($source, $getLine) !== FALSE)
$matches[] = $buffer;
}
fclose($source);
}
}
}
//show results:
print_r($matches);
+source.txt has numbers from 1 to 100, each number in a separate line.
+list.txt has these numbers:
5
20000
1000000
87456
Current Error: Warning: strpos() expects parameter 1 to be string, resource given in C:\laragon\www\SearchFind\test2.php on line 26
I've tried many stackexchange solutions but nothing worked out.
There's no need to count the lines in list.txt first. Just loop calling fgets() until you get to the end of the file.
You need to initialize the $matches array before the loop. Otherwise you clear it out before searching for each number, and the final value will just be the matches for the last number in list.txt.
You need to reopen source.txt each time through the outer loop so you can read from it again.
You should be searching $buffer in the strpos() call, not $source (that's the reason for the error you're getting).
Don't use while (!feof($source)). Use while ($buffer = fgets($source)). fgets() returns FALSE when you get to the end of the file.
You need to use rtrim($getLine) and `rtrim($buffer) to remove the newline at the end of the line, so you're just searching for the number.
for ($i=1; $i <= 1 ; $i++) serves no purpose at all. It just loops one time, which is the same as not looping at all.
If you want to match the whole line, not just look for a substring, use === to compare $getLine and $buffer, not strpos().
<?php
//Let's open the file
$list = fopen("list.txt", "r");
$matches = array();
if ($list) {
while ($getLine = fgets($list)) {
$getLine = rtrim($getLine); // remove newline
$source = fopen("source.txt", "r");
if ($source)
{
while ($buffer = fgets($source)) {
$buffer = rtrim($buffer);
if($buffer === $getLine)
$matches[] = $buffer;
}
fclose($source);
}
}
}
//show results:
print_r($matches);

adding string after every 'n' number of lines in a file using php

I have a .lst(playlist) file with around 1800 lines of data. Each line contains a URL to an audio file that is being played on my radio station.
The thing is I need to add URLs to some Advertisements after every 'n' number of lines in that file.
There are 10 URLs of advertisements from which 1 URL needs to be added after every 5 lines.
Example: URLs of Advertisements are in this format: file1.mp3, file2.mp3 ... file10.mp3
They will be added like this: file1.mp3 on line 5, file2.mp3 on line 10, file3.mp3 on line 15 and so on. After file10.mp3 has been inserted, it will again start from file1.mp3. I hope you understand.
So far I have managed to cook up the following code, but it only takes up one string to be added and have to manually tell the line number on which the string will be added. Unable to figure out the looping logic to do the aforementioned work.
$url = "/home/station/content/file1.mp3";
$line_number = 5; //add above URL on line number 5
$contents = file('playlist.lst', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if($line_number > sizeof($contents)) {
$line_number = sizeof($contents) + 1;
}
array_splice($contents, $line_number-1, 0, array($url));
$contents = implode("\n", $contents);
file_put_contents('playlist.lst', $contents);
How can I achieve this ?
You can use array_chunk to split your array into $line_number. Then, use array_map() to add your advertisements to each group. Finally, you could reduce to a linear array. You can format the $url using sprintf().
$url = "/home/station/content/file%d.mp3"; // use %d to use using sprintf()
$line_number = 5; //add above URL on line number 5
$counter = 1;
$contents = file('playlist.lst', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
// split in group of $line_number
$groups = array_chunk($contents, $line_number);
// for each group:
$groups = array_map(function($arr) use ($url, &$counter) { // pass $counter as reference
// format the link
$adv = sprintf($url, $counter++) ;
// restart to 1 if greater than 10
if ($counter > 10) $counter = 1;
// append to group
$arr[] = $adv;
return $arr ;
},$groups);
// transform to linear array
$contents = array_reduce($groups, 'array_merge', array());
// save new file
file_put_contents('playlist.lst', implode("\n", $contents));
You could do it this way, with a simple loop:
//changing it to a "model" string, we are going to add the correct file number later
$url = "/home/station/content/file";
$contents = file('playlist.lst', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$count = 0;
$AddCount = 1;
//Loop until there is nothing left in our radio list
while ($count < sizeof($contents)) {
//if we have a multiple of 5, we are inserting an ad
if (($count % 5) == 0) {
// to know wich ad to add we use our AddCounter
$tmp = $url . strval($AddCount) . ".mp3";
//As suggested by Justinas, don't forget that each time you had a line you need to also increase the index for the next one using $count%5 to know how many lines you already added
array_splice($contents, $count - 1 + ($count % 5) , 0, array($tmp));
$AddCount += 1;
if ($AddCount > 10)
$AddCount = 1;
}
$count += 1;
}
$contents = implode("\n", $contents);
file_put_contents('playlist.lst', $contents);
This way, you don't even have to handle the advertisements file selection yourself as long as they are all formated like you said.
You should do a loop in such way.
$line_number = 5;
$line_count = 0;
for($i=0; $i < sizeof($contents); $i++)
{
$line_count = $line_count +1; // start with Line 1 that +1
if($line_count == $line_number)
{
// do your magic code of adding a line
$line_count = 0; // refresh counting from the beginning
}
}
You don't need to handle each line in the file one at a time.
Leave the file contents as its original string, inject placeholders with regex, then replace those placeholders with your ad array strings.
Code: (Basic Demo)
$filename = 'playlist.lst';
file_put_contents(
$filename,
vsprintf(
preg_replace(
'/(?:.+(\R)){5}\K/', // match (and forget) 5 lines, capture the newline character
'%s\1', // inject the placeholder following the the newline character
file_get_contents($filename),
count($ads) // make the exact number of needed placeholders
),
$ads // replace the placeholders with the ad strings
)
);

adding an increment to a variable [duplicate]

I have a string formed up by numbers and sometimes by letters.
Example AF-1234 or 345ww.
I have to get the numeric part and increment it by one.
how can I do that? maybe with regex?
You can use preg_replace_callback as:
function inc($matches) {
return ++$matches[1];
}
$input = preg_replace_callback("|(\d+)|", "inc", $input);
Basically you match the numeric part of the string using the regex \d+ and replace it with the value returned by the callback function which returns the incremented value.
Ideone link
Alternatively this can be done using preg_replace() with the e modifier as:
$input = preg_replace("|(\d+)|e", "$1+1", $input);
Ideone link
If the string ends with numeric characters it is this simple...
$str = 'AF-1234';
echo $str++; //AF-1235
That works the same way with '345ww' though the result may not be what you expect.
$str = '345ww';
echo $str++; //345wx
#tampe125
This example is probably the best method for your needs if incrementing string that end with numbers.
$str = 'XXX-342';
echo $str++; //XXX-343
Here is an example that worked for me by doing a pre increment on the value
$admNo = HF0001;
$newAdmNo = ++$admNo;
The above code will output HF0002
If you are dealing with strings that have multiple number parts then it's not so easy to solve with regex, since you might have numbers overflowing from one numeric part to another.
For example if you have a number INV00-10-99 which should increment to INV00-11-00.
I ended up with the following:
for ($i = strlen($string) - 1; $i >= 0; $i--) {
if (is_numeric($string[$i])) {
$most_significant_number = $i;
if ($string[$i] < 9) {
$string[$i] = $string[$i] + 1;
break;
}
// The number was a 9, set it to zero and continue.
$string[$i] = 0;
}
}
// If the most significant number was set to a zero it has overflowed so we
// need to prefix it with a '1'.
if ($string[$most_significant_number] === '0') {
$string = substr_replace($string, '1', $most_significant_number, 0);
}
Here's some Python code that does what you ask. Not too great on my PHP, but I'll see if I can convert it for you.
>>> import re
>>> match = re.match(r'(\D*)(\d+)(\D*)', 'AF-1234')
>>> match.group(1) + str(int(match.group(2))+1) + match.group(3)
'AF-1235'
This is similar to the answer above, but contains the code inline and does a full check for the last character.
function replace_title($title) {
$pattern = '/(\d+)(?!.*\d)+/';
return preg_replace_callback($pattern, function($m) { return ++$m[0]; }, $title);
}
echo replace_title('test 123'); // test 124
echo replace_title('test 12 3'); // test 12 4
echo replace_title('test 123 - 2'); // test 123 - 3
echo replace_title('test 123 - 3 - 5'); // test 123 - 3 - 6
echo replace_title('123test'); // 124test

PHP/MySQL Limiting characters outputted from a field

When I get a database array, sometimes there is fields with too much data for my results list page, which is suppose to give just a short description. How do I limit the characters count to something like 100.
This is my array for the loop:
<?php
$i = 1;
while ($row = mysql_fetch_array($result)) {
?>
This is my echo statement:
<?php echo $row['description']; ?>
You can use substr like this:
<?php echo substr($row['description'], 0, 100); ?>
But it might be better—depending on your application needs—to do the limiting when making the initial MySQL query using SUBSTR which behaves the same way, but in MySQL.
SELECT SUBSTR(example_field, 1, 100)
FROM example_table
WHERE example_field IS NOT NULL
LIMIT 1
;
That MySQL script basically means return the substring of example_field starting from the first (1) character and going 100 characters in.
This might be better in some cases since if you are limiting text length to 100 characters, why grab the data for fields that might have 1,000+ characters? That would definitely bog down your PHP script in many cases. Handling that in MySQL is the best way to nip it in the bud if your app just needs 100 characters returned.
You can try with substr()
<?php echo substr($row['description'],0,100); ?>
OR
function truncate($input, $maxWords, $maxChars)
{
$words = preg_split('/\s+/', $input);
$words = array_slice($words, 0, $maxWords);
$words = array_reverse($words);
$chars = 0;
$truncated = array();
while(count($words) > 0)
{
$fragment = trim(array_pop($words));
$chars += strlen($fragment);
if($chars > $maxChars) break;
$truncated[] = $fragment;
}
$result = implode($truncated, ' ');
return $result . ($input == $result ? '' : '...');
}
// try with cuctom function truncate() , it help to cut description by words.
<?php echo truncate($row['description'],5,200); ?>
Like the other answers, you should use substr, but you can use it in combination with strpos so that when you shorten the string, you stop after a complete word instead of interrupting a word itself.
$pos = strpos($row['description'], ' ', 100);
echo substr($row['description'], $pos);
I would do it in the SQL query. It's better to limit the results returned which reduces I/O and network throughput by not returning data from the database you're not going to use. In your SQL query do:
SELECT LEFT(description, 100) AS description
,....
FROM ...
Just in case someone is interested in another code snippets on how to limit character output with dots, I use this and it works well for me
CONCAT(SUBSTR(<column_name_here>, 1, 100),'...... Read More') AS Column_Name

Search for pattern in a string

Pattern search within a string.
for eg.
$string = "111111110000";
FindOut($string);
Function should return 0
function FindOut($str){
$items = str_split($str, 3);
print_r($items);
}
If I understand you correctly, your problem comes down to finding out whether a substring of 3 characters occurs in a string twice without overlapping. This will get you the first occurence's position if it does:
function findPattern($string, $minlen=3) {
$max = strlen($string)-$minlen;
for($i=0;$i<=$max;$i++) {
$pattern = substr($string,$i,$minlen);
if(substr_count($string,$pattern)>1)
return $i;
}
return false;
}
Or am I missing something here?
What you have here can conceptually be solved with a sliding window. For your example, you have a sliding window of size 3.
For each character in the string, you take the substring of the current character and the next two characters as the current pattern. You then slide the window up one position, and check if the remainder of the string has what the current pattern contains. If it does, you return the current index. If not, you repeat.
Example:
1010101101
|-|
So, pattern = 101. Now, we advance the sliding window by one character:
1010101101
|-|
And see if the rest of the string has 101, checking every combination of 3 characters.
Conceptually, this should be all you need to solve this problem.
Edit: I really don't like when people just ask for code, but since this seemed to be an interesting problem, here is my implementation of the above algorithm, which allows for the window size to vary (instead of being fixed at 3, the function is only briefly tested and omits obvious error checking):
function findPattern( $str, $window_size = 3) {
// Start the index at 0 (beginning of the string)
$i = 0;
// while( (the current pattern in the window) is not empty / false)
while( ($current_pattern = substr( $str, $i, $window_size)) != false) {
$possible_matches = array();
// Get the combination of all possible matches from the remainder of the string
for( $j = 0; $j < $window_size; $j++) {
$possible_matches = array_merge( $possible_matches, str_split( substr( $str, $i + 1 + $j), $window_size));
}
// If the current pattern is in the possible matches, we found a duplicate, return the index of the first occurrence
if( in_array( $current_pattern, $possible_matches)) {
return $i;
}
// Otherwise, increment $i and grab a new window
$i++;
}
// No duplicates were found, return -1
return -1;
}
It should be noted that this certainly isn't the most efficient algorithm or implementation, but it should help clarify the problem and give a straightforward example on how to solve it.
Looks like you more want to use a sub-string function to walk along and check every three characters and not just break it into 3
function fp($s, $len = 3){
$max = strlen($s) - $len; //borrowed from lafor as it was a terrible oversight by me
$parts = array();
for($i=0; $i < $max; $i++){
$three = substr($s, $i, $len);
if(array_key_exists("$three",$parts)){
return $parts["$three"];
//if we've already seen it before then this is the first duplicate, we can return it
}
else{
$parts["$three"] = i; //save the index of the starting position.
}
}
return false; //if we get this far then we didn't find any duplicate strings
}
Based on the str_split documentation, calling str_split on "1010101101" will result in:
Array(
[0] => 101
[1] => 010
[2] => 110
[3] => 1
}
None of these will match each other.
You need to look at each 3-long slice of the string (starting at index 0, then index 1, and so on).
I suggest looking at substr, which you can use like this:
substr($input_string, $index, $length)
And it will get you the section of $input_string starting at $index of length $length.
quick and dirty implementation of such pattern search:
function findPattern($string){
$matches = 0;
$substrStart = 0;
while($matches < 2 && $substrStart+ 3 < strlen($string) && $pattern = substr($string, $substrStart++, 3)){
$matches = substr_count($string,$pattern);
}
if($matches < 2){
return null;
}
return $substrStart-1;

Categories