質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

Q&A

解決済

4回答

8123閲覧

PHPで文字列内のURLをリンクに変換

退会済みユーザー

退会済みユーザー

総合スコア0

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

0グッド

1クリップ

投稿2020/08/12 21:13

編集2020/08/12 21:16

やりたいこと

PHPで文字列の中に含むURLをaタグリンクに置換したい
ただし、既にaリンクである場合は無視したい

ベースコード

こちらを参考にしました。

php

1function url2link($body, $link_title = null) 2{ 3 $pattern = '/(?<!href=")https?://[-_.!~*\'()a-zA-Z0-9;/?:@&=+$,%#]+/'; 4 $body = preg_replace_callback($pattern, function($matches) use ($link_title) { 5 $link_title = $link_title ?: $matches[0]; 6 return "<a href=\"{$matches[0]}\">$link_title</a>"; 7 }, $body); 8 return $body; 9}

問題

下記のように、コンテンツ部分にURLが含まれる場合はコンテンツ部分もaタグに置換されてしまいます。

html

1 2#OK 3テストhttps://example.comテスト 45テスト<a href="https://example.com">https://example.com</a>テスト 6 7#OK 8テスト<a href="https://example.com">コンテンツ</a>テスト 910テスト<a href="https://example.com">コンテンツ</a>テスト 11 12#NG 13テスト<a href="https://example.com">https://example.com</a>テスト 1415テスト<a href="https://example.com"><a href="https://example.com">https://example.com</a></a>テスト 16 17

試したこと

URL + **<**にはマッチしないように (?!<) を追加してみましたが、
結果が期待と異なります。

php

1$pattern = '/(?<!href=")https?://[-_.!~*\'()a-zA-Z0-9;/?:@&=+$,%#]+(?!<)/'; 2 3//結果 <a href="https://example.com"><a href="https://example.co">https://example.co</a>m</a>

正規表現が間違っているか、
そもそも** preg_replace_callback **の挙動認識が間違っているかのどちらかだと思いますが、
解決に向けてアドバイスいただけないでしょうか。

また、上記が解決したとしても「URL + <」は不完全な気がします。。
<a href="http://-"><p>http://-</p></a>などに対応できない)
抜本解決的にも他の手法がありましたらご教示お願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

取りあえずそのパターン「URL直後に<がある場合を除外」だけ対応できれば良いのであれば、絶対最大量指定子++を使って、

PHP

1$pattern = '/(?<!href=")https?://[-_.!~*\'()a-zA-Z0-9;/?:@&=+$,%#]++(?!<)/';

で良いかと思いますが、「URL直後に<がある場合を除外」だと、

html

1テスト<a href="https://example.com">https://example.com です</a>テスト

のように、URLの直後に別の文字があると除外されずに置換されますが、それでいいのでしょうか?

また、

html

1テスト<span>https://example.com</span>テスト

は、「URL直後に<がある場合を除外」に該当して置換されませんが、それでいいのでしょうか?

html

1テスト<a href="https://example.com">URLは https://example.com です</a>テスト 2テスト<a href="https://example.com">URLは<span>https://example.com</span>です</a>テスト

のようなものは、直前がaタグじゃなくても除外したいのではないでしょうか?

ということで、DOM等を使って置換対象の範囲を絞り込むようなことが必要ではないでしょうか。
もしくは仕様を変えて、タグを全部取り去ってから処理するとか。

投稿2020/08/13 00:08

編集2020/08/13 00:15
otn

総合スコア85901

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2020/08/13 00:16

>タグを全部取り去ってから処理 これに1票。 処理しやすい形態のデータにするのもひとつ手としてはありですね。
退会済みユーザー

退会済みユーザー

2020/08/19 15:57

ご指摘の通り、想定していなかったパターン漏れがたくさんありました。 タグを取り去ってから処理する考えはなかったので勉強になりました。 工夫してみます。ありがとうございました。
guest

0

これはDomdocumentを絡めてやらないと相当非効率だと思います
たとえばこんなデータとか正規表現ではお手上げです

HTML

