指定文字列が含まれていたときに失敗させるというチート技を,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*/