45
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

URLをリンクにする(但しすでにリンクになっていたら無視)

Posted at

いまさら感の拭えないタイトルではありますが、意外と「すでにリンクになっている場合もある」想定のノウハウが見つかりませんでした。

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つの方法を紹介しました。

45
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?