New line to paragraph function - php

I have this interesting function that I'm using to create new lines into paragraphs. I'm using it instead of the nl2br() function, as it outputs better formatted text.
function nl2p($string, $line_breaks = true, $xml = true) {
$string = str_replace(array('<p>', '</p>', '<br>', '<br />'), '', $string);
// It is conceivable that people might still want single line-breaks
// without breaking into a new paragraph.
if ($line_breaks == true)
return '<p>'.preg_replace(array("/([\n]{2,})/i", "/([^>])\n([^<])/i"), array("</p>\n<p>", '<br'.($xml == true ? ' /' : '').'>'), trim($string)).'</p>';
else
return '<p>'.preg_replace(
array("/([\n]{2,})/i", "/([\r\n]{3,})/i","/([^>])\n([^<])/i"),
array("</p>\n<p>", "</p>\n<p>", '<br'.($xml == true ? ' /' : '').'>'),
trim($string)).'</p>';
}
The problem is that whenever I try to create a single line break, it inadvertently removes the first character of the paragraph below it. I'm not familiar enough with regex to understand what is causing the problem.

Here is another approach that doesn't use regular expressions. Note, this function will remove any single line-breaks.
function nl2p($string)
{
$paragraphs = '';
foreach (explode("\n", $string) as $line) {
if (trim($line)) {
$paragraphs .= '<p>' . $line . '</p>';
}
}
return $paragraphs;
}
If you only need to do this once in your app and don't want to create a function, it can easily be done inline:
<?php foreach (explode("\n", $string) as $line): ?>
<?php if (trim($line)): ?>
<p><?=$line?></p>
<?php endif ?>
<?php endforeach ?>

The problem is with your match for single line breaks. It matches the last character before the line break and the first after. Then you replace the match with <br>, so you lose those characters as well. You need to keep them in the replacement.
Try this:
function nl2p($string, $line_breaks = true, $xml = true) {
$string = str_replace(array('<p>', '</p>', '<br>', '<br />'), '', $string);
// It is conceivable that people might still want single line-breaks
// without breaking into a new paragraph.
if ($line_breaks == true)
return '<p>'.preg_replace(array("/([\n]{2,})/i", "/([^>])\n([^<])/i"), array("</p>\n<p>", '$1<br'.($xml == true ? ' /' : '').'>$2'), trim($string)).'</p>';
else
return '<p>'.preg_replace(
array("/([\n]{2,})/i", "/([\r\n]{3,})/i","/([^>])\n([^<])/i"),
array("</p>\n<p>", "</p>\n<p>", '$1<br'.($xml == true ? ' /' : '').'>$2'),
trim($string)).'</p>';
}

I also wrote a very simple version:
function nl2p($text)
{
return '<p>' . str_replace(['\r\n', '\r', '\n'], '</p><p>', $text) . '</p>';
}

#Laurent's answer wasn't working for me - the else statement was doing what the $line_breaks == true statement should have been doing, and it was making multiple line breaks into <br> tags, which PHP's native nl2br() already does.
Here's what I managed to get working with the expected behavior:
function nl2p( $string, $line_breaks = true, $xml = true ) {
// Remove current tags to avoid double-wrapping.
$string = str_replace( array( '<p>', '</p>', '<br>', '<br />' ), '', $string );
// Default: Use <br> for single line breaks, <p> for multiple line breaks.
if ( $line_breaks == true ) {
$string = '<p>' . preg_replace(
array( "/([\n]{2,})/i", "/([\r\n]{3,})/i", "/([^>])\n([^<])/i" ),
array( "</p>\n<p>", "</p>\n<p>", '$1<br' . ( $xml == true ? ' /' : '' ) . '>$2' ),
trim( $string ) ) . '</p>';
// Use <p> for all line breaks if $line_breaks is set to false.
} else {
$string = '<p>' . preg_replace(
array( "/([\n]{1,})/i", "/([\r]{1,})/i" ),
"</p>\n<p>",
trim( $string ) ) . '</p>';
}
// Remove empty paragraph tags.
$string = str_replace( '<p></p>', '', $string );
// Return string.
return $string;
}

Here's an approach that comes with a reverse method to replace paragraphs back to regular line breaks and vice versa.
These are useful to use when building a form input. When saving a users input you may want to convert line breaks to paragraph tags, however when editing the text in a form, you may not want the user to see any html characters. Then we would replace the paragraphs back to line breaks.
// This function will convert newlines to HTML paragraphs
// without paying attention to HTML tags. Feed it a raw string and it will
// simply return that string sectioned into HTML paragraphs
function nl2p($str) {
$arr=explode("\n",$str);
$out='';
for($i=0;$i<count($arr);$i++) {
if(strlen(trim($arr[$i]))>0)
$out.='<p>'.trim($arr[$i]).'</p>';
}
return $out;
}
// Return paragraph tags back to line breaks
function p2nl($str)
{
$str = preg_replace("/<p[^>]*?>/", "", $str);
$str = str_replace("</p>", "\r\n", $str);
return $str;
}

