PHP递归变量替换

时间:2010-07-13 20:10:37

标签: php regex variables

我正在编写代码以递归方式替换给定字符串中的预定义变量。变量以字符'%'为前缀。以“^”开头的输入字符串将被评估。

例如,假设一个变量数组,例如:

$vars['a'] = 'This is a string';  
$vars['b'] = '123';  
$vars['d'] = '%c';  // Note that $vars['c'] has not been defined
$vars['e'] = '^5 + %d';  
$vars['f'] = '^11 + %e + %b*2';  
$vars['g'] = '^date(\'l\')';  
$vars['h'] = 'Today is %g.';  
$vars['input_digits'] = '*****';  
$vars['code'] = '%input_digits';  

以下代码将导致:

a) $str = '^1 + %c';  
   $rc = _expand_variables($str, $vars);  
  // Result: $rc == 1 

b) $str = '^%a != NULL';  
   $rc = _expand_variables($str, $vars);  
   // Result: $rc == 1  

c) $str = '^3+%f + 3';  
   $rc = _expand_variables($str, $vars);  
   // Result: $rc == 262  

d) $str = '%h';  
   $rc = _expand_variables($str, $vars);  
   // Result: $rc == 'Today is Monday'  

e) $str = 'Your code is: %code';  
   $rc = _expand_variables($str, $vars);  
   // Result:  $rc == 'Your code is: *****'  

有关如何做到这一点的任何建议?我花了很多天试图做到这一点,但只取得了部分成功。不幸的是,我的最后一次尝试设法产生了“分段错误”!!

非常感谢帮助!

3 个答案:

答案 0 :(得分:1)

请注意,没有检查圆形包含,这只会导致无限循环。 (示例:$vars['s'] = '%s'; ..)因此,请确保您的数据不含此类构造。 评论代码

    // if(!is_numeric($expanded) || (substr($expanded.'',0,1)==='0'
    //            && strpos($expanded.'', '.')===false)) {
..
    // }

可以使用或跳过。如果跳过,则引用任何替换,如果稍后将对字符串$str进行评估!但是,由于PHP自动将字符串转换为数字(或者我应该说它试图这样做?)跳过代码不应该导致任何问题。 请注意,不支持布尔值! (此外,PHP没有自动转换,将'true'或'false'等字符串转换为适当的布尔值!)

    <?
    $vars['a'] = 'This is a string';
    $vars['b'] = '123';
    $vars['d'] = '%c';
    $vars['e'] = '^5 + %d';
    $vars['f'] = '^11 + %e + %b*2';
    $vars['g'] = '^date(\'l\')';
    $vars['h'] = 'Today is %g.';
    $vars['i'] = 'Zip: %j';
    $vars['j'] = '01234';
    $vars['input_digits'] = '*****';
    $vars['code'] = '%input_digits';

    function expand($str, $vars) {
        $regex = '/\%(\w+)/';
        $eval = substr($str, 0, 1) == '^';
        $res = preg_replace_callback($regex, function($matches) use ($eval, $vars) {
            if(isset($vars[$matches[1]])) {
                $expanded = expand($vars[$matches[1]], $vars);
                if($eval) {
                    // Special handling since $str is going to be evaluated ..
//                    if(!is_numeric($expanded) || (substr($expanded.'',0,1)==='0'
//                            && strpos($expanded.'', '.')===false)) {
                        $expanded = "'$expanded'";
//                    }
                }
                return $expanded;
            } else {
                // Variable does not exist in $vars array
                if($eval) {
                    return 'null';
                }
                return $matches[0];
            }
        }, $str);
        if($eval) {
            ob_start();
            $expr = substr($res, 1);
            if(eval('$res = ' . $expr . ';')===false) {
                ob_end_clean();
                die('Not a correct PHP-Expression: '.$expr);
            }
            ob_end_clean();
        }
        return $res;
    }

    echo expand('^1 + %c',$vars);
    echo '<br/>';
    echo expand('^%a != NULL',$vars);
    echo '<br/>';
    echo expand('^3+%f + 3',$vars);
    echo '<br/>';
    echo expand('%h',$vars);
    echo '<br/>';
    echo expand('Your code is: %code',$vars);
    echo '<br/>';
    echo expand('Some Info: %i',$vars);
    ?>

上面的代码假设PHP 5.3,因为它使用了闭包。

输出:

1
1
268
Today is Tuesday.
Your code is: *****
Some Info: Zip: 01234

对于PHP&lt; 5.3可以使用以下改编代码:

function expand2($str, $vars) {
    $regex = '/\%(\w+)/';
    $eval = substr($str, 0, 1) == '^';
    $res = preg_replace_callback($regex, array(new Helper($vars, $eval),'callback'), $str);
    if($eval) {
        ob_start();
        $expr = substr($res, 1);
        if(eval('$res = ' . $expr . ';')===false) {
            ob_end_clean();
            die('Not a correct PHP-Expression: '.$expr);
        }
        ob_end_clean();
    }
    return $res;
}

class Helper {
    var $vars;
    var $eval;

    function Helper($vars,$eval) {
        $this->vars = $vars;
        $this->eval = $eval;
    }

    function callback($matches) {
        if(isset($this->vars[$matches[1]])) {
            $expanded = expand($this->vars[$matches[1]], $this->vars);
            if($this->eval) {
                // Special handling since $str is going to be evaluated ..
                if(!is_numeric($expanded) || (substr($expanded . '', 0, 1)==='0'
                        && strpos($expanded . '', '.')===false)) {
                    $expanded = "'$expanded'";
                }
            }
            return $expanded;
        } else {
            // Variable does not exist in $vars array
            if($this->eval) {
                return 'null';
            }
            return $matches[0];
        }
    }
}

答案 1 :(得分:1)

我现在为您的代码编写了一个求值程序,它也解决了循环引用问题。

使用:

$expression = new Evaluator($vars);

$vars['a'] = 'This is a string';  
// ...

$vars['circular'] = '%ralucric';
$vars['ralucric'] = '%circular';

echo $expression->evaluate('%circular');

我使用$this->stack来处理循环引用。 (不知道堆栈实际上是什么,我只是将其命名为^^)

class Evaluator {
    private $vars;
    private $stack = array();
    private $inEval = false;

    public function __construct(&$vars) {
        $this->vars =& $vars;
    }

    public function evaluate($str) {
        // empty string
        if (!isset($str[0])) {
            return '';
        }

        if ($str[0] == '^') {
            $this->inEval = true;
            ob_start();
            eval('$str = ' . preg_replace_callback('#%(\w+)#', array($this, '_replace'), substr($str, 1)) . ';');
            if ($error = ob_get_clean()) {
                throw new LogicException('Eval code failed: '.$error);
            }
            $this->inEval = false;
        }
        else {
            $str = preg_replace_callback('#%(\w+)#', array($this, '_replace'), $str);
        }

        return $str;
    }

    private function _replace(&$matches) {
        if (!isset($this->vars[$matches[1]])) {
            return $this->inEval ? 'null' : '';
        }

        if (isset($this->stack[$matches[1]])) {
            throw new LogicException('Circular Reference detected!');
        }
        $this->stack[$matches[1]] = true;
        $return = $this->evaluate($this->vars[$matches[1]]);
        unset($this->stack[$matches[1]]);
        return $this->inEval == false ? $return : '\'' . $return . '\'';
    }
}

编辑1 :我使用以下方法测试了此脚本的最大递归深度:

$alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEF'; // GHIJKLMNOPQRSTUVWXYZ
$length = strlen($alphabet);
$vars['a'] = 'Hallo World!';
for ($i = 1; $i < $length; ++$i) {
    $vars[$alphabet[$i]] = '%' . $alphabet[$i-1];
}
var_dump($vars);
$expression = new Evaluator($vars);
echo $expression->evaluate('%' . $alphabet[$length - 1]);

如果将另一个字符添加到$alphabet,则达到最大递归深度100。 (但是你可以在某个地方修改这个设置吗?)

答案 2 :(得分:0)

我实际上只是在实现MVC框架时这样做了。

我所做的是创建一个“find-tags”函数,它使用正则表达式来查找应该使用preg_match_all替换的所有内容,然后遍历列表并使用str_replaced代码递归调用该函数。

非常简化的代码

function findTags($body)
{
    $tagPattern = '/{%(?P<tag>\w+) *(?P<inputs>.*?)%}/'

    preg_match_all($tagPattern,$body,$results,PREG_SET_ORDER);
    foreach($results as $command)
    {

        $toReturn[] = array(0=>$command[0],'tag'=>$command['tag'],'inputs'=>$command['inputs']);
    }
    if(!isset($toReturn))
        $toReturn = array();
    return $toReturn;
}

function renderToView($body)
{
    $arr = findTags($body);
    if(count($arr) == 0)
        return $body;
    else
    {
        foreach($arr as $tag)
        {
           $body = str_replace($tag[0],$LOOKUPARRY[$tag['tag']],$body);
        }

    }       
    return renderToView($body);
}