Finding Plain Text Links inside HTML
I really liked this answer - yet I needed a solution for possible plain text links that are inside very simple HTML text:
<p>I found a really cool site you might like:</p>
<p>www.stackoverflow.com</p>
This meant I needed the regex patterns to ignore the html chars < and >
Regex Adjustment
So I changed parts of the patterns to [^\s\>\<] instead of \S
\S - not white-space; matches any char that is not white-space (tab, space, newline)
[^] - a negated set; matches any char not in the set
My version of the function from this answer
I needed another format in addition to HTML so I separated out the regexes from their replacements to accommodate this.
I also added a way to return just the links/emails found into an array so I can save them as a relationship on my posts (great for making meta cards for them later ...and for analytics!).
UPDATE: Consecutive periods were matching
I was getting matches for text like there...it - So I wanted to ensure I didn't get any matches that included consecutive dots.
Note: To accomplish fixing this, I added an additional format string to undo matching them to avoid having to redo these otherwise reliable url regexes.
/***
* based on this answer: https://stackoverflow.com/a/49689245/2100636
*
* @var $text String
* @var $format String - html (<a href=""...), short ([link:https://somewhere]), other (https://somewhere)
*/
public function formatLinksInString(
$string,
$format = 'html',
$returnMatches = false
) {
$formatProtocol = $format == 'html'
? '<a href="$0" target="_blank" title="$0">$0</a>'
: ($format == 'short' || $returnMatches ? '[link:$0]' : '$0');
$formatSansProtocol = $format == 'html'
? '<a href="//$0" target="_blank" title="$0">$0</a>'
: ($format == 'short' || $returnMatches ? '[link://$0]' : '$0');
$formatMailto = $format == 'html'
? '<a href="mailto:$1" target="_blank" title="$1">$1</a>'
: ($format == 'short' || $returnMatches ? '[mailto:$1]' : '$1');
$regProtocol = '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(\/[^\<\>\s]*)?/';
$regSansProtocol = '/(?<=\s|\A|\>)([0-9a-zA-Z\-\.]+\.[a-zA-Z0-9\/]{2,})(?=\s|$|\,|\<)/';
$regEmail = '/([^\s\>\<]+\@[^\s\>\<]+\.[^\s\>\<]+)\b/';
$consecutiveDotsRegex = $format == 'html'
? '/<a[^\>]+[\.]{2,}[^\>]*?>([^\<]*?)<\/a>/'
: '/\[link:.*?\/\/([^\]]+[\.]{2,}[^\]]*?)\]/';
// Protocol links
$formatString = preg_replace($regProtocol, $formatProtocol, $string);
// Sans Protocol Links
$formatString = preg_replace($regSansProtocol, $formatSansProtocol, $formatString); // use formatString from above
// Email - Mailto - Links
$formatString = preg_replace($regEmail, $formatMailto, $formatString); // use formatString from above
// Prevent consecutive periods from getting captured
$formatString = preg_replace($consecutiveDotsRegex, '$1', $formatString);
if ($returnMatches) {
// Find all [x:link] patterns
preg_match_all('/\[.*?:(.*?)\]/', $formatString, $matches);
current($matches); // to move pointer onto groups
return next($matches); // return the groups
}
return $formatString;
}