Expanding upon #NaturalBornCamper's solution:
function nl2p( $text, $class = '' ) {
$string = str_replace( array( "\r\n\r\n", "\n\n" ), '</p><p>', $text);
$string = str_replace( array( "\r\n", "\n" ), '<br />', $string);
return '<p' . ( $class ? ' class="' . $class . '"' : '' ) . '>' . $string . '</p>';
}
This takes care of both double line breaks by converting them to paragraphs, and single line breaks by converting them to <br />

Just type this between your lines:
echo '<br>';
This will give you a new line.

Related

Migrate create_function() which is not supported since PHP 7.2

I have 'create_function() in my PHP code:
function encode_code_in_comment( $source ) { $encoded = preg_replace_callback( '/\[(php|html|javascript|css|nginx|apache|terminal)\](.*?)\[\/\1\]/ims',
create_function(
'$matches',
'$matches[2] = preg_replace(
array("/^[\r|\n]+/i", "/[\r|\n]+$/i"), "",
$matches[2]);
return "<pre class=\"language-" . $matches[1] . "\"><code>" . esc_html( $matches[2] ) . "</code></pre>";'
),
$source );
if ( $encoded ) {
return $encoded;
} else {
return $source;
}}
I know that there are duplicates threads about the subject, but nevertheless, i'm really struggling to covert this to an anonymous function. How do i rewrite it?
Your main problem is that your code is badly formatted, making it hard to see where the create_function call begins and ends; here it is with some more logical linebreaks and indents:
function encode_code_in_comment( $source ) {
$encoded = preg_replace_callback(
'/\[(php|html|javascript|css|nginx|apache|terminal)\](.*?)\[\/\1\]/ims',
create_function(
'$matches',
'
$matches[2] = preg_replace(
array("/^[\r|\n]+/i", "/[\r|\n]+$/i"),
"",
$matches[2]
);
return "<pre class=\"language-" . $matches[1] . "\"><code>" . esc_html( $matches[2] ) . "</code></pre>";
'
),
$source
);
if ( $encoded ) {
return $encoded;
} else {
return $source;
}
}
From this and the documentation of create_function, we can see that the created function needs one argument, $matches, and to have a body starting $matches[2] = and ending </pre>";
Looking at the manual for anonymous functions we see that the new syntax is function(arguments) { body }, so instead of:
create_function('$matches', ... )
you want:
function($matches) { ... }
and in between, instead of:
'
$matches[2] = ...
...
... </pre>";
'
you want to just remove the quotes and leave the code:
$matches[2] = ...
...
... </pre>";
The body is in single quotes, and there are no escaped single quotes in there, so the code doesn't need any other changes.

PHP preg_match ignore within certain elements

I'm writing a regex where I need to filter content to format it's typography. So far, my code seems to be filtering out my content properly using preg_replace, but I can't figure out how to avoid this for content wrapped within certain tags, say <pre>.
As a reference, this is to be used within WordPress's the_content filter, so my current code looks like so:
function my_typography( $str ) {
$ignore_elements = array("code", "pre");
$rules = array(
"?" => array("before"=> " ", "after"=>""),
// the others are stripped out for simplicity
);
foreach($rules as $rule=>$params) {
// Pseudo :
// if( !in_array( $parent_tag, $ignore_elements) {
// /Pseudo
$formatted = $params['before'] . $rule . $params['after'];
$str = preg_replace( $rule, $formatted, $str );
// Pseudo :
// }
// /Pseudo
}
return $str;
}
add_filter( 'the_content', 'my_typography' );
Basically:
<p>Was this filtered? I hope so</p>
<pre>Was this filtered? I hope not.</pre>
should become
<p>Was this filtered ? I hope so</p>
<pre>Was this filtered? I hope not.</pre>
You need to wrap search regex with regex delimiter in preg_replace and must call preg_quote to escape all special regex characters such ?, ., *, + etc:
$str = preg_replace( '~' . preg_quote($rule, '~') . '~', $formatted, $str );
Full Code:
function my_typography( $str ) {
$ignore_elements = array("code", "pre");
$rules = array(
"?" => array("before"=> " ", "after"=>""),
// the others are stripped out for simplicity
);
foreach($rules as $rule=>$params) {
// Pseudo :
// if( !in_array( $parent_tag, $ignore_elements) {
// /Pseudo
$formatted = $params['before'] . $rule . $params['after'];
$str = preg_replace( '~' . preg_quote($rule, '~') . '~', $formatted, $str );
// Pseudo :
// }
// /Pseudo
}
return $str;
}
Output:
<p>Was this filtered ? I hope so</p>
<pre>Was this filtered ? I hope not.</pre>

