PHP
downloads | documentation | faq | getting help | mailing lists | licenses | wiki | reporting bugs | php.net sites | links | conferences | my php.net

search for in the

preg_match> <preg_last_error
Last updated: Fri, 19 Jun 2009

view this page in

preg_match_all

(PHP 4, PHP 5)

preg_match_allВыполняет глобальный поиск шаблона в строке

Описание

int preg_match_all ( string $pattern , string $subject , array $&matches [, int $flags [, int $offset ]] )

Ищет в строке subject все совпадения с шаблоном pattern и помещает результат в массив matches в порядке, определяемом комбинацией флагов flags .

После нахождения первого соответствия последующие поиски будут осуществляться не с начала строки, а от конца последнего найденного вхождения.

Дополнительный параметр flags может комбинировать следующие значения (необходимо понимать, что использование PREG_PATTERN_ORDER одновременно с PREG_SET_ORDER бессмысленно):

PREG_PATTERN_ORDER

Если этот флаг установлен, результат будет упорядочен следующим образом: элемент $matches[0] содержит массив полных вхождений шаблона, элемент $matches[1] содержит массив вхождений первой подмаски, и так далее.

<?php
preg_match_all
("|<[^>]+>(.*)</[^>]+>|U"
    
"<b>example: </b><div align=left>this is a test</div>"
    
$outPREG_PATTERN_ORDER);
echo 
$out[0][0] . ", " $out[0][1] . "\n";
echo 
$out[1][0] . ", " $out[1][1] . "\n";
?>

Результат работы примера:

<b>example: </b>, <div align=left>this is a test</div>
example: , this is a test

Как мы видим, $out[0] содержит массив полных вхождений шаблона, а элемент $out[1] содержит массив подстрок, содержащихся в тегах.

PREG_SET_ORDER

Если этот флаг установлен, результат будет упорядочен следующим образом: элемент $matches[0] содержит первый набор вхождений, элемент $matches[1] содержит второй набор вхождений, и так далее.

<?php
preg_match_all
("|<[^>]+>(.*)</[^>]+>|U"
    
"<b>example: </b><div align=\"left\">this is a test</div>"
    
$outPREG_SET_ORDER);
echo 
$out[0][0] . ", " $out[0][1] . "\n";
echo 
$out[1][0] . ", " $out[1][1] . "\n";
?>

Результат работы примера:

<b>example: </b>, example: 
<div align="left">this is a test</div>, this is a test

В таком случае массив $matches[0] содержит первый набор вхождений, а именно: элемент $matches[0][0] содержит первое вхождение всего шаблона, элемент $matches[0][1] содержит первое вхождение первой подмаски, и так далее. Аналогично массив $matches[1] содержит второй набор вхождений, и так для каждого найденного набора.

PREG_OFFSET_CAPTURE

В случае, если этот флаг указан, для каждой найденной подстроки будет указана ее позиция в исходной строке. Необходимо помнить, что этот флаг меняет формат возвращаемых данных: каждое вхождение возвращается в виде массива, в нулевом элементе которого содержится найденная подстрока, а в первом - смещение. Данный флаг доступен в PHP 4.3.0 и выше.

В случае, если никакой флаг не используется, по умолчанию используется PREG_PATTERN_ORDER.

Поиск осуществляется слева направо, с начала строки. Дополнительный параметр offset может быть использован для указания альтернативной начальной позиции для поиска. Дополнительный параметр offset доступен, начиная с PHP 4.3.3.

Замечание: Использование параметра offset не эквивалентно замене сопоставляемой строки выражением substr($subject, $offset) при вызове функции preg_match_all(), поскольку шаблон pattern может содержать такие условия как ^, $ или (?<=x). Вы можете найти соответствующие примеры в описании функции preg_match().

Возвращает количество найденных вхождений шаблона (может быть нулем) либо FALSE, если во время выполнения возникли какие-либо ошибки.

