php 'preg_match_all' 和 'str_replace':用数组键替换常量的正则表达式
Posted
技术标签:
【中文标题】php \'preg_match_all\' 和 \'str_replace\':用数组键替换常量的正则表达式【英文标题】:php 'preg_match_all' and 'str_replace': regular expression to replace constants with array keysphp 'preg_match_all' 和 'str_replace':用数组键替换常量的正则表达式 【发布时间】:2022-01-23 13:41:51 【问题描述】:我需要实现一个 preg_replace 来修复我在大量脚本中遇到的一些警告。
我的目标是替换...
$variable[key] = "WhatElse";
$result = $wso->RSLA("7050", $vegalot, "600", "WFID_OK_WEB","1300", $_POST[username]);
if ($result[ECD] != 0)
if ($line=="AAAA" && in_array(substr($wso->lot,0,7),$lot_aaaa_list) && $lot[wafer][25])
... 相同的语句将 CONSTANTS 替换为 ARRAY KEYS ...
$variable['key'] = "WhatElse";
$result = $wso->RSLA("7050", $vegalot, "600", "WFID_OK_WEB","1300", $_POST['username']);
if ($result['ECD'] != 0)
if ($line=="AAAA" && in_array(substr($wso->lot,0,7),$lot_aaaa_list) && $lot[wafer][25])
但不包括在字符串中声明数组变量的情况,即...
$output = "<input name='variable[key]' has to be preserved as it is.";
$output = 'Even this string variable[key] has to be preserved as it is.';
...因为它们将被替换(但这不是我想要的):
$output = "<input name='variable['key']' has to be preserved as it is.";
$output = 'Even this string variable['key'] has to be preserved as it is.';
每个语句都由“preg_match_all”语句标识,然后替换为“str_replace”:
preg_match_all('/(\[(\w*)\])/', $str, $matches, PREG_SET_ORDER, 0);
$replace_str = $str;
$local_changeflag = false;
foreach($matches as $m)
if (!$m[2]) continue;
if (is_numeric($m[2])) continue;
$replace_str = str_replace($m[1], "['" . $m[2] . "']", $replace_str);
$local_changeflag = true;
您有什么建议可以更好地解决我遇到的此类问题吗?
【问题讨论】:
尝试like this demo 跳过引用的部分(不确定这个想法是否好)。 或者,this one,如果您只想匹配方括号内的有效标识符 ('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1(*SKIP)(*F)|(\[(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)])/'
)。
$lot[wafer]
中的晶圆不应该也被引用吗?
【参考方案1】:
如果你想在方括号内包裹任何有效的标识符,你可以直接使用preg_replace
:
$regex = '/(["\'])(?:(?=(\\\\?))\2.)*?\1(*SKIP)(*F)|\[([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)]/s';
$ouptut = preg_replace($regex, '$3', $text);
请参阅regex demo。 详情:
(["'])(?:(?=(\\?))\2.)*?\1
- 匹配单引号或双引号之间的字符串(包含两个捕获组)
(*SKIP)(*F)
- 丢弃匹配的文本并使匹配失败,从失败位置开始新的搜索
|
- 或
\[
- [
字符
([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)
- 第 3 组:字母、下划线或 \x7f-\xff
范围内的任何字符,然后是 \x7f-\xff
范围内的任何字母数字、下划线或任何字符
]
- ]
字符。
见php demo:
$regex = '/(["\'])(?:(?=(\\\\?))\2.)*?\1(*SKIP)(*F)|\[([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)]/s';
$str = '$output = "<input name=\'variable[key]\' has to be preserved as it is.";
$output = \'Even this string variable[key] has to be preserved as it is.\';
$variable[key] = "WhatElse";
$result = $wso->RSLA("7050", $vegalot, "600", "WFID_OK_WEB","1300", $_POST[username]);
if ($result[ECD] != 0)
if ($line=="AAAA" && in_array(substr($wso->lot,0,7),$lot_aaaa_list) && $lot[wafer][25]) ';
echo preg_replace($regex, "['\$3']", $str);
输出:
$output = "<input name='variable[key]' has to be preserved as it is.";
$output = 'Even this string variable[key] has to be preserved as it is.';
$variable['key'] = "WhatElse";
$result = $wso->RSLA("7050", $vegalot, "600", "WFID_OK_WEB","1300", $_POST['username']);
if ($result['ECD'] != 0)
if ($line=="AAAA" && in_array(substr($wso->lot,0,7),$lot_aaaa_list) && $lot['wafer'][25])
【讨论】:
【参考方案2】:[我知道这不是正则表达式,但既然您要求“更好地解决此类问题的建议”,我给您 2 美分]
简单地解析代码怎么样;):
$source = file_get_contents('/tmp/test.php'); // Change this
$tokens = token_get_all($source);
$history = [];
foreach ($tokens as $token)
if (is_string($token)) // simple 1-character token
array_push($history, str_replace(["\r", "\n"], '', $token));
$history = array_slice($history, -2);
echo $token;
else
list($id, $text) = $token;
switch ($id)
case T_STRING:
if ($history == [T_VARIABLE, '['])
// Token sequence is [T_VARIABLE, '[', T_STRING]
echo "'$text'";
else
echo $text;
break;
default:
// anything else -> output "as is"
echo $text;
break;
array_push($history, $id);
$history = array_slice($history, -2);
当然,$source
需要更改为适合您的任何内容。 token_get_all()
然后加载 PHP 代码并将其解析为令牌列表。然后根据我们的需要逐项处理该列表,并可能在再次输出之前对其进行更改。
像 [
这样的 1 字符标记(f.ex $myVariable[1]
中的“[”和“]”都成为标记)是必须在循环中处理的特殊情况。否则 $token 是一个数组,其中包含令牌类型和令牌本身的 ID。
“不幸”T_STRING
是一种一般情况,因此为了仅确定在数组索引中用作常量的字符串,我们将当前前面的 2 项存储在 $history
中。 ("$myVariable" 和 "[")
..而且..就是这样,真的。代码从文件中读取、处理并输出到标准输出。除了“作为数组索引的常量”情况之外的所有内容都应该按原样输出。
如果你喜欢我可以将它重写为函数或其他东西。不过,以上应该是一种通用的解决方案。
编辑第2版,支持$myObject->myProp[key]
:
<?php
$source = file_get_contents('/tmp/test.php'); // Change this
$tokens = token_get_all($source);
//print_r($tokens); exit();
$history = [];
foreach ($tokens as $token)
if (is_string($token)) // simple 1-character token
array_push($history, str_replace(["\r", "\n"], '', $token));
echo $token;
else
list($id, $text) = $token;
switch ($id)
case T_STRING:
if (array_slice($history, -2) == [T_VARIABLE, '['])
// Token sequence is [T_VARIABLE, '[', T_STRING]
echo "'$text'";
else if (array_slice($history, -4) == [T_VARIABLE, T_OBJECT_OPERATOR, T_STRING, '['])
echo "'$text'";
else
echo $text;
break;
default:
// anything else -> output "as is"
echo $text;
break;
array_push($history, $id);
// This has to be at least as large as the largest chunk
// checked anywhere above
$history = array_slice($history, -5);
可以看出,引入更多案例的难点在于$history
将不再统一。起初我想直接从$tokens
获取东西,但它们没有经过消毒,所以我坚持使用$history
。底部的“修剪线”可能不需要,它只是用于内存使用。也许跳过$history
,在foreach() 之前清理所有$tokens
项目,然后直接从中获取内容(当然,将索引添加到foreach())会更干净。我想我觉得第 3 版即将推出;-j..
编辑版本 3: 这应该尽可能简单。只需查找其中包含未引用字符串的括号。
$source = file_get_contents('/tmp/test.php'); // Change this
$tokens = token_get_all($source);
$history = [];
foreach ($tokens as $token)
if (is_string($token)) // simple 1-character token
array_push($history, str_replace(["\r", "\n"], '', $token));
echo $token;
else
list($id, $text) = $token;
switch ($id)
case T_STRING:
if (array_slice($history, -1) == ['['])
echo "'$text'";
else
echo $text;
break;
default:
// anything else -> output "as is"
echo $text;
break;
array_push($history, $id);
测试输入(/tmp/test.php):
<?php
$variable[key] = "WhatElse";
$result = $wso->RSLA("7050", $vegalot, "600", "WFID_OK_WEB","1300", $_POST[username]);
if ($result[ECD] != 0)
if ($line=="AAAA" && in_array(substr($wso->lot,0,7),$lot_aaaa_list) && $lot[wafer][25])
$object->method[key];
$variable[test] = 'one';
$variable[one][two] = 'three';
$variable->property[three]['four'] = 5;
【讨论】:
很好的解决方案,但如果 'test.php' 包含 '$object->method[key]' 则不起作用,因为 "key" 不会自动用引号括起来。 你说得对,不支持对象/类属性。我做了一个版本2,添加了。我感兴趣的(嗯,很好奇)这里的代码如果增长会是什么样子,可维护性如何受到影响。我认为这里与正则表达式相比的优势在于可读性。但也许事实并非如此,我不确定.. 哦,见鬼.. 添加了第 3 版。考虑您所写的案例很有趣。版本 3 只是查找其中包含未引用字符串的括号。我不确定该标准是否过于简单。【参考方案3】:我用双循环解决了这个问题:
<?
$script = file("/tmp/test.php");
/*
* Search any row containing a php variable $(...)
*/
$scanstr = preg_grep("/\\$([\w\-\>]+)(\[.*\])/", $script);
foreach($scanstr as $k => $str)
unset($matchvar, $match);
/* Get php variable */
preg_match_all('/\$(\w|-|>|\[|\]|\'|")+/', $str, $matchvar, PREG_SET_ORDER, 0);
/* Get array key name */
preg_match_all('/(\[(\w+)\])/', $matchvar[0][0], $match, PREG_SET_ORDER, 0);
$replace_str = $str;
foreach($match as $m)
/*
* if key is not defined or a number, then skip conversion
*/
if (!$m[2]) continue;
if (is_numeric($m[2])) continue;
$r = str_replace($m[1], "['" . $m[2] . "']", $matchvar[0][0]);
$replace_str = str_replace($matchvar[0][0], $r, $replace_str);
$matchvar[0][0] = $r;
$local_changeflag = true;
?>
它适用于以下任何一种情况:
$variable[test] = 'one';
$variable[one][two] = 'three';
$variable->property[three]['four'] = 5;
我知道,它不是很干净;)
【讨论】:
preg_
调用次数过多。请参阅 Wiktor 的方法。以上是关于php 'preg_match_all' 和 'str_replace':用数组键替换常量的正则表达式的主要内容,如果未能解决你的问题,请参考以下文章