PHP preg_replace inside for loop - php

I'm currently trying out this PHP preg_replace function and I've run into a small problem. I want to replace all the tags with a div with an ID, unique for every div, so I thought I would add it into a for loop. But in some strange way, it only do the first line and gives it an ID of 49, which is the last ID they can get. Here's my code:
$res = mysqli_query($mysqli, "SELECT * FROM song WHERE id = 1");
$row = mysqli_fetch_assoc($res);
mysqli_set_charset("utf8");
$lyric = $row['lyric'];
$lyricHTML = nl2br($lyric);
$lines_arr = preg_split('[<br />]',$lyricHTML);
$lines = count($lines_arr);
for($i = 0; $i < $lines; $i++) {
$string = preg_replace(']<br />]', '</h4><h4 id="no'.$i.'">', $lyricHTML, 1);
echo $i;
}
echo '<h4>';
echo $string;
echo '</h4>';
How it works is that I have a large amount of text in my database, and when I add it into the lyric variable, it's just plain text. But when I nl2br it, it gets after every line, which I use here. I get the number of by using the little "lines_arr" method as you can see, and then basically iterate in a for loop.
The only problem is that it only outputs on the first line and gives that an ID of 49. When I move it outside the for loop and removes the limit, it works and all lines gets an <h4> around them, but then I don't get the unique ID I need.
This is some text I pulled out from the database
Mama called about the paper turns out they wrote about me
Now my broken heart´s the only thing that's broke about me
So many people should have seen what we got going on
I only wanna put my heart and my life in songs
Writing about the pain I felt with my daddy gone
About the emptiness I felt when I sat alone
About the happiness I feel when I sing it loud
He should have heard the noise we made with the happy crowd
Did my Gran Daddy know he taught me what a poem was
How you can use a sentence or just a simple pause
What will I say when my kids ask me who my daddy was
I thought about it for a while and I'm at a loss
Knowing that I´m gonna live my whole life without him
I found out a lot of things I never knew about him
All I know is that I´ll never really be alone
Cause we gotta lot of love and a happy home
And my goal is to give every line an <h4 id="no1">TEXT</h4> for example, and the number after no, like no1 or no4 should be incremented every iteration, that's why I chose a for-loop.