1テストhttps://example.com/1テスト 2テスト<a href="https://example.com/2">https://example.com/3</a>テスト 3テスト<a href="https://example.com/4"><span>https://example.com/5</span></a>テスト 4<span>https://example.com/6<span>https://example.com/7</span></span>テスト 5<span>https://example.com/8<a href="./">test</a></span>テスト

投稿2020/08/15 05:39

yambejp

総合スコア116734

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/08/19 16:02

正規表現では限界のある処理だったのですね。 確かにそのようなケース全てに対応するのは難しそうです。 ありがとうございました。
guest

0

そもそもなんでテキストの中からURLを抽出するのに元からURLがタグ付きで入ってるのかと聞きたいところですが、とりあえず置いておいて、見た目も速度も度外視でとりあえずなんか処理してるように見えるコードを書きました。

  • 入力文字列は固定で定義してます
  • URLのパターンも正規表現で固定で定義してます(ネットに転がってた適当な定義です。良くないです。)
  • 入力文字列はとりあえずDOMに入れてます
  • その後、テキスト部分を無条件で(本当はscript要素などを除外する)XPathで取得して、中身のURLを抽出してます
  • 抽出したURLをURLエンコードしてません
  • テキストノードを作成する際に勝手に文字参照に変わる可能性があります
  • デバッグももちろんしてないし、そのまま使う人はいないと信じています
  • phpはよく知らないので、お作法に則ってないところは教えて下さい
  • 日本語は1文字でも入ると文字化けします(こちらにひどい対策が載ってます)

php

1<?php 2$url_pattern='(https?://[\w!\?/\+\-_~=;\.,\*&@#\$%\(\)\'\[\]]+)'; 3 4$doc = new DOMDocument(); 5$doc->loadHTML("<html><body>Test<br>eehttp://yahoo.jp/hogehoge?asda=1#asd https://goo/h eee</body></html>"); 6$docXPath = new DOMXpath($doc); 7$nodes = $docXPath->query('//text()'); 8 9foreach($nodes as $node) { 10 $count = preg_match_all($url_pattern, $node->textContent, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); 11 if ($count > 0) { 12 $index = 0; 13 $targetLength = strlen($node->textContent); 14 foreach($matches as &$match) { 15 $beforeNode = $doc->createTextNode(substr($node->textContent, $index, $match[0][1] - $index)); 16 $node->parentNode->insertBefore($beforeNode, $node); 17 18 $index = $match[0][1]; 19 $anchorElement = $doc->createElement("a"); 20 $anchorElement->setAttribute("href", $match[0][0]); 21 $anchorElement->appendChild($doc->createTextNode($match[0][0])); 22 $node->parentNode->insertBefore($anchorElement, $node); 23 24 $index += strlen($match[0][0]); 25 unset($match); 26 } 27 if ($index < $targetLength) { 28 $beforeNode = $doc->createTextNode(substr($node->textContent, $index, $targetLength - $index)); 29 $node->parentNode->insertBefore($beforeNode, $node); 30 } 31 $node->parentNode->removeChild($node); 32 } 33} 34echo $doc->saveHTML(); 35?>

投稿2020/08/13 21:49

編集2020/08/13 22:17
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/08/19 16:01

DOMに入れて処理する方法もあるのですね。 サンプルコードまでいただきありがとうございました。 工夫してみます。
guest

0

「直前にa要素がない位置」を追加して、

regex

1/(?<!href="|<a.+?>)https?://[-_.!~*\'()a-zA-Z0-9;/?:@&=+$,%#]+/

と書きたいところなのですが、PHPの正規表現の後読み(戻り読み言明)では繰り返しを指定できません。

そこで、提示されているサイトに書いてある、もうひとつのurl2link(preg_replace_callback でゴリ押しする方法)を使ってください。そして、$pattern

regex

1/(href="|<a.*?>)?https?://[-_.!~*\'()a-zA-Z0-9;/?:@&=+$,%#]+/

に差し替えます。すると、「直前にhref="またはa要素がある」ときだけ$matches[1]が存在するので、その場合は元のままの文字列が返されます。

投稿2020/08/12 23:51

Daregada

総合スコア11990

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/08/19 15:55

PHPでは正規表現の後読みで繰り返しができないのですね。 aタグ内の別のタグが含まれている一部のケースに対応できなかったため工夫してみますね。 勉強になりました。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問