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

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

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

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

正規表現

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

Q&A

解決済

5回答

4675閲覧

正規表現で、「.*?」の間に特定の文字列を含まない、という記述をお教えいただきたいです

joeast

総合スコア26

PHP

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

正規表現

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

2グッド

3クリップ

投稿2016/10/05 06:19

###前提・実現したいこと
正規表現で、「.*?」の間に特定の文字列を含まない、という記述をお教えいただきたいです。
言語はPHPです。

###該当のソースコード
例えば、

$text = '
<span class="code">401</span>
<span class="num">25</span>

<span class="code">402</span>
<span class="num">3</span>

<span class="code">403</span>
<span class="num">126</span>

<span class="code">404</span>

<span class="code">405</span>
<span class="num">78</span>
';

この $text の、「<span class="code">403</span>」直下の、「126」という値を取得したいです。

$v = preg_replace('{^.?<span class="code">403</span>.?<span class="num">(.?)</span>.$}si', "$1", $text);

と、後方参照で取得はできるのですが、「<span class="code">404</span>」の例のように、
<span class="num">~」の行が存在しな場合もあります。
その場合、最短マッチにすると次の「<span class="num">~」にマッチしてしまい意図しない動作になります。
ただし「<span class="code">~」の行は必ず存在します。

ですので私の考えでは、「<span class="code">403</span>」から最短マッチ 「.*?」 を行って、
その中に「<span class="code">」という文字列を含まないマッチ、というのを実現したいです。
否定の正規表現は「(?!<span class="code">)」というような書き方があるのは調べて分かったのですが、うまくいきません。
やり方がおかしいのか考え方がおかしいのか、わからない状況です。

よろしくお願いいたします。

mpyw, 39ff👍を押しています

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

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

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

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

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

otn

2016/10/05 06:37

「<span class="num">~」の行が存在しない場合に、どういう結果を期待していますか?
joeast

2016/10/05 06:43

その場合はマッチ失敗とわかるだけで大丈夫です
joeast

2016/10/05 08:38

ご回答いただいた皆様、ありがとうございました。
guest

回答5

0

ベストアンサー

指定文字列が含まれていたときに失敗させるというチート技を,PCREでは(*SKIP)(*FAIL)で使うことができます.

Java - 正規表現で『「abc」と言う塊以外の文字列』は指定できるか(31010)|teratail
(こちらの回答およびコメント欄を参考に)

これを今回のケースに適用すると

php

1<?php 2 3$text = ' 4<span class="code">401</span> 5<span class="num">25</span> 6 7<span class="code">402</span> 8<span class="num">3</span> 9 10<span class="code">403</span> 11<span class="num">126</span> 12 13<span class="code">404</span> 14 15<span class="code">405</span> 16<span class="num">78</span> 17 18<span class="code">406</span> 19'; 20 21$getNumber = function ($code) use ($text) { 22 $pattern = '@ 23 <span[ ]class="code">' . $code . '</span> 24 .*? 25 (?: 26 <span[ ]class="code">(*SKIP)(*FAIL) 27 | 28 <span[ ]class="num">(.*?)</span> 29 ) 30 @xs'; 31 return preg_match($pattern, $text, $m) ? $m[1] : null; 32}; 33 34$codes = [401, 402, 403, 404, 405, 406]; 35 36var_dump(array_map($getNumber, array_combine($codes, $codes))); 37 38/* 39 40array(5) { 41 [401]=> 42 string(2) "25" 43 [402]=> 44 string(1) "3" 45 [403]=> 46 string(3) "126" 47 [404]=> 48 NULL 49 [405]=> 50 string(2) "78" 51 [406]=> 52 NULL 53} 54 55*/

どうせなら (404, 406以外) 結果を一気に取ってしまいましょうか.少し(*SKIP)(*FAIL)の部分を書き換えて,先読みを併用してみます.これが無いと405がマッチしなくなってしまいます.

php

1<?php 2 3$text = ' 4<span class="code">401</span> 5<span class="num">25</span> 6 7<span class="code">402</span> 8<span class="num">3</span> 9 10<span class="code">403</span> 11<span class="num">126</span> 12 13<span class="code">404</span> 14 15<span class="code">405</span> 16<span class="num">78</span> 17 18<span class="code">406</span> 19'; 20 21$pattern = '@ 22 <span[ ]class="code">(.*?)</span> 23 .*? 24 (?: 25 (?=<span[ ]class="code">)(*SKIP)(*FAIL) 26 | 27 <span[ ]class="num">(.*?)</span> 28 ) 29@xs'; 30 31$results = 32 preg_match_all($pattern, $text, $m) 33 ? array_combine($m[1], $m[2]) 34 : []; 35 36var_dump($results); 37 38/* 39 40array(4) { 41 [401]=> 42 string(2) "25" 43 [402]=> 44 string(1) "3" 45 [403]=> 46 string(3) "126" 47 [405]=> 48 string(2) "78" 49} 50 51*/