Looks like you need to escape your regexp
preg_replace('/\[<br \/\]/', ...);
Really though, this is a classic XY Problem. Instead of asking us how to fix your solution, you should ask us how to solve your problem.
Show us some example text in the database and then show us how you would like it to be formatted. It's very likely there's a better way.
I would use array_walk for this. ideone demo here
$lines = preg_split("/[\r\n]+/", $row['lyric']);
array_walk($lines, function(&$line, $idx) {
$line = sprintf("<h4 id='no%d'>%s</h4>", $idx+1, $line);
});
echo implode("\n", $lines);
Output
<h4 id="no1">Mama called about the paper turns out they wrote about me</h4>
<h4 id="no2">Now my broken heart's the only thing that's broke about me</h4>
<h4 id="no3">So many people should have seen what we got going on</h4>
...
<h4 id="no16">Cause we gotta lot of love and a happy home</h4>
Explanation of solution
nl2br doesn't really help us here. It converts \n to <br /> but then we'd just end up splitting the string on the br. We might as well split using \n to start with. I'm going to use /[\r\n]+/ because it splits one or more \r, \n, and \r\n.
$lines = preg_split("/[\r\n]+/", $row['lyric']);
Now we have an array of strings, each containing one line of lyrics. But we want to wrap each string in an <h4 id="noX">...</h4> where X is the number of the line.
Ordinarily we would use array_map for this, but the array_map callback does not receive an index argument. Instead we will use array_walk which does receive the index.
One more note about this line, is the use of &$line as the callback parameter. This allows us to alter the contents of the $line and have it "saved" in our original $lyrics array. (See the Example #1 in the PHP docs to compare the difference).
array_walk($lines, function(&$line, $idx) {
Here's where the h4 comes in. I use sprintf for formatting HTML strings because I think they are more readable. And it allows you to control how the arguments are output without adding a bunch of view logic in the "template".
Here's the world's tiniest template: '<h4 id="no%d">%s</h4>'. It has two inputs, %d and %s. The first will be output as a number (our line number), and the second will be output as a string (our lyrics).
$line = sprintf('<h4 id="no%d">%s</h4>', $idx+1, $line);
Close the array_walk callback function
});
Now $lines is an array of our newly-formatted lyrics. Let's output the lyrics by separating each line with a \n.
echo implode("\n", $lines);
Done!

If your text in db is in every line why just not explode it with \n character?
Always try to find a solution without using preg set of functions, because they are heavy memory consumers:
I would go lke this:
$lyric = $row['lyric'];
$lyrics =explode("\n",$lyrics);
$lyricsHtml=null;
$i=0;
foreach($lyrics as $val){
$i++;
$lyricsHtml[] = '<h4 id="no'.$i.'">'.$val.'</h4>';
}
$lyricsHtml = implode("\n",$lyricsHtml);

An other way with preg_replace_callback:
$id = 0;
$lyric = preg_replace_callback('~(^)|$~m',
function ($m) use (&$id) {
return (isset($m[1])) ? '<h4 id="no' . ++$id . '">' : '</h4>'; },
$lyric);

Related

I am getting a error using php (str_replace)

I am getting a error while using php str_replace function.
I am reading out a string in a different file a JSON
and if I remove the str_replace part it works without the error but I want to make the ** go to bold if there are any other ways you know you can also just tell that.
<?php
$data = json_decode($readjson, true);
echo "<br/><br<br/>";
foreach ($data as $emp) {
echo str_replace("**","<strong>","$data"), $emp['message']."<br/>";
}
?>
And the output is
Notice: Array to string conversion in C:\Users\k-ver\Dropbox\Other\website stuff or smth\r3mind3r\changelog.php on line 16
Array - Weekley Update - Another great week at our side! We have made enournous advances in synching with the raspberry pi (the computer we are going to host from) and are closer than ever to our promised release We have also been fixing on the mute commands and are very close to making it work, aswell with unmute command.language feature is closing up on complete and about 70% of the bot has the language system working. We also made a new system that should be easier to use for bouth us devs and the translators. All thats left for the release atm is: -finishing synching -fixing the mute command and unmute command -make a functioning permissonlevel system -adding those last 30% of the bot that does not have the translationsystem in place. and the bot will have its huge release! (about time if you asked me)
(it is for a dev log)
and the part I don't understand is the notice and I also don't understand how to fix it
It would be awesome if you guys would like to help me.
This variable should be string value e.g $emp['message'] not the multi-dimensional array $data.
// see this line with $emp['message'] not $data array
str_replace("**","<strong>",$emp['message']);
EDIT: As per comment
<?php
$string = '{
"188762891137056769": {
"message": "\n**- Weekley Update -**\nAnother great week at our side! \nWe have made enournous advances in synching with the raspberry pi *(the computer we are going to host from)* and are closer than ever to our promised release\n\nWe have also been fixing on the mute commands and are very close to making it work, aswell with unmute command.language feature is closing \nup on complete and about 70% of the bot has the language system working. We also made a new system that should be easier to use for bouth \nus devs and the translators.\n\n**All thats left for the release atm is:**\n-finishing synching \n-fixing the mute command and unmute command \n-make a functioning permissonlevel system\n-adding those last 30% of the bot that does not have the translationsystem in place. \n\nand the bot will have its huge release! *(about time if you asked me)*"
}
}';
$array = json_decode($string,1);
$message = $array['188762891137056769']['message'];
$re = '/\*\*(.*?)\*\*/m';
$subst = '<strong>$1</strong>';
echo preg_replace($re, $subst, $message);
?>
DEMO: https://3v4l.org/ovhGq
You used array inside str_replace("**","","$data") this is wrong, how you can fix it just replace $data with $emp
Your code is:
foreach ($data as $emp) {
echo str_replace("**","<strong>","$data"), $emp['message']."<br/>";
}
You see $data is an array, an $emp is the current element within the foreach loop.
So, you should do: str_replace("", "", $emp)
By the way, I see this: $emp['message'], which means $emp is an array too?
Maybe you should post the $readjson variable, so we'll know what type of data is.
If you want to enclose the text between ** with <strong></strong>, you need to use a regex. Here is a little code that does what you want :
function boldify($text) {
return preg_replace('/\*\*((.|\n|\r)*)\*\*/imU', '<strong>$1</strong>', $text);
}
Basically, it uses the function preg_replace to replace according to the regex (the first parameter).
How does this regex work :
1) You have \*\* at the beginning because that's your "opening tag". (* is a special regex character, so it needs escaping.)
2) You have ((.|\n|\r)*).
The inner part : .|\n|\r says "Catch me any character (the .) or (the |) a line feed (the \n) or a carriage return (the \r).".
Then you have the inner part enclosed with (inner part)*. This says "Match the inner part any number of time.".
Finally, you have the "middle part" enclosed with (middle part). This says "Remember what you just caught inside the parentheses, we will need it for the replace.
3) You have \*\* again.
4) All this is enclosed by /regex/imU.
The / are just there to say where the regex actually is.
The imU are flags: i is ignore case, m multiline, U ungreedy.
i are m are pretty straightforward, but U says "catch the smallest group possible".
As the second parameter you have '<strong>$1</strong>'. $1 is the group we remember from 2).
The third parameter is the subject.
I hope it was clear.
You can just use it like this :
echo boldify($emp['message']);