How can I avoid adding href to an overlapping keyword in string?

Using the following code:
$text = "أطلقت غوغل النسخة المخصصة للأجهزة الذكية العاملة بنظام أندرويد من الإصدار “25″ لمتصفحها الشهير كروم.ولم تحدث غوغل تطبيق كروم للأجهزة العاملة بأندرويد منذ شهر تشرين الثاني العام الماضي، وهو المتصفح الذي يستخدمه نسبة 2.02% من أصحاب الأجهزة الذكية حسب دراسة سابقة. ";
$tags = "غوغل, غوغل النسخة, كروم";
$tags = explode(",", $tags);
foreach($tags as $k=>$v) {
$text = preg_replace("/\b{$v}\b/u","$0",$text, 1);
}
echo $text;
Will give the following result:
I love PHP">love PHP</a>, but I am facing a problem
Note that my text is in Arabic.
The way is to do all in one pass. The idea is to build a pattern with an alternation of tags. To make this way work, you must before sort the tags because the regex engine will stop at the first alternative that succeeds (otherwise 'love' will always match even if it is followed by 'php' and 'love php' will never be matched).
To limit the replacement to the first occurence of each word you can remove tag from the array once it has been found and you test if it is always present in the array inside the replacement callback function:
$text = 'I love PHP, I love love but I am facing a problem';
$tagsCSV = 'love, love php, facing';
$tags = explode(', ', $tagsCSV);
rsort($tags);
$tags = array_map('preg_quote', $tags);
$pattern = '/\b(?:' . implode('|', $tags) . ')\b/iu';
$text = preg_replace_callback($pattern, function ($m) use (&$tags) {
$mLC = mb_strtolower($m[0], 'UTF-8');
if (false === $key = array_search($mLC, $tags))
return $m[0];
unset($tags[$key]);
return '<a href="index.php?s=news&tag=' . rawurlencode($mLC)
. '">' . $m[0] . '</a>';
}, $text);
Note: when you build an url you must encode special characters, this is the reason why I use preg_replace_callback instead of preg_replace to be able to use rawurlencode.
If you have to deal with an utf8 encoded string, you need to add the u modifier to the pattern and you need to replace strtolower with mb_strtolower)
the preg_split way
$tags = explode(', ', $tagsCSV);
rsort($tags);
$tags = array_map('preg_quote', $tags);
$pattern = '/\b(' . implode('|', $tags) . ')\b/iu';
$items = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$itemsLength = count($items);
$i = 1;
while ($i<$itemsLength && count($tags)) {
if (false !== $key = array_search(mb_strtolower($items[$i], 'UTF-8'), $tags)) {
$items[$i] = '<a href="index.php?s=news&tag=' . rawurlencode($tags[$key])
. '">' . $items[$i] . '</a>';
unset($tags[$key]);
}
$i+=2;
}
$result = implode('', $items);
Instead of calling preg_replace multiple times, call it a single time with a regexp that matches any of the tags:
$tags = explode(",", tags);
$tags_re = '/\b(' . implode('|', $tags) . ')\b/u';
$text = preg_replace($tags_re, '$0', $text, 1);
This turns the list of tags into the regexp /\b(love|love php|facing)\b/u. x|y in a regexp means to match either x or y.

str_replace not replacing correctly

