- preg_replace_callback でゴリ押しする方法
- 否定戻り読みを使う方法
いまさら感の拭えないタイトルではありますが、意外と「すでにリンクになっている場合もある」想定のノウハウが見つかりませんでした。
preg_replace_callback でゴリ押しする方法
/**
* @param string $body 処理対象のテキスト
* @param string|null $link_title リンクテキスト
* @return string
*/
function url2link($body, $link_title = null)
{
$pattern = '/(href=")?https?:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:@&=+$,%#]+/';
$body = preg_replace_callback($pattern, function($matches) use ($link_title) {
// 既にリンクの場合や Markdown style link の場合はそのまま
if (isset($matches[1])) return $matches[0];
$link_title = $link_title ?: $matches[0];
return "<a href=\"{$matches[0]}\">$link_title</a>";
}, $body);
return $body;
}
使い方
// リンクタイトルなし
echo url2link($body);
// リンクタイトルあり
echo url2link($body, "詳細はこちら");
解説
preg_replace_callback はその名の通り、正規表現にマッチした対象をコールバックで逐次処理する関数です。
コールバックで処理するため、よりきめ細やかな置換処理ができるのはもちろんのこと、コールバックの中でさらに置換するかしないかの判断ができるというメリットがあります。
つまり上記コードで、正規表現状は (href=")?
とあるように、href 属性の形になっていてもなっていなくてもマッチするように書いていますが、コールバックの中で href="
にマッチしている場合は対象を置換せずにそのまま返却することで、置換をキャンセルしています。
たとえばさらに Markdown スタイルのリンクもキャンセルしたいならば、先頭のマッチグループを (href="|]\()?
とすればOKです。
/(href="|]\()?https?:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:@&=+$,%#]+/
否定戻り読みを使う方法
正規表現の高等テクニックとして
- 肯定先読み
(?=regex)
- 否定先読み
(?!regex)
- 肯定戻り読み
(?<=regex)
- 否定戻り読み
(?<!regex)
というものがあります。このうち否定戻り読みを使って href="
が前方にくっついている場合はそもそもマッチしない正規表現をつくることができます。
キャンセル処理が不要なのでこちらのほうがスッキリします。
/**
* @param string $body 処理対象のテキスト
* @param string|null $link_title リンクテキスト
* @return string
*/
function url2link($body, $link_title = null)
{
$pattern = '/(?<!href=")https?:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:@&=+$,%#]+/';
$body = preg_replace_callback($pattern, function($matches) use ($link_title) {
$link_title = $link_title ?: $matches[0];
return "<a href=\"{$matches[0]}\">$link_title</a>";
}, $body);
return $body;
}
こちらもさらに Markdown スタイルのリンクを除外する場合は (?<!href="|]\()
とすればOKです。
/(?<!href="|]\()https?:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:@&=+$,%#]+/
否定戻り読みを使う方法のほうが正攻法だとは思いますが、あまり知られていなかったり覚えていないこともあるかと思います。preg_replace_callback を使えば、平易な表現でも高度な処理に手が届くなと思い、2つの方法を紹介しました。