简体   繁体   中英

Limit the number of results using preg_match_all PHP

Is there any way to limit the number of matches 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

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;
}
$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

您可以使用T-Regx库:

pattern('<p>')->match($yourHtml)->only(20);

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);

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'
---

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];
    }
}

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 ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM