如果你想从字符串中提取所有 {token}s
<?php
$pattern = "/{[^}]*}/";
$subject = "{token1} foo {token2} bar";
preg_match_all($pattern, $subject, $matches);
print_r($matches);
?>
输出
数组
(
[0] => 数组
(
[0] => {token1}
[1] => {token2}
)
)
(PHP 4, PHP 5, PHP 7, PHP 8)
preg_match_all — 执行全局正则表达式匹配
$pattern
,$subject
,&$matches
= null
,$flags
= 0,$offset
= 0在 subject
中搜索与 pattern
中给出的正则表达式匹配的所有内容,并将它们按 flags
指定的顺序放入 matches
中。
找到第一个匹配项后,后续搜索将从最后一个匹配项的末尾继续。
pattern
要搜索的模式,以字符串形式。
subject
输入字符串。
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>",
$out, PREG_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] 包含一个包含由标签包围的字符串的数组。
如果模式包含命名子模式,则 $matches 还会包含具有子模式名称的键的条目。
如果模式包含重复的命名子模式,则只有最右边的子模式存储在 $matches[NAME] 中。
<?php
preg_match_all(
'/(?J)(?<match>foo)|(?<match>bar)/',
'foo bar',
$matches,
PREG_PATTERN_ORDER
);
print_r($matches['match']);
?>
上面的例子将输出
Array ( [0] => [1] => bar )
PREG_SET_ORDER
对结果进行排序,以便 $matches[0] 是一个包含第一组匹配项的数组,$matches[1] 是一个包含第二组匹配项的数组,依此类推。
<?php
preg_match_all("|<[^>]+>(.*)</[^>]+>|U",
"<b>example: </b><div align=\"left\">this is a test</div>",
$out, PREG_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
PREG_OFFSET_CAPTURE
如果传递了此标志,则对于每个出现的匹配项,还将返回附加的字符串偏移量(以字节为单位)。请注意,这会将 matches
的值更改为一个数组的数组,其中每个元素都是一个数组,包含在偏移量 0
处的匹配字符串,以及它在 subject
中的字符串偏移量,在偏移量 1
处。
<?php
preg_match_all('/(foo)(bar)(baz)/', 'foobarbaz', $matches, PREG_OFFSET_CAPTURE);
print_r($matches);
?>
上面的例子将输出
Array ( [0] => Array ( [0] => Array ( [0] => foobarbaz [1] => 0 ) ) [1] => Array ( [0] => Array ( [0] => foo [1] => 0 ) ) [2] => Array ( [0] => Array ( [0] => bar [1] => 3 ) ) [3] => Array ( [0] => Array ( [0] => baz [1] => 6 ) ) )
PREG_UNMATCHED_AS_NULL
如果没有给出顺序标志,则假定为 PREG_PATTERN_ORDER
。
offset
通常,搜索从主题字符串的开头开始。可选参数 offset
可用于指定要开始搜索的替代位置(以字节为单位)。
注意:
使用
offset
不等同于将substr($subject, $offset)
传递给 preg_match_all() 以代替主题字符串,因为pattern
可以包含断言,例如 ^、$ 或 (?<=x)。有关示例,请参阅 preg_match()。
返回完整模式匹配的数量(可能为零),或在失败时返回 false
。
如果传递的正则表达式模式无法编译为有效的正则表达式,则会发出 E_WARNING
。
示例 #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 是反向引用的一个例子。它告诉 pcre 必须匹配正则表达式中的第二个括号
// 本身,在本例中将是 ([\w]+)。额外的反斜杠是必需的,因为字符串在双引号中。
$html = "<b>bold text</b><a href=howdy.html>click me</a>";
preg_match_all("/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/", $html, $matches, PREG_SET_ORDER);
foreach ($matches as $val) {
echo "matched: " . $val[0] . "\n";
echo "part 1: " . $val[1] . "\n";
echo "part 2: " . $val[2] . "\n";
echo "part 3: " . $val[3] . "\n";
echo "part 4: " . $val[4] . "\n\n";
}
?>
上面的例子将输出
matched: <b>bold text</b> part 1: <b> part 2: b part 3: bold text part 4: </b> matched: <a href=howdy.html>click me</a> part 1: <a href=howdy.html> part 2: a part 3: click me part 4: </a>
示例 #3 使用命名子模式
<?php
$str = <<<FOO
a: 1
b: 2
c: 3
FOO;
preg_match_all('/(?P<name>\w+): (?P<digit>\d+)/', $str, $matches);
/* 备选方法 */
// preg_match_all('/(?<name>\w+): (?<digit>\d+)/', $str, $matches);
print_r($matches);
?>
上面的例子将输出
Array ( [0] => Array ( [0] => a: 1 [1] => b: 2 [2] => c: 3 ) [name] => Array ( [0] => a [1] => b [2] => c ) [1] => Array ( [0] => a [1] => b [2] => c ) [digit] => Array ( [0] => 1 [1] => 2 [2] => 3 ) [2] => Array ( [0] => 1 [1] => 2 [2] => 3 ) )
如果你想从字符串中提取所有 {token}s
<?php
$pattern = "/{[^}]*}/";
$subject = "{token1} foo {token2} bar";
preg_match_all($pattern, $subject, $matches);
print_r($matches);
?>
输出
数组
(
[0] => 数组
(
[0] => {token1}
[1] => {token2}
)
)
preg_match_all() 和其他 preg_*() 函数在处理非常长的字符串(至少超过 1Mb)时效果不好。
在这种情况下,函数返回 FALSE 并且 $matchers 的值不可预测,可能包含一些值,也可能为空。
在这种情况下,解决方法是将长字符串预先分割成多个部分,例如用 explode() 函数根据某些标准分割长字符串,然后对每个部分应用 preg_match_all() 函数。
此情况的典型场景是通过正则表达式进行日志分析。
已在 PHP 7.2.0 上测试。
这里有一个很棒的在线正则表达式编辑器 https://regex101.com/
它可以帮助您测试您的正则表达式(prce、js、python),并实时突出显示数据输入上的正则表达式匹配。
john at mccarthy dot net 发布的代码是不必要的。如果您希望将结果按单个匹配项进行分组,只需使用
<?
preg_match_all($pattern, $string, $matches, PREG_SET_ORDER);
?>
例如:
<?
preg_match_all('/([GH])([12])([!?])/', 'G1? H2!', $matches); // 默认 PREG_PATTERN_ORDER
// $matches = array(0 => array(0 => 'G1?', 1 => 'H2!'),
// 1 => array(0 => 'G', 1 => 'H'),
// 2 => array(0 => '1', 1 => '2'),
// 3 => array(0 => '?', 1 => '!'))
preg_match_all('/([GH])([12])([!?])/', 'G1? H2!', $matches, PREG_SET_ORDER);
// $matches = array(0 => array(0 => 'G1?', 1 => 'G', 2 => '1', 3 => '?'),
// 1 => array(0 => 'H2!', 1 => 'H', 2 => '2', 3 => '!'))
?>
以下是一个函数,它用数字替换字符串中所有出现的数字。
<?php
function decremente_chaine($chaine)
{
// 获取所有出现的数字及其索引
preg_match_all("/[0-9]+/",$chaine,$out,PREG_OFFSET_CAPTURE);
// 遍历出现次数
for($i=0;$i<sizeof($out[0]);$i++)
{
$longueurnombre = strlen((string)$out[0][$i][0]);
$taillechaine = strlen($chaine);
// 将字符串分成 3 个部分
$debut = substr($chaine,0,$out[0][$i][1]);
$milieu = ($out[0][$i][0])-1;
$fin = substr($chaine,$out[0][$i][1]+$longueurnombre,$taillechaine);
// 如果是 10、100、1000 等,则将所有数字向后移一位,因为结果少了一个数字
if(preg_match('#[1][0]+$#', $out[0][$i][0]))
{
for($j = $i+1;$j<sizeof($out[0]);$j++)
{
$out[0][$j][1] = $out[0][$j][1] -1;
}
}
$chaine = $debut.$milieu.$fin;
}
return $chaine;
}
?>
要计算 UTF-8 字符串中的 str_length,我使用
$count = preg_match_all("/[[:print:]\pL]/u", $str, $pockets);
其中
[:print:] - 可打印字符,包括空格
\pL - UTF-8 字母
/u - UTF-8 字符串
其他 Unicode 字符属性请参阅 http://www.pcre.org/pcre.txt
这里有一些柔顺的代码,用于 1. 验证地址列表的 RCF2822 符合性,以及 2. 提取地址规范(通常称为“电子邮件”)。我建议不要在输入表单的电子邮件检查中使用它,但它可能是您在其他电子邮件应用程序中想要的。我知道它可以进一步优化,但我将把这部分留给你们这些“敲代码者”。生成的正则表达式的总长度约为 30000 字节。这是因为它接受注释。您可以通过将 $cfws 设置为 $fws 来删除它,它会缩小到大约 6000 字节。符合性检查绝对严格地参照 RFC2822。玩得开心,如果您有任何改进,请给我发电子邮件!
<?php
function mime_extract_rfc2822_address($string)
{
// RFC2822 令牌设置
$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; // 评论的替代方案
$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)+";
// 输出字符串长度(让你知道它有多长)
echo(strlen($address_list) . " ");
// 应用表达式
preg_match_all("/^$address_list$/", $string, $array, PREG_SET_ORDER);
return $array;
};
?>
这是一个将字节偏移量转换为(UTF-8)字符偏移量的函数(无论你是否使用/u 修饰符)
<?php
function mb_preg_match_all($ps_pattern, $ps_subject, &$pa_matches, $pn_flags = PREG_PATTERN_ORDER, $pn_offset = 0, $ps_encoding = NULL) {
// 注意! - 此函数只修正偏移量,其他什么都不做:
//
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);
//
// (代码独立于 PREG_PATTER_ORDER / PREG_SET_ORDER)
return $ret;
}
?>
我发现 simpleXML 只有在 XML 非常小的情况下才有用,否则服务器会因内存不足而崩溃(我怀疑是存在内存泄漏或其他问题?)。因此,在寻找替代解析器时,我决定尝试一种更简单的方法。我不知道这与 CPU 使用率相比如何,但我确定它对大型 XML 结构有效。这更像是一种手动方法,但对我来说很有效,因为我一直都知道我会接收什么数据结构。
本质上,我只是使用 preg_match() 匹配唯一节点以找到我想要的值,或者使用 preg_match_all() 找到多个节点。这将结果放在一个数组中,然后我就可以根据需要处理这些数据。
不过,我并不满意 preg_match_all() 会将数据存储两次(需要两倍的内存),一个数组用于所有完整模式匹配,另一个数组用于所有子模式匹配。你可能可以编写自己的函数来克服这个问题。但目前这对我很有效,我希望它也能帮助其他人节省一些时间。
// 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
// 示例函数
function parse_xml($xml) {
// 获取分隔符(单例)
$match_res = preg_match('/<DELIMITER value ?= ?"(.*)" ?\/>/', $xml, $matches);
if(!empty($matches[1])) {
$results["delimiter"] = chr($matches[1]);
} else {
// 默认分隔符
$results["delimiter"] = "\t";
}
unset($match_res, $matches);
// 获取多个数据节点(多个实例)
$results["data_count"] = preg_match_all("/<DATA>(.*)<\/DATA>/", $xml, $matches);
// 获取子模式的匹配项,丢弃其余项
$results["data"]=$matches[1];
unset($match_res, $matches);
// 取消设置 XML 以节省内存(也应该在函数外部取消设置)
unset($xml);
// 返回结果数组
return $results;
}
?>
最近我不得不写一个希伯来语搜索引擎,遇到了很多问题。我的数据存储在带有 utf8_bin 编码的 MySQL 表中。
所以,为了能够在 utf8 表中写入希伯来语,你需要这样做
<?php
$prepared_text = addslashes(urf8_encode($text));
?>
但我必须找到某个单词是否存在于存储的文本中。这就是我卡住的地方。简单的 preg_match 无法找到文本,因为希伯来语没有那么简单。我尝试了 /u 和其他一些东西。
解决方案有点合乎逻辑且简单...
<?php
$db_text = bin2hex(stripslashes(utf8_decode($db_text)));
$word = bin2hex($word);
$found = preg_match_all("/($word)+/i", $db_text, $matches);
?>
我使用了 preg_match_all,因为它返回了出现次数。因此我可以根据此对搜索结果进行排序。
希望有人发现它有用!
我需要一个函数来旋转 preg_match_all 查询的结果,并创建了这个函数。不确定它是否存在。
<?php
function turn_array($m)
{
for ($z = 0;$z < count($m);$z++)
{
for ($x = 0;$x < count($m[$z]);$x++)
{
$rt[$x][$z] = $m[$z][$x];
}
}
return $rt;
}
?>
示例 - 获取某些 preg_match_all 查询的结果
数组
(
[0] => 数组
(
[1] => Banff
[2] => Canmore
[3] => Invermere
)
[1] => 数组
(
[1] => AB
[2] => AB
[3] => BC
)
[2] => 数组
(
[1] => 51.1746254
[2] => 51.0938416
[3] => 50.5065193
)
[3] => 数组
(
[1] => -115.5719757
[2] => -115.3517761
[3] => -116.0321884
)
[4] => 数组
(
[1] => T1L 1B3
[2] => T1W 1N2
[3] => V0B 2G0
)
)
将它旋转 90 度以将结果分组为记录
数组
(
[0] => 数组
(
[1] => Banff
[2] => AB
[3] => 51.1746254
[4] => -115.5719757
[5] => T1L 1B3
)
[1] => 数组
(
[1] => Canmore
[2] => AB
[3] => 51.0938416
[4] => -115.3517761
[5] => T1W 1N2
)
[2] => 数组
(
[1] => Invermere
[2] => BC
[3] => 50.5065193
[4] => -116.0321884
[5] => V0B 2G0
)
)
小心使用此模式匹配和 preg_match_* 函数上的大型输入缓冲区。
<?php
$pattern = '/\{(?:[^{}]|(?R))*\}/';
preg_match_all($pattern, $buffer, $matches);
?>
如果 $buffer 的大小为 80+ KB,你最终会得到段错误!
[89396.588854] php[4384]: segfault at 7ffd6e2bdeb0 ip 00007fa20c8d67ed sp 00007ffd6e2bde70 error 6 in libpcre.so.3.13.1[7fa20c8c3000+3c000]
这是由于 PCRE 递归造成的。这是 PHP 自 2008 年以来已知的一个错误,但它的来源不是 PHP 本身,而是 PCRE 库。
Rasmus Lerdorf 给出了答案:https://bugs.php.net/bug.php?id=45735#1365812629
"这里的问题是,没有办法检测到失控的正则表达式
这里没有巨大的性能和内存开销。是的,我们可以以一种方式构建 PCRE
它不会导致段错误,我们可以将默认回溯限制提高到
一个巨大的数字,但这会大大降低每次正则表达式调用的速度。如果 PCRE
提供了一种更优雅的方式来处理这个问题,而不会影响性能
点击,当然我们会使用它。"
我编写了一个简单的函数来从字符串中提取数字。
我不确定它有多好,但它确实有效。
它只获取数字 0-9、"-"、" "、"("、")"、"."
字符。据我所知,这是电话号码最常用的字符。
<?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;
}
?>
最好使用 preg_replace 将文本转换为带有 <a> 标签的可点击链接
$html = preg_replace('"\b(http://\S+)"', '<a href="$1">$1</a>', $text);
用于解析带有实体的查询
<?php
preg_match_all("/(?:^|(?<=\&(?![a-z]+\;)))([^\=]+)=(.*?)(?:$|\&(?![a-z]+\;))/i",
$s, $m, PREG_SET_ORDER );
?>
也许你想找到所有锚标签的位置。这将返回一个二维数组,其中将返回起始和结束位置。
<?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 )
)
*****/
?>
一个多字节安全的 preg_match_all,它在使用 PREG_OFFSET_CAPTURE 处理 utf-8 字符串时修复了捕获偏移量
<?php
function mb_preg_match_all($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) {
$out=preg_match_all($pattern, $subject, $matches, $flags, $offset);
if($flags & PREG_OFFSET_CAPTURE && is_array($matches) && count($matches)>0) {
foreach ($matches[0] as &$match) {
$match[1] = mb_strlen(substr($subject, 0, $match[1]));
}
}
return $out;
}
?>
有时你不仅仅想挑选匹配项,还需要整个主体由匹配的子字符串构成,因此主体的每个字符都是匹配项的一部分。现有的 preg_* 函数都不容易适用于此任务,因此我创建了 preg_match_entire() 函数。
它使用 (*MARK) 语法,该语法在以下文档中有所介绍:https://pcre.org/original/doc/html/pcrepattern.html#SEC27
<?php
// 返回:匹配到的数组
// 如果字符串不是模式的重复则为 null
// 错误时为 false
function preg_match_entire( string $pattern, string $subject, int $flags = 0 ){
// 重建并包装模式
$delimiter = $pattern[0];
$ldp = strrpos( $pattern, $delimiter );
$pattern = substr( $pattern, 1, $ldp - 1 );
$modifiers = substr( $pattern, $ldp + 1 );
$pattern = "{$delimiter} \G\z (*MARK:END) | \G (?:{$pattern}) {$delimiter}x{$modifiers}";
$r = preg_match_all( $pattern, $subject, $m, PREG_SET_ORDER | $flags );
if( $r === false ) return false; // 错误
$end = array_pop( $m );
if( $end === null || ! isset( $end['MARK']) || $end['MARK'] !== 'END')
return null; // 未到达字符串末尾
return $m; // 返回实际匹配项,可能为空数组
}
// 相同的结果:
test('#{\d+}#', ''); // []
test('#{\d+}#', '{11}{22}{33}'); // {11},{22},{33}
// 不同的结果:preg_match_entire 不会匹配此项:
test('#{\d+}#', '{11}{}{aa}{22},{{33}}');
// preg_match_entire: null
// preg_match_all: {11},{22},{33}
function test( $pattern, $subject ){
echo "pattern: $pattern\n";
echo "subject: $subject\n";
print_matches('preg_match_entire: ', preg_match_entire( $pattern, $subject ));
preg_match_all( $pattern, $subject, $matches, PREG_SET_ORDER );
print_matches('preg_match_all: ', $matches );
echo "\n";
}
function print_matches( $t, $m ){
echo $t, is_array( $m ) && $m ? implode(',', array_column( $m, 0 )) : json_encode( $m ), "\n";
} ?>
<?php
// 允许在 html 文本中有限的跨度格式
$str='<span style="text-decoration-line: underline; font-weight: bold; font-style: italic;">White</span>
<span style="text-decoration-line: underline;">RED</span><span style="color:blue">blue</span>';
function next_format($str)
{
$array=array("text-decoration-line"=>"underline","font-weight"=>"bold","font-style"=>"italic");
foreach ($array as $key=>$val)
{
if($str[1]==$key && $str[2]==$val)
{
return $str[1].': '.$str[2].";";
}
}
return '';
}
function next_span($matches)
{
$needFormat=preg_replace_callback('/([a-z\-]+):\s*([^;]+)(;|)/ism',"next_format",$matches[2]);
return $matches[1].$needFormat.$matches[3];
}
echo preg_replace_callback(
"/(\<span\s+style\=\")([^\"]+)(\">)/ism",
"next_span",
$str);
?>
为什么 <?php preg_match_all('/(?:^|\s)(ABC|XYZ)(?:\s|$)/i', 'ABC XYZ', $match) ?> 仅找到 'ABC'?
因为第一个完全匹配是 'ABC ' - 包含尾部空格。而这个空格不可用于进一步处理。
使用后视和前视来解决这个问题: <?php preg_match_all('/(?<=^|\s)(ABC|XYZ)(?=\s|$)/i', 'ABC XYZ', $match) ?>
如果设置了 PREG_OFFSET_CAPTURE,则不匹配的捕获(即带 '?' 的捕获)将不会出现在结果数组中。这可能是因为没有偏移量,因此原始的 PHP 开发人员决定最好将其省略。
当正则表达式用于字符串的较长和较短版本时,
仅捕获了长短版本之一。
当正则表达式匹配发生在字符串的某个位置时,
对于该位置,仅将一个匹配保存到 matches[0] 中。
如果使用 '?',正则表达式是贪婪的,并且会捕获更长的版本,
如果使用 '|',则会捕获最先匹配的变体。
<?php
preg_match_all('/ab|abc/','abc',$m);
var_dump($m);
preg_match_all('/abc?/','abc',$m);
var_dump($m);
?>
在 $m[0] 中预期 'ab' 和 'abc',但事实并非如此,
实际上它们输出 [['ab']] 和 [['abc']]
array(1) {
[0]=>
array(1) {
[0]=>
string(2) "ab"
}
}
array(1) {
[0]=>
array(1) {
[0]=>
string(3) "abc"
}
}
示例
$file = file_get_contents('file');
if(preg_match_all('#Task To Run(.*)#s', $file, $m)) {
var_dump($m);
}
没有输出...
如果文件存在 BOM 字节 (FF FE),则 preg_match_all 无法工作。
╰─$ head -n1 file | hexdump -C
00000000 ff fe 48 00 6f 00 73 00 74 00 4e 00 61 00 6d 00 |..H.o.s.t.N.a.m.|
使用 dos2unix 清除 BOM
╰─$ dos2unix file
dos2unix: 正在将 UTF-16LE 文件 file 转换为 UTF-8 Unix 格式...
再次检查
╰─$ head -n1 file | hexdump -C
00000000 48 6f 73 74 4e 61 6d 65 3a 20 20 20 20 20 20 20 |HostName: |
太好了!现在 preg_match_all 工作正常了。
使用 preg_match_all,我创建了一个非常方便的函数。
<?php
function reg_smart_replace($pattern, $replacement, $subject, $replacementChar = "$$$", $limit = -1)
{
if (! $pattern || ! $subject || ! $replacement ) { return false; }
$replacementChar = preg_quote($replacementChar);
preg_match_all ( $pattern, $subject, $matches);
if ($limit > -1) {
foreach ($matches as $count => $value )
{
if ($count + 1 > $limit ) { unset($matches[$count]); }
}
}
foreach ($matches[0] as $match) {
$rep = ereg_replace($replacementChar, $match, $replacement);
$subject = ereg_replace($match, $rep, $subject);
}
return $subject;
}
?>
这个函数可以将文本块转换为可点击的链接或其他内容。示例
<?php
reg_smart_replace(EMAIL_REGEX, '<a href="mailto:$$$">$$$</a>', $description)
?>
将把所有电子邮件地址转换为实际链接。
只需用正则表达式将找到的文本替换为 $$$。如果你不能使用 $$$,则使用第四个参数 $replacementChar
以下是如何匹配页面上的所有内容,并在处理每个匹配项时执行一个操作。我曾在其他语言中使用过这种用法,它很常见,但在 PHP 中似乎并不那么常见。
<?php
function custom_preg_match_all($pattern, $subject)
{
$offset = 0;
$match_count = 0;
while(preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, $offset))
{
// 递增计数器
$match_count++;
// 获取字节偏移量和字节长度(假设单字节编码)
$match_start = $matches[0][1];
$match_length = strlen(matches[0][0]);
// (可选)将 $matches 转换为通常设置的格式(不设置 PREG_OFFSET_CAPTURE)
foreach($matches as $k => $match) $newmatches[$k] = $match[0];
$matches = $new_matches;
// 你的代码在这里
echo "匹配编号 $match_count,字节偏移量为 $match_start,长度为 $match_length 字节:".$matches[0]."\r\n";
// 将偏移量更新到匹配的末尾
$offset = $match_start + $match_length;
}
return $match_count;
}
?>
请注意,返回的偏移量是字节值(不一定是字符数),因此您需要确保数据是单字节编码的。(或者在 strlen 手册页上查看 paolo mosna 的 strByte 函数)。
我很想知道这种方法在速度上与使用 preg_match_all 然后递归遍历结果的比较。
我一直在使用 Regex101 和 `preg_match_all()` 测试器在线编写和测试一些正则表达式模式,发现我编写的正则表达式模式在它们上可以正常工作,但在我自己的代码中却不行。
我的问题是双重转义反斜杠字符
<?php
// 输入测试
$input = "\"something\",\"something here\",\"some\nnew\nlines\",\"this is the end\"";
// 在在线正则表达式测试器中工作,在 PHP 中不工作
preg_match_all( "/(?:,|^)(?<!\\)\".*?(?<!\\)\"(?:(?=,)|$)/s", $input, $matches );
/*
输出:NULL
*/
// 在在线正则表达式测试器中工作,在 PHP 中工作
preg_match_all( "/(?:,|^)(?<!\\\\)\".*?(?<!\\\\)\"(?:(?=,)|$)/s", $input, $matches );
/*
输出:
array(2) {
[0]=>
array(4) {
[0]=>
string(11) ""something""
[1]=>
string(17) ","something here""
[2]=>
string(17) ","some
new
lines""
[3]=>
string(18) ","this is the end""
}
[1]=>
array(4) {
[0]=>
string(9) "something"
[1]=>
string(14) "something here"
[2]=>
string(14) "some
new
lines"
[3]=>
string(15) "this is the end"
}
}
*/
?>
如果你想在正则表达式中包含双引号以用于 preg_match_all,尝试三次转义,例如:\\\"
例如,模式
'/<table>[\s\w\/<>=\\\"]*<\/table>/'
应该能够匹配
<table>
<row>
<col align="left" valign="top">a</col>
<col align="right" valign="bottom">b</col>
</row>
</table>
..以及这些 table 标签下的所有内容。
我不确定为什么会出现这种情况,但我尝试过只使用双引号和一个或两个转义字符,但它不起作用。在沮丧之下,我添加了另一个,然后它就正常工作了。
我收到了投诉,说我的 html2a() 代码(见下文)在某些情况下不起作用。
然而,这不是算法或过程的问题,而是 PCRE 递归堆栈限制的问题。
如果你使用递归 PCRE (?R),你应该记住要增加这两个 ini 设置
ini_set('pcre.backtrack_limit', 10000000);
ini_set('pcre.recursion_limit', 10000000);
但请注意:(来自 php.ini)
; 请注意,如果你将此值设置为一个高数字,你可能会消耗所有
; 可用的进程堆栈,最终导致 PHP 崩溃(由于达到
; 操作系统施加的堆栈大小限制)。
我写这个例子主要是为了展示 PCRE 语言的力量,而不是它实现的力量 :)
但如果你喜欢它,请使用它,当然要自担风险。
因为我想为自己的目的创建一个干净的 PHP 类来处理 XML 文件,它结合了 DOM 和 simplexml 函数的使用,我遇到了一个很小但非常烦人的问题,那就是路径中的偏移量在两者中编号不同。
也就是说,例如,如果我获得一个 DOM xpath 对象,它看起来像
/ANODE/ANOTHERNODE/SOMENODE[9]/NODE[2]
而作为 simplexml 对象将等效于
ANODE->ANOTHERNODE->SOMENODE[8]->NODE[1]
所以你明白我的意思吗?我使用 preg_match_all 来解决这个问题,最终我经过几个小时的头痛后得到了这个(因为我是法国人,变量的名称是法语,抱歉),希望它对你们中的有些人有用
<?php
function decrease_string($string)
{
/* 从原始字符串中检索所有数字及其偏移量: */
preg_match_all("/[0-9]+/",$chaine,$out,PREG_OFFSET_CAPTURE);
for($i=0;$i<sizeof($out[0]);$i++)
{
$longueurnombre = strlen((string)$out[0][$i][0]);
$taillechaine = strlen($chaine);
// 将字符串分割成三部分
$debut = substr($chaine,0,$out[0][$i][1]);
$milieu = ($out[0][$i][0])-1;
$fin = substr($chaine,$out[0][$i][1]+$longueurnombre,$taillechaine);
/* 如果是10、100、1000,问题是字符串会变短,所有的偏移量都会发生变化,所以我们必须将它们减少 1 */
if(preg_match('#[1][0]+$#', $out[0][$i][0]))
{
for($j = $i+1;$j<sizeof($out[0]);$j++)
{
$out[0][$j][1] = $out[0][$j][1] -1;
}
}
$chaine = $debut.$milieu.$fin;
}
return $chaine;
}
?>
请注意,“mail at SPAMBUSTER at milianw dot de” 的函数在某些情况下可能会导致无效的 xhtml。我认为我使用的方式是正确的,但我的结果是这样的
<img src="./img.jpg" alt="nice picture" />foo foo foo foo </img>
如果我错了,请纠正我。
我会在有时间的时候看看如何修复它。 -.-
这里 http://tryphpregex.com/ 是一个基于 PHP 的在线正则表达式编辑器,可以帮助您测试正则表达式,并在数据输入时实时突出显示正则表达式匹配。
从 csv 字符串中提取字段:(因为在 php5.3 之前,你不能使用 str_getcsv 函数)
这是正则表达式
<?php
$csvData = <<<EOF
10,'20',"30","'40","'50'","\"60","70,80","09\\/18,/\"2011",'a,sdfcd'
EOF
$reg = <<<EOF
/
(
(
([\'\"])
(
(
[^\'\"]
|
(\\\\.)
)*
)
(\\3)
|
(
[^,]
|
(\\\\.)
)*
),)
/x
EOF;
preg_match_all($reg,$csvData,$matches);
// 提取 csv 字段
print_r($matches[2]);
?>
<?php
// 返回一个字符串数组,其中包含找到的开始和结束位置
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);
/* 结果是
(
[0] => 6
[1] => all the
)
*/
?>
pregs 的力量只受限于你的 *想象力* :)
我使用递归匹配 (?R) 写了这个 html2a() 函数,它提供了相当安全和防弹的 html/xml 提取
<?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;
}
?>
现在让我们用这个例子来试试: (有一些非常糟糕的符合 XHTML 的错误,但是...我们不应该担心)
<?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);
//现在我们将用它创建一些漂亮的 HTML
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;
}
?>
这将产生
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
下一个函数几乎适用于任何复杂的 XML/XHTML 字符串
<?php
/**
* 查找并关闭未关闭的 XML 标签
**/
function close_tags($text) {
$patt_open = "%((?<!</)(?<=<)[\s]*[^/!>\s]+(?=>|[\s]+[^>]*[^/]>)(?!/>))%";
$patt_close = "%((?<=</)([^>]+)(?=>))%";
if (preg_match_all($patt_open,$text,$matches))
{
$m_open = $matches[1];
if(!empty($m_open))
{
preg_match_all($patt_close,$text,$matches2);
$m_close = $matches2[1];
if (count($m_open) > count($m_close))
{
$m_open = array_reverse($m_open);
foreach ($m_close as $tag) $c_tags[$tag]++;
foreach ($m_open as $k => $tag) if ($c_tags[$tag]--<=0) $text.='</'.$tag.'>';
}
}
}
return $text;
}
?>
// 这是一个允许你使用 preg_match_all 数组的函数
function getMatches($pattern, $subject) {
$matches = array();
if (is_array($pattern)) {
foreach ($pattern as $p) {
$m = getMatches($p, $subject);
foreach ($m as $key => $match) {
if (isset($matches[$key])) {
$matches[$key] = array_merge($matches[$key], $m[$key]);
} else {
$matches[$key] = $m[$key];
}
}
}
} else {
preg_match_all($pattern, $subject, $matches);
}
return $matches;
}
$patterns = array(
'/<span>(.*?)<\/span>/',
'/<a href=".*?">(.*?)<\/a>/'
);
$html = '<span>some text</span>';
$html .= '<span>some text in another span</span>';
$html .= '<a href="path/">here is the link</a>';
$html .= '<address>address is here</address>';
$html .= '<span>here is one more span</span>';
$matches = getMatches($patterns, $html);
print_r($matches); // 结果如下
/*
数组
(
[0] => 数组
(
[0] => <span>some text</span>
[1] => <span>some text in another span</span>
[2] => <span>here is one more span</span>
[3] => <a href="path/">here is the link</a>
)
[1] => 数组
(
[0] => some text
[1] => some text in another span
[2] => here is one more span
[3] => here is the link
)
)
*/