I have the following, simple code:
$text = str_replace($f,''.$u.'',$text);
where $f is a URL, like http://google.ca, and $u is the name of the URL (my function names it 'Google').
My problem is, is if I give my function a string like
http://google.ca http://google.ca
it returns
Google" target="_blank">Google</a> Google" target="_blank">Google</a>
Which obviously isn't what I want. I want my function to echo out two separate, clickable links. But str_replace is replacing the first occurrence (it's in a loop to loop through all the found URLs), and that first occurrence has already been replaced.
How can I tell str_replace to ignore that specific one, and move onto the next? The string given is user input, so I can't just give it a static offset or anything with substr, which I have tried.
Thank you!
One way, though it's a bit of a kludge: you can use a temporary marker that (hopefully) won't appear in the string:
$text = str_replace ($f, '' . $u . '',
$text);
That way, the first substitution won't be found again. Then at the end (after you've processed the entire line), simply change the markers back:
$text = str_replace ('XYZZYPLUGH', $f, $text);
Why not pass your function an array of URLs, instead?
function makeLinks(array $urls) {
$links = array();
foreach ($urls as $url) {
list($desc, $href) = $url;
// If $href is based on user input, watch out for "javascript: foo;" and other XSS attacks here.
$links[] = '<a href="' . htmlentities($href) . '" target="_blank">'
. htmlentities($desc)
. '</a>';
}
return $links; // or implode('', $links) if you want a string instead
}
$urls = array(
array('Google', 'http://google.ca'),
array('Google', 'http://google.ca')
);
var_dump(makeLinks($urls));
If i understand your problem correctly, you can just use the function sprintf. I think something like this should work:
function urlize($name, $url)
{
// Make sure the url is formatted ok
if (!filter_var($url, FILTER_VALIDATE_URL))
return '';
$name = htmlspecialchars($name, ENT_QUOTES);
$url = htmlspecialchars($url, ENT_QUOTES);
return sprintf('%s', $url, $name);
}
echo urlize('my name', 'http://www.domain.com');
// my name
I havent test it though.
I suggest you to use preg_replace instead of str_replace here like this code:
$f = 'http://google.ca';
$u = 'Google';
$text='http://google.ca http://google.ca';
$regex = '~(?<!<a href=")' . preg_quote($f) . '~'; // negative lookbehind
$text = preg_replace($regex, ''.$u.'', $text);
echo $text . "\n";
$text = preg_replace($regex, ''.$u.'', $text);
echo $text . "\n";
OUTPUT:
Google Google
Google Google

Converting line breaks into <li> tags

I'm creating an upload form that has a text area for users to input cooking recipes with. Essentially, what I want to do is wrap each line in a <li> tag for output purposes. I've been trying to manipulate the nl2br function but to no avail. Can anyone help?
I'm retrieving the text area's content via POST and storing entries in a MySQL database.
Here's what the code looks like at the moment (the check_input function strips slashes, etc.):
$prepText=check_input($_POST['preparationText']);
$cookText=check_input($_POST['cookingText']);
Explode the string by \n then wrap each line in an li tag.
<?php
$string = "line 1\nline 2\nline3";
$bits = explode("\n", $string);
$newstring = "<ol>";
foreach($bits as $bit)
{
$newstring .= "<li>" . $bit . "</li>";
}
$newstring .= "</ol>";
Not quite pretty, but an idea that comes to mind is to :
explode the string, using newline as a separator
and implode the array, using </li><li> between items :
Which could be translated to something like this :
$new_string = '<li>' . implode('</li><li>', explode("\n", $old_string)) . '</li>';
(Yeah, bad idea -- don't do that, especially if the text is long)
Another solution, way cleaner, would be to just replace the newlines in your string by </li><li> :
(wrapping the resulting string inside <li> and </li>, to open/close those)
$new_string = '<li>' . str_replace("\n", '</li><li>', $old_string) . '</li>';
With that idea, for example, the following portion of code :
$old_string = <<<STR
this is
an example
of a
string
STR;
$new_string = '<li>' . str_replace("\n", '</li><li>', $old_string) . '</li>';
var_dump($new_string);
Would get you this kind of output :
string '<li>this is</li><li>an example</li><li>of a </li><li>string</li>' (length=64)
I created a function based on Richard's answer in case it saves anyone some time!
/**
* #param string $str - String containing line breaks
* #param string $tag - ul or ol
* #param string $class - classes to add if required
*/
function nl2list($str, $tag = 'ul', $class = '')
{
$bits = explode("\n", $str);
$class_string = $class ? ' class="' . $class . '"' : false;
$newstring = '<' . $tag . $class_string . '>';
foreach ($bits as $bit) {
$newstring .= "<li>" . $bit . "</li>";
}
return $newstring . '</' . $tag . '>';
}
function nl2li($str)
{
if (!isset($str)) return false;
$arr = explode("\r\n", $str);
$li = array_map(function($s){ return '<li>'.$s.'</li>'; }, $arr);
return '<ul>'.implode($li).'</ul>';
}
Input:
Line 1\r\nLine2\r\nLine3\r\n
Output:
<ul><li>Line 1</li><li>Line 2</li><li>Line 3</li></ul>
The simplest way to do it:
function ln2ul($string) {
return '<ul><li>' . str_replace("\n", '</li><li>', trim($string)) . '</li></ul>';
}

Categories