PHP variables look the same but are not equal (I'm confused)

OK, so I shave my head, but if I had hair I wouldn't need a razor because I'd have torn it all out tonight. It's gone 3am and what looked like a simple solution at 00:30 has become far from it.
Please see the code extract below..
$psusername = substr($list[$count],16);
if ($psusername == $psu_value){
$answer = "YES";
}
else {
$answer = "NO";
}
$psusername holds the value "normann" which is taken from a URL in a text based file (url.db)
$psu_value also holds the value "normann" which is retrieved from a cookie set on the user's computer (or a parameter in the browser address bar - URL).
However, and I'm sure you can guess my problem, the variable $answer contains "NO" from the test above.
All the PHP I know I've picked up from Google searches and you guys here, so I'm no expert, which is perhaps evident.
Maybe this is a schoolboy error, but I cannot figure out what I'm doing wrong. My assumption is that the data types differ. Ultimately, I want to compare the two variables and have a TRUE result when they contain the same information (i.e normann = normann).
So if you very clever fellows can point out why two variables echo what appears to be the same information but are in fact different, it'd be a very useful lesson for me and make my users very happy.
Do they echo the same thing when you do:
echo gettype($psusername) . '\n' . gettype($psu_value);
Since i can't see what data is stored in the array $list (and the index $count), I cannot suggest a full solution to yuor problem.
But i can suggest you to insert this code right before the if statement:
var_dump($psusername);
var_dump($psu_value);
and see why the two variables are not identical.
The var_dump function will output the content stored in the variable and the type (string, integer, array ec..), so you will figure out why the if statement is returning false
Since it looks like you have non-printable characters in your string, you can strip them out before the comparison. This will remove whatever is not printable in your character set:
$psusername = preg_replace("/[[:^print:]]/", "", $psusername);
0D 0A is a new line. The first is the carriage return (CR) character and the second is the new line (NL) character. They are also known as \r and \n.
You can just trim it off using trim().
$psusername = trim($psusername);
Or if it only occurs at the end of the string then rtrim() would do the job:
$psusername = rtrim($psusername);
If you are getting the values from the file using file() then you can pass FILE_IGNORE_NEW_LINES as the second argument, and that will remove the new line:
$contents = file('url.db', FILE_IGNORE_NEW_LINES);
I just want to thank all who responded. I realised after viewing my logfile the outputs in HEX format that it was the carriage return values causing the variables to mismatch and a I mentioned was able to resolve (trim) with the following code..
$psusername = preg_replace("/[^[:alnum:]]/u", '', $psusername);
I also know that the system within which the profiles and usernames are created allow both upper and lower case values to match, so I took the precaution of building that functionality into my code as an added measure of completeness.
And I'm happy to say, the code functions perfectly now.
Once again, thanks for your responses and suggestions.

How to avoid adding a comma at the last row?

I use the following code to add a comma , at the end of every filename. After this using explode i put the rows on an array.
The images array if I echo it, it shows a.jpg,b.jpg,c.jpg, (please mind the last comma at the end of c.jpg)
How can I avoid adding a comma to the last found row?
<?php
while($image = mysql_fetch_array($images)) {
$images_path .= $image['FILE_NAME'].',';
}
$images_array = explode(",", $images_path);
?>
Make use of rtrim() by providing a comma as the second paramter as character mask
$images_path = rtrim($images_path,","); //<---- The added code
$images_array = explode(",", $images_path);
Why add it to a string and then convert it to an array, if you can add it straight away?
$images_array = array();
while($image = mysql_fetch_array($images)) {
$images_array[] = $image['FILE_NAME'];
}
You do this:
Create your required string
Knowingly introduce a problem that can easily be avoided
Fix the issue you created with more code (generate your required array by splitting the string)
Instead, do this:
Create the required array as you want it, first time
Your current approach will create more problems as your coding style improves, and you have more complex code and are reusing your code (ie this would be in a class).
Like this:
<?php
while($image = mysql_fetch_array($images))
{
$images_array[] = $image['FILE_NAME'];
}
?>
Instead of, how you currently do it, setting a variable with the data from the database, with a comma separating each value (and a rouge one at the end as a result), then exploding the variable into an array, and wanting to remove the end comma..
Just set the array in the first place!
The simplest way from the code you supplied would be
explode(',', $images_path, -1);
in most cases you might reconsider if you really need a string which has been constructed in such a way.
There are going to be a dozen ways to tackle this, some pretty, some not.
Ideally, this is why you use libraries like Apache / Google commons in production code, so that there is standard ways to do things like implode / explode ; split / join; or whatever its called in the language you are in.
With that said, maybe you are writing base code or provide this as a library or maybe just want to know how you handle this.
I don't care for the trim options, they are destructive and you will find yourself one day when you depend upon things of that nature, staring at a code review or debugging a problem and thinking, oh this trim, substring whatever is making assumptions and messing things up, which when in base / shared code is bad to be making.
I would handle this in a more deterministic way of:
(pseudocode)
<?php
$images_path = "";
// Base case
if($image = mysql_fetch_array($images)) {
$images_path .= $image['FILE_NAME'];
}
// If multiple results, iterate
while($image = mysql_fetch_array($images)) {
$images_path .= ',' . $image['FILE_NAME'];
}
$images_array = explode(",", $images_path);
?>
Switch it from appending the , to prepending the comma when there are multiple. Now you never have to remove an extra comma or do any destructive command. Very similar how doing a proof you build a base case and then build on the base.

PHP Extract Single String from List of Values

I need some assistance with php. I have been trying several things for the past several days including str_replace to no avail.
I have a field that may contain from 1 to 20 values, all listed on their own line, there is no html code in that field to separate them and some of the values may have their own spaces in between, so separating by space doesnt work.
What I need is to extract every single string of each line and convert it to code.
<p>For example, my field with values looks like this: </p>
<p><b>lang_fld </b><br>
------
<br>
English<br>
Spanish<br>
German<br>
French</p>
What I have in mind is to extract each line, ex. "English" from that string, and create a line of code like
<img src="images/flags/english.png> English
Basically I want to add the flag graphic to the word
I already tried
echo str_replace('English','<img src="images/flags/english.png>,lang_fld)<br>
...and so on
what I get after going through every single possible value is a bunch error
messages (different every time since I keep making changes - by guessing)
Can someone offer an easier option to do this? Not all 20 values will be in
that areatext field, some may contain just one language, some ten, some all 20,
etc.
Thank you!
Assuming you have all the values on a separate line, you can do explode() on the string to convert it to array of separate items, then loop through the array with foreach and perform any modifications with the single item. After you are done with the items and you would like to get them back together, you can use implode() to combine them into a single string.
A short sample ( you can of course use a for loop, I just prefer foreach here as it shows you better what is happening with the data ):
$text =
"French language
Italian language
English language";
$items = explode( "\n", $text ); // Split by newline ( you can also use "<br>" as separator )
$result = array(); // Modified data will be placed here
foreach ( $items as $item )
{
// Do something with $item
$item = "<img src=\"images/flags/$item.png\"> $item";
$result[] = $item;
}
// Merge them back together
$text = implode("\n", $result);

Is regex the right tool to find a line of HTML?

I have a PHP script that pulls some content off of a server, but the problem is that the line on which the content is changes every day, so I can't just pull a specific line. However, the content is contained within a div that has a unique id. Is it possible (and is it the best way) for regex to search for this unique id and then pass the line of which it's on back to my script?
Example:
HTML file:
<html><head><title>Example</title></head>
<body>
<div id="Alpha"> Blah blah blah </div>
<div id="Beta"> Blah Blah Blah </div>
</body>
</html>
So let's say that I'm looking for the line with an opening div tag with an id of alpha. The code should return 3, because on the third line is the div with the id of alpha.
At the risk of providing more up-votes for Jeff who has already crossed the mountains of madness... see here
The argument rages back and forth, but... it's is a simple one-off or little used script you are writing then sure use regex, if it's more complex and needs to be reliable with little future tweaking then I'd suggest using an HTML parser. HTML is a nasty often non-regular beast to tame. Use the right tool for the job... maybe in your case it's regex, or maybe its a full blown parser.
Generally, NO. But if you are sure that the div will always be one line or there is not another div inside it, you can use it without problem. Something like /<div id=\"mydivid\">(.*?)</div>/ or something similar.
Otherwise, DOMDocument would be a more sane way.
EDIT See from your HTML example. My answer would be "YES". RegEx is a very good tool for this.
I assume that you have the HTML as a continuous text not as lines (which will be slightly different). I also assume that you want the line number more that the line content.
Here is a rought PHP code to extract it. (just to give some idea)
$HTML =
"<html><head><title>Example</title></head>
<body>
<div id=\"Alpha\"> Blah blah blah </div>
<div id=\"Beta\"> Blah Blah Blah </div>
</body>
</html>";
$ID = "Alpha";
function GetLineOfDIV($HTML, $ID) {
$RegEx_Alpha = '/\n(<div id="'.$ID.'">.*?<\/div>)\n/m';
$Index = preg_match($RegEx_Alpha, $HTML, $Match, PREG_OFFSET_CAPTURE);
$Match = $Match[1]; // Only the one in '(...)'
if ($Match == "")
return -1;
//$MatchStr = $Match[0]; Since you do not want it, so we comment it out.
$MatchOffset = $Match[1];
$StartLines = preg_split("/\n/", $HTML, -1, PREG_SPLIT_OFFSET_CAPTURE);
foreach($StartLines as $I => $StartLine) {
$LineOffset = $StartLine[1];
if ($MatchOffset <= $LineOffset)
return $I + 1;
}
return count($StartLines);
}
echo GetLineOfDIV($HTML, $ID);
I hope I give you some idea.
According to Jeff Atwood, you should never parse HTML using regex.
Since the line number is important to you here and not the actual contents of the div, I'd be inclined not to use regex at all. I'd probably explode() the string into an array and loop through that array looking for your marker. Like so:
<?php
$myContent = "[your string of html here]";
$myArray = explode("\n", $myContent);
$arraylen = count($myArray); // So you don't waste time counting the array at every loop
$lineNo = 0;
for($i = 0; $i < $arraylen; $i++)
{
$pos = strpos($myArray[$i], 'id="Alpha"');
if($pos !== false)
{
$lineNo = $i+1;
break;
}
}
?>
Disclaimer: I haven't got a php installation readily available to test this so some debugging may be required.
Hope this helps as I think it's probably just going to be a waste of time for you to implement a parsing engine just to do something so simple - especially if it's a one-off.
Edit: if the content is impotant to you at this stage too then you can use this in combination with the other answers which provide an adequate regex for the job.
Edit #2: Oh what the hey... here's my two cents:
"/<div.*?id=\"Alpha\".*?>.*?(<div.*//div>)*.*?//div>/m"
The (<div.*//div>) tells the regex engine that it may find nested div tags and to just incorporate them into the match if it finds them rather than just stopping at the first </div>. However this only solves the problem if there is only one level of nesting. If there's more, then regex is not for you sorry :(.
The /m also makes the regex engine ignore linebreaks so you don't have to dirty up your expressions with [\S\s] everywhere.
Again, sorry, I've no environment to test this in at the moment so you may need to debug.
Cheers
Iain
The fact that a unique id is involved, sounds promising, but since it will be a DIV, and not necessarily a single line of HTML, it will be difficult to construct a regular expression, and the usual objections to parsing HTML with regexes apply.
Not recommended.
Instead of RegEx, use a parser that is made especially to handle (messy) HTML. This will make your application less brittle in case the HTML changes slightly, and you don't have to hand-craft custom RegEx each time you want to pull out a new piece of data.
See this Stack Overflow page: Mature HTML Parsers for PHP
#OP since your requirement is that easy, you can just use string methods
$f = fopen("file","r");
if($f){
$s="";
while( !feof($f) ){
$i+=1;
$line = fgets($f,4096);
if (stripos($line,'<div id="Alpha">')!==FALSE){
print "line number: $i\n";
}
}
fclose($f);
}

Categories