【问题标题】: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] 中的晶圆不应该也被引用吗?

标签: php regex string replace preg-match-all


【解决方案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 的方法。
    猜你喜欢
    • 2011-05-13
    • 2021-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多