もっと欲張りな「404, 406に対応する値は空文字列として含めてしまって,結果を全部一気に欲しい」という要望にもお答えできます. (*FAIL)は強制的に失敗させるものでしたが,(*ACCEPT)は強制的に成功させます.マッチした文字列が何も無い状態で成功させると,その部分は空文字列として結果に含まれます.また,406のようにnumを持たないcodeが末尾にきたときのために,\zによる判定を追加しています.

php

1<?php 2 3$text = ' 4<span class="code">401</span> 5<span class="num">25</span> 6 7<span class="code">402</span> 8<span class="num">3</span> 9 10<span class="code">403</span> 11<span class="num">126</span> 12 13<span class="code">404</span> 14 15<span class="code">405</span> 16<span class="num">78</span> 17 18<span class="code">406</span> 19'; 20 21$pattern = '@ 22 <span[ ]class="code">(.*?)</span> 23 .*? 24 (?: 25 (?=\z|<span[ ]class="code">)(*SKIP)(*ACCEPT) 26 | 27 <span[ ]class="num">(.*?)</span> 28 ) 29@xs'; 30 31$results = 32 preg_match_all($pattern, $text, $m) 33 ? array_combine($m[1], $m[2]) 34 : []; 35 36var_dump($results); 37 38/* 39 40array(5) { 41 [401]=> 42 string(2) "25" 43 [402]=> 44 string(1) "3" 45 [403]=> 46 string(3) "126" 47 [404]=> 48 string(0) "" 49 [405]=> 50 string(2) "78" 51 [406]=> 52 string(0) "" 53} 54 55*/

投稿2016/10/05 07:43

編集2016/10/05 08:53
mpyw

総合スコア5223

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

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

joeast

2016/10/05 08:37

この方法でいけそうです。 詳しい説明などありがとうございました。
mpyw

2016/10/05 08:54

(*ACCEPT)を使う方法に若干漏れがあったので,406を追加してそれにも対応できるように改善しました.
guest

0

直下のということは単純に改行まで示せばよいのでは?

PHP

1$num="403"; 2$pattern='#<span class="code">'.$num.'</span>(?:\r|\n)+<span class="num">(.*?)</span>#mis'; 3if(preg_match($pattern,$text,$match)){ 4print $match[1]; 5}

※データの取り出し方を追記しておきました
こういうことではないですか?

投稿2016/10/05 06:40

編集2016/10/05 07:41
yambejp

総合スコア114843

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

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

joeast

2016/10/05 07:09

こちらでもうまくいきませんでした。 指定文字列を含まないマッチは難しいのでしょうか?
yambejp

2016/10/05 07:42

replaceではなくmatchしたデータを配列で受けて参照するほうが普通です
guest

0

その場合はマッチ失敗とわかるだけで大丈夫です

ということであれば、単にこれで良いかと。間に別のタグがないということで、「.?」⇒「[^<]?」。

PHP

1'{^.*?<span class="code">403</span>[^<]*?<span class="num">(.*?)</span>.*$}si'

投稿2016/10/05 06:52

otn

総合スコア84555

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

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

joeast

2016/10/05 07:07

すみません、例題に載せているのはかなり簡略化したもので問題のテキストはもっと複雑でして、間にその他のタグなども多数含まれている状況です、説明不足すみません。
guest

0

<span class="num">だけ見るのはだめですか?

php

1$pattern = '/<span class="num">(\d+)<\/span>/'; 2$match_num = preg_match_all($pattern, $text, $matches); 3 4var_dump($match_num); 5var_dump($matches);

投稿2016/10/05 07:02

moonphase

総合スコア6621

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

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

joeast

2016/10/05 07:11

<span class="num">の行は存在しないことがありますので、 <span class="code">403</span> 直下の値を取得する場合、エラーになるケースがあると思います。
moonphase

2016/10/05 07:16

あぁ、なるほど。書いてある通り<span class="code">403</span>直下のものだけが欲しいのですね。失礼しました。
guest

0

$pattern = '<span class="code">403</span>[^(?:</span>)]*<span class="num">([0-9]+)</span>';

こんな感じではどうでしょうか。

投稿2016/10/05 06:29

kunai

総合スコア5405

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

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

joeast

2016/10/05 06:45

[^(?:</span>)]*  この記述は、「</span>」という文字列以外の任意の文字列ということでしょうか?
kunai

2016/10/05 07:00

その通りです。 [^a]で、a以外のモノと言う形になりますので
joeast

2016/10/05 07:10

調べてみたのですが、こちらでは文字列の否定はできないようです。 [^]の記述方法はあくまで1字の否定になるようです。
kunai

2016/10/05 08:46

他の方法を採用されたようですので付記になりますが。 ^の否定の対象を()でグルーピングしている形になります。 回答文の「こんな感じ」と書いてるところのリンク先などで試してみてください。
mpyw

2016/10/05 09:04

>> ^の否定の対象を()でグルーピングしている形になります。 ^で否定できるのは文字クラスに限られすが,そもそも文字クラス [ ] の中に (?:</span>) のようなサブパターンを置くことはできません.置けるのは 「単一の文字」 「ハイフンつなぎの文字範囲」 「\dや\w,[:alnum:] のような特殊な文字クラス」 に限られます.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問