Limit the number of results using preg_match_all PHP
Is there any way to limit the number of matc开发者_JAVA百科hes that will be returned using preg_match_all
?
So for example, I want to match only the first 20 <p>
tags on a web page but there are 100 <p>
tags.
Cheers
$matches = array();
preg_match_all ( $pattern , $subject , $matches );
$twenty = array_slice($matches , 0, 20);
Just match all and slice the resulting array:
$allMatches = array ();
$numMatches = preg_match_all($pattern, $subject, $allMatches, PREG_SET_ORDER);
$limit = 20;
$limitedResults = $allMatches;
if($numMatches > $limit)
{
$limitedResults = array_slice($allMatches, 0, $limit);
}
// Use $limitedResults here
No, the computation of the preg_match_all
result set cannot be limited. You can only limit the results afterwards with array_slice
or array_splice
(this would require PREG_SET_ORDER):
preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER);
$firstMatches = array_slice($matches, 0, 20);
But besides that, you shouldn’t use regular expressions to parse HTML anyway. Although modern regular expressions engines are not regular any more and can process an irregular language like HTML, it is too error prone. Better use an appropriate HTML parser instead like the one of PHP’s DOM library. Then just use a counter to only get up to 20 matches:
$doc = new DOMDocument();
$doc->loadHTML($code);
$counter = 20;
$matches = array();
foreach ($doc->getElementsByTagName('p') as $elem) {
if ($counter-- <= 0) {
break;
}
$matches[] = $elem;
}
You can use T-Regx library:
pattern('<p>')->match($yourHtml)->only(20);
To extend on @Gumbo's great advice to use a DOM parser instead of regex, the following snippet will use a XPath query with a position()
condition to limit the targeted tags.
Code: (Demo targeting 4 of 5 p tags)
$html = <<<HTML
<div>
<p class="classy">1
</p>
<p>2</p>
<p data-p="<p>notatag</p>">3</p>
<span data-monkeywrench='<p'>z</span>
<p
data-p="<p>notatag</p>">4</p>
<p>5</p>
</div>
HTML;
$dom = new DOMDocument();
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//p[position() <= 4]') as $p) {
echo var_export($p->nodeValue, true) , "\n---\n";
}
Output:
'1
'
---
'2'
---
'3'
---
'4'
---
This is the true answer; the most memory-efficient way.
Use reference assignment via preg_replace_callback()
instead.
<?php
$matches = [];
preg_replace_callback(
'~<p(?:\s.*?)?>(?:.*?)</p>~s',
function (array $match) use (&$matches) {
$matches[] = $match[0];
},
$html,
20,
$_
);
var_dump($matches);
You can either use preg_match_all()
and discard the matches you're not interested in, or you can use a loop with preg_match()
. The second option would be better if you're concern about the expense of scanning a large string.
This example limits to 2 matches, when there are actually 3 in the entire string:
<?php
$str = "ab1ab2ab3ab4c";
for ($offset = 0, $n = 0;
$n < 2 && preg_match('/b([0-9])/', $str, $matches, PREG_OFFSET_CAPTURE, $offset);
++$n, $offset = $matches[0][1] + 1) {
var_dump($matches);
}
Really a while
loop would probably have been clearer than a for
loop on reflection ;)
I don't think so, but preg_match does have an offset
parameter, and also a PREG_OFFSET_CAPTURE
flag which, when combined, can be used to get the "next match".
This is mainly useful if you don't want to get all results and then array_slice()
a portion off :o)
EDIT: Ok, here's some code (not tested or used in any way):
$offset = 0;
$matches = array();
for ($i = 0; $i < 20; $i++) {
$results = preg_match('/<p(?:.*?)>/', $string, PREG_OFFSET_CAPTURE, $offset);
if (empty($results)) {
break;
} else {
$matches[] = $results[0][0];
$offset += $results[0][1];
}
}
精彩评论