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-&gt;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':用数组键替换常量的正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

preg_match_all (PHP) 中的 UTF-8 字符

php数组

php 获得linux 机器的性能

php 正则获取字符串中的汉字preg_match_all

在PHP中从preg_match_all中返回的值

PHP - preg_match_all 没有搜索完整的字符串?