Пример #1 Получение всех телефонных номеров из текста.

<?php
preg_match_all
("/\(?  (\d{3})?  \)?  (?(1)  [\-\s] ) \d{3}-\d{4}/x",
                
"Call 555-1212 or 1-800-555-1212"$phones);
?>

Пример #2 Жадный поиск совпадений с HTML-тэгами

<?php
// Запись \\2 является примером использования ссылок на подмаски.
// Она означает необходимость соответствия подстроки строке, зафиксированной
// второй подмаской, в нашем примере это ([\w]+).
// Дополнительный слеш необходим, так как используются двойные кавычки.

$html "<b>bold text</b><a href=howdy.html>click me</a>";

preg_match_all("/(<([\w]+)[^>]*>)(.*)(<\/\\2>)/"$html$matches);

for (
$i=0$icount($matches[0]); $i++) {
  echo 
"matched: " $matches[0][$i] . "\n";
  echo 
"part 1: " $matches[1][$i] . "\n";
  echo 
"part 2: " $matches[3][$i] . "\n";
  echo 
"part 3: " $matches[4][$i] . "\n\n";
}
?>

Результат работы примера:

matched: <b>bold text</b>
part 1: <b>
part 2: bold text
part 3: </b>

matched: <a href=howdy.html>click me</a>
part 1: <a href=howdy.html>
part 2: click me
part 3: </a>

Смотрите также preg_match(), preg_replace(), и preg_split().



preg_match> <preg_last_error
Last updated: Fri, 19 Jun 2009
 
add a note add a note User Contributed Notes
preg_match_all
ad
01-Apr-2009 01:18
i have made up a simple function to extract a number from a string..

I am not sure how good it is, but it works.

It gets only the numbers 0-9, the "-", " ", "(", ")", "."

characters.. This is as far as I know the most widely used characters for a Phone number.

