如果您想从字符串中提取所有 {token}。
<?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 "匹配: " . $val[0] . "\n";
echo "部分 1: " . $val[1] . "\n";
echo "部分 2: " . $val[2] . "\n";
echo "部分 3: " . $val[3] . "\n";
echo "部分 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}。
<?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/
它可以帮助您测试正则表达式 (pcre、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 => '!'))
?>
这是一个将字符串中所有数字都替换为数字减1的函数:
<?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);
// 将字符串分成三部分
$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 年以来已知的一个 bug,但其来源并非 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, "起始位置: ".strpos($strBody, $strFullTag, $intOffset)."\n");
fwrite($fhDebug, "结束位置: ".(strpos($strBody, $strFullTag, $intOffset) + strlen($strFullTag))."\n");
fwrite($fhDebug, "长度: ".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 = '我有很多<a href="http://my.site.com">链接</a>在这个<a href="http://my.site.com">页面</a>上,我想找到它们的位置。';
$strBody = strip_tags(html_entity_decode($strBody), '<a>');
$intTagPositions = getTagPositions($strBody);
print_r($intTagPositions);
/*****
输出:
Array (
[0] => Array (
[start] => 15
[end] => 53 )
[1] => Array (
[start] => 62
[end] => 99 )
[2] => Array (
[start] => 115
[end] => 152 )
)
*****/
?>
一个多字节安全的 preg_match_all,修复了在 utf-8 字符串上使用 PREG_OFFSET_CAPTURE 时的捕获偏移量。
<?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\n";
echo "目标字符串: $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 文本中使用有限的 span 格式
$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']]
数组(1) {
[0]=>
数组(1) {
[0]=>
字符串(2) "ab"
}
}
数组(1) {
[0]=>
数组(1) {
[0]=>
字符串(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("$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;
}
?>
请注意,“[email protected]”邮箱的函数在某些情况下可能会导致无效的 XHTML。我认为我的使用方法正确,但结果却类似这样:
<img src="./img.jpg" alt="nice picture" />foo foo foo foo </img>
如果我错了,请纠正我。
如果有时间我会处理这个问题。 -.-
这里http://tryphpregex.com/是一个基于PHP的在线正则表达式编辑器,它可以帮助你测试正则表达式,并实时高亮显示数据输入中的正则表达式匹配。
从CSV字符串中提取字段:(因为在PHP 5.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
)
*/
?>
preg 的强大之处仅受限于你的 *想象力* :)
我使用递归匹配 (?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
一些剩余文本...
< DIV class=noCompliant style = "text-align:left;" >
... 和其他一些 ...
< dIv > < empty> </ empty>
<p> 这又是另一段文本 <br >
它也不 <b>符合规范</b>... <br />
</p>
<div class="noClass" > 这个更好,但我们不在乎 </div ><P>
<input type= "text" name ='my "name' value = "nothin really." readonly>
段落结尾 </p> </Div> </div>一些尾随文本
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;
}
?>
这将生成
一些剩余文本...
<div class="noCompliant" style="text-align:left;">
... 和其他一些 ...
<div>
<empty>
</empty>
<p>
这又是另一段文本
<br/>
它也不
<b>
符合规范
</b>
...
<br/>
</p>
<div class="noClass">
这个更好,但我们不在乎
</div>
<p>
<input type="text" name='my "name' value="nothin really." readonly="readonly"/>
段落结尾
</p>
</div>
</div>
一些尾随文本
下一个函数几乎可以处理任何复杂的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;
}
?>
// 此函数允许你使用正则表达式匹配模式数组
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
)
)
*/