<?php
function clean_phone_number($phone) {
       if (!empty(
$phone)) {
              
//var_dump($phone);
              
preg_match_all('/[0-9\(\)+.\- ]/s', $phone, $cleaned);
               foreach(
$cleaned[0] as $k=>$v) {
                      
$ready .= $v;
               }
              
var_dump($ready);
               die;
               if (
mb_strlen($cleaned) > 4 && mb_strlen($cleaned) <=25) {
                       return
$cleaned;
               }
               else {
                       return
false;
               }
       }
       return
false;
}
?>
royaltm75 at NOSPAM dot gmail dot com
21-Feb-2009 10:55
The power of pregs is limited only by your *imagination* :)
I wrote this html2a() function using preg recursive match (?R) which provides quite safe and bulletproof html/xml extraction:
<?php
function html2a ( $html ) {
  if ( !
preg_match_all( '
@
\<\s*?(\w+)((?:\b(?:\'[^\']*\'|"[^"]*"|[^\>])*)?)\>
((?:(?>[^\<]*)|(?R))*)
\<\/\s*?\\1(?:\b[^\>]*)?\>
|\<\s*(\w+)(\b(?:\'[^\']*\'|"[^"]*"|[^\>])*)?\/?\>
@uxis'
, $html = trim($html), $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER) )
    return
$html;
 
$i = 0;
 
$ret = array();
  foreach (
$m as $set) {
    if (
strlen( $val = trim( substr($html, $i, $set[0][1] - $i) ) ) )
     
$ret[] = $val;
   
$val = $set[1][1] < 0
     
? array( 'tag' => strtolower($set[4][0]) )
      : array(
'tag' => strtolower($set[1][0]), 'val' => html2a($set[3][0]) );
    if (
preg_match_all( '
/(\w+)\s*(?:=\s*(?:"([^"]*)"|\'([^\']*)\'|(\w+)))?/usix
'
, isset($set[5]) && $set[2][1] < 0
 
? $set[5][0]
  :
$set[2][0]
  ,
$attrs, PREG_SET_ORDER ) ) {
      foreach (
$attrs as $a) {
       
$val['attr'][$a[1]]=$a[count($a)-1];
      }
    }
   
$ret[] = $val;
   
$i = $set[0][1]+strlen( $set[0][0] );
  }
 
$l = strlen($html);
  if (
$i < $l )
    if (
strlen( $val = trim( substr( $html, $i, $l - $i ) ) ) )
     
$ret[] = $val;
  return
$ret;
}
?>

Now let's try it with this example: (there are some really nasty xhtml compliant bugs, but ... we shouldn't worry)

<?php
$html
= <<<EOT
some leftover text...
     < DIV class=noCompliant style = "text-align:left;" >
... and some other ...
< dIv > < empty>  </ empty>
  <p> This is yet another text <br  >
     that wasn't <b>compliant</b> too... <br   />
     </p>
 <div class="noClass" > this one is better but we don't care anyway </div ><P>
    <input   type= "text"  name ='my "name' value  = "nothin really." readonly>
end of paragraph </p> </Div>   </div>   some trailing text
EOT;

$a = html2a($html);
//now we will make some neat html out of it
echo a2html($a);

function
a2html ( $a, $in = "" ) {
  if (
is_array($a) ) {
   
$s = "";
    foreach (
$a as $t)
      if (
is_array($t) ) {
       
$attrs="";
        if ( isset(
$t['attr']) )
          foreach(
$t['attr'] as $k => $v )
           
$attrs.=" ${k}=".( strpos( $v, '"' )!==false ? "'$v'" : "\"$v\"" );
       
$s.= $in."<".$t['tag'].$attrs.( isset( $t['val'] ) ? ">\n".a2html( $t['val'], $in."  " ).$in."</".$t['tag'] : "/" ).">\n";
      } else
       
$s.= $in.$t."\n";
  } else {
   
$s = empty($a) ? "" : $in.$a."\n";
  }
  return
$s;
}
?>
This produces:
some leftover text...
<div class="noCompliant" style="text-align:left;">
  ... and some other ...
  <div>
    <empty>
    </empty>
    <p>
      This is yet another text
      <br/>
      that wasn't
      <b>
        compliant
      </b>
      too...
      <br/>
    </p>
    <div class="noClass">
      this one is better but we don't care anyway
    </div>
    <p>
      <input type="text" name='my "name' value="nothin really." readonly="readonly"/>
      end of paragraph
    </p>
  </div>
</div>
some trailing text
meaneye at mail dot com
15-Oct-2008 09:56
Recently I had to write search engine in hebrew and ran into huge amount of problems. My data was stored in MySQL table with utf8_bin encoding.

So, to be able to write hebrew in utf8 table you need to do
<?php
$prepared_text
= addslashes(urf8_encode($text));
?>

But then I had to find if some word exists in stored text. This is the place I got stuck. Simple preg_match would not find text since hebrew doesnt work that easy. I've tried with /u and who kows what else.

Solution was somewhat logical and simple...
<?php
$db_text
= bin2hex(stripslashes(utf8_decode($db_text)));
$word = bin2hex($word);

$found = preg_match_all("/($word)+/i", $db_text, $matches);
?>

I've used preg_match_all since it returns number of occurences. So I could sort search results acording to that.

Hope someone finds this useful!
MonkeyMan
07-Oct-2008 08:25
Here is a way to match everything on the page, performing an action for each match as you go. I had used this idiom in other languages, where its use is customary, but in PHP it seems to be not quite as common.

<?php
function custom_preg_match_all($pattern, $subject)
{
   
$offset = 0;
   
$match_count = 0;
    while(
preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, $offset))
    {
       
// Increment counter
       
$match_count++;
   
       
// Get byte offset and byte length (assuming single byte encoded)
       
$match_start = $matches[0][1];
       
$match_length = strlen(matches[0][0]);

       
// (Optional) Transform $matches to the format it is usually set as (without PREG_OFFSET_CAPTURE set)
       
foreach($matches as $k => $match) $newmatches[$k] = $match[0];
       
$matches = $new_matches;
   
       
// Your code here
       
echo "Match number $match_count, at byte offset $match_start, $match_length bytes long: ".$matches[0]."\r\n";
           
       
// Update offset to the end of the match
       
$offset = $match_start + $match_length;
    }

    return
$match_count;
}
?>

Note that the offsets returned are byte values (not necessarily number of characters) so you'll have to make sure the data is single-byte encoded. (Or have a look at paolo mosna's strByte function on the strlen manual page).
I'd be interested to know how this method performs speedwise against using preg_match_all and then recursing through the results.
sledge NOSPAM
19-Jun-2008 08:46
Perhaps you want to find the positions of all anchor tags.  This will return a two dimensional array of which the starting and ending positions will be returned.

<?php
function getTagPositions($strBody)
{
   
define(DEBUG, false);
   
define(DEBUG_FILE_PREFIX, "/tmp/findlinks_");
   
   
preg_match_all("/<[^>]+>(.*)<\/[^>]+>/U", $strBody, $strTag, PREG_PATTERN_ORDER);
   
$intOffset = 0;
   
$intIndex = 0;
   
$intTagPositions = array();

    foreach(
$strTag[0] as $strFullTag) {
        if(
DEBUG == true) {
           
$fhDebug = fopen(DEBUG_FILE_PREFIX.time(), "a");
           
fwrite($fhDebug, $fulltag."\n");
           
fwrite($fhDebug, "Starting position: ".strpos($strBody, $strFullTag, $intOffset)."\n");
           
fwrite($fhDebug, "Ending position: ".(strpos($strBody, $strFullTag, $intOffset) + strlen($strFullTag))."\n");
           
fwrite($fhDebug, "Length: ".strlen($strFullTag)."\n\n");
           
fclose($fhDebug);
        }
       
$intTagPositions[$intIndex] = array('start' => (strpos($strBody, $strFullTag, $intOffset)), 'end' => (strpos($strBody, $strFullTag, $intOffset) + strlen($strFullTag)));
       
$intOffset += strlen($strFullTag);
       
$intIndex++;
    }
    return
$intTagPositions;
}

$strBody = 'I have lots of <a href="http://my.site.com">links</a> on this <a href="http://my.site.com">page</a> that I want to <a href="http://my.site.com">find</a> the positions.';

$strBody = strip_tags(html_entity_decode($strBody), '<a>');
$intTagPositions = getTagPositions($strBody);
print_r($intTagPositions);

/*****
Output:

Array (
    [0] => Array (
        [start] => 15
        [end] => 53 )
    [1] => Array (
        [start] => 62
        [end] => 99 )
    [2] => Array (
        [start] => 115
        [end] => 152 )
 )
*****/
?>
spambegone at cratemedia dot com
21-Apr-2008 06:39
I found simpleXML to be useful only in cases where the XML was extremely small, otherwise the server would run out of memory (I suspect there is a memory leak or something?). So while searching for alternative parsers, I decided to try a simpler approach. I don't know how this compares with cpu usage, but I know it works with large XML structures. This is more a manual method, but it works for me since I always know what structure of data I will be receiving.

Essentially I just preg_match() unique nodes to find the values I am looking for, or I preg_match_all to find multiple nodes. This puts the results in an array and I can then process this data as I please.

I was unhappy though, that preg_match_all() stores the data twice (requiring twice the memory), one array for all the full pattern matches, and one array for all the sub pattern matches. You could probably write your own function that overcame this. But for now this works for me, and I hope it saves someone else some time as well.

// SAMPLE XML
<RETS ReplyCode="0" ReplyText="Operation Successful">
  <COUNT Records="14" />
  <DELIMITER value="09" />
  <COLUMNS>PropertyID</COLUMNS>
  <DATA>521897</DATA>
  <DATA>677208</DATA>
  <DATA>686037</DATA>
</RETS>

<?PHP

// SAMPLE FUNCTION
function parse_xml($xml) {
   
   
   
// GET DELIMITER (single instance)
   
$match_res = preg_match('/<DELIMITER value ?= ?"(.*)" ?\/>/', $xml, $matches);
    if(!empty(
$matches[1])) {
       
$results["delimiter"] = chr($matches[1]);
    } else {
       
// DEFAULT DELIMITER
       
$results["delimiter"] = "\t";
    }
    unset(
$match_res, $matches);
   
   
   
// GET MULTIPLE DATA NODES (multiple instances)
   
$results["data_count"] = preg_match_all("/<DATA>(.*)<\/DATA>/", $xml, $matches);
   
// GET MATCHES OF SUB PATTERN, DISCARD THE REST
   
$results["data"]=$matches[1];
    unset(
$match_res, $matches);
   
   
// UNSET XML TO SAVE MEMORY (should unset outside the function as well)
   
unset($xml);

   
// RETURN RESULTS ARRAY
   
return $results;
   
   
}

?>
bruha
04-Mar-2008 08:13
To count str_length in UTF-8 string i use

$count = preg_match_all("/[[:print:]\pL]/u", $str, $pockets);

where
[:print:] - printing characters, including space
\pL - UTF-8 Letter
/u - UTF-8 string
other unicode character properties on http://www.pcre.org/pcre.txt
dolbegraeb
29-Jan-2008 12:30
please note, that the function of "mail at SPAMBUSTER at milianw dot de" can result in invalid xhtml in some cases. think i used it in the right way but my result is sth like this:

<img src="./img.jpg" alt="nice picture" />foo foo foo foo </img>

correct me if i'm wrong.
i'll see when there's time to fix that. -.-
mr davin
12-Jul-2007 09:57
<?php
// Returns an array of strings where the start and end are found
   
function findinside($start, $end, $string) {
       
preg_match_all('/' . preg_quote($start, '/') . '([^\.)]+)'. preg_quote($end, '/').'/i', $string, $m);
        return
$m[1];
    }
   
   
$start = "mary has";
   
$end = "lambs.";
   
$string = "mary has 6 lambs. phil has 13 lambs. mary stole phil's lambs. now mary has all the lambs.";

   
$out = findinside($start, $end, $string);

   
print_r ($out);

/* Results in
(
    [0] =>  6
    [1] =>  all the
)
*/
?>
phektus at gmail dot com
27-Jun-2007 06:22
If you'd like to include DOUBLE QUOTES on a regular expression for use with preg_match_all, try ESCAPING THRICE, as in: \\\"

For example, the pattern:
'/<table>[\s\w\/<>=\\\"]*<\/table>/'

Should be able to match:
<table>
<row>
<col align="left" valign="top">a</col>
<col align="right" valign="bottom">b</col>
</row>
</table>
.. with all there is under those table tags.

I'm not really sure why this is so, but I tried just the double quote and one or even two escape characters and it won't work. In my frustration I added another one and then it's cool.
chuckie
06-Dec-2006 02:20
This is a function to convert byte offsets into (UTF-8) character offsets (this is reagardless of whether you use /u modifier:

<?php

function mb_preg_match_all($ps_pattern, $ps_subject, &$pa_matches, $pn_flags = PREG_PATTERN_ORDER, $pn_offset = 0, $ps_encoding = NULL) {
 
// WARNING! - All this function does is to correct offsets, nothing else:
  //
 
if (is_null($ps_encoding))
   
$ps_encoding = mb_internal_encoding();

 
$pn_offset = strlen(mb_substr($ps_subject, 0, $pn_offset, $ps_encoding));
 
$ret = preg_match_all($ps_pattern, $ps_subject, $pa_matches, $pn_flags, $pn_offset);

  if (
$ret && ($pn_flags & PREG_OFFSET_CAPTURE))
    foreach(
$pa_matches as &$ha_match)
      foreach(
$ha_match as &$ha_match)
       
$ha_match[1] = mb_strlen(substr($ps_subject, 0, $ha_match[1]), $ps_encoding);
   
//
    // (code is independent of PREG_PATTER_ORDER / PREG_SET_ORDER)

 
return $ret;
  }

?>
phpnet at sinful-music dot com
20-Feb-2006 08:53
Here's some fleecy code to 1. validate RCF2822 conformity of address lists and 2. to extract the address specification (the part commonly known as 'email'). I wouldn't suggest using it for input form email checking, but it might be just what you want for other email applications. I know it can be optimized further, but that part I'll leave up to you nutcrackers. The total length of the resulting Regex is about 30000 bytes. That because it accepts comments. You can remove that by setting $cfws to $fws and it shrinks to about 6000 bytes. Conformity checking is absolutely and strictly referring to RFC2822. Have fun and email me if you have any enhancements!

<?php
function mime_extract_rfc2822_address($string)
{
       
//rfc2822 token setup
       
$crlf           = "(?:\r\n)";
       
$wsp            = "[\t ]";
       
$text           = "[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F]";
       
$quoted_pair    = "(?:\\\\$text)";
       
$fws            = "(?:(?:$wsp*$crlf)?$wsp+)";
       
$ctext          = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F" .
                         
"!-'*-[\\]-\\x7F]";
       
$comment        = "(\\((?:$fws?(?:$ctext|$quoted_pair|(?1)))*" .
                         
"$fws?\\))";
       
$cfws           = "(?:(?:$fws?$comment)*(?:(?:$fws?$comment)|$fws))";
       
//$cfws           = $fws; //an alternative to comments
       
$atext          = "[!#-'*+\\-\\/0-9=?A-Z\\^-~]";
       
$atom           = "(?:$cfws?$atext+$cfws?)";
       
$dot_atom_text  = "(?:$atext+(?:\\.$atext+)*)";
       
$dot_atom       = "(?:$cfws?$dot_atom_text$cfws?)";
       
$qtext          = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F!#-[\\]-\\x7F]";
       
$qcontent       = "(?:$qtext|$quoted_pair)";
       
$quoted_string  = "(?:$cfws?\"(?:$fws?$qcontent)*$fws?\"$cfws?)";
       
$dtext          = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F!-Z\\^-\\x7F]";
       
$dcontent       = "(?:$dtext|$quoted_pair)";
       
$domain_literal = "(?:$cfws?\\[(?:$fws?$dcontent)*$fws?]$cfws?)";
       
$domain         = "(?:$dot_atom|$domain_literal)";
       
$local_part     = "(?:$dot_atom|$quoted_string)";
       
$addr_spec      = "($local_part@$domain)";
       
$display_name   = "(?:(?:$atom|$quoted_string)+)";
       
$angle_addr     = "(?:$cfws?<$addr_spec>$cfws?)";
       
$name_addr      = "(?:$display_name?$angle_addr)";
       
$mailbox        = "(?:$name_addr|$addr_spec)";
       
$mailbox_list   = "(?:(?:(?:(?<=:)|,)$mailbox)+)";
       
$group          = "(?:$display_name:(?:$mailbox_list|$cfws)?;$cfws?)";
       
$address        = "(?:$mailbox|$group)";
       
$address_list   = "(?:(?:^|,)$address)+";

       
//output length of string (just so you see how f**king long it is)
       
echo(strlen($address_list) . " ");

       
//apply expression
       
preg_match_all("/^$address_list$/", $string, $array, PREG_SET_ORDER);

        return
$array;
};
?>
mnc at u dot nu
03-Feb-2006 06:05
PREG_OFFSET_CAPTURE always seems to provide byte offsets, rather than character position offsets, even when you are using the unicode /u modifier.

preg_match> <preg_last_error
Last updated: Fri, 19 Jun 2009
 
 
show source | credits | sitemap | contact | advertising | mirror sites