2010-07-13 4 views
0

주어진 문자열 내부에서 미리 정의 된 변수를 재귀 적으로 바꾸는 코드를 작성하고 있습니다. 변수 앞에 '%'문자가 붙습니다. '^'로 시작하는 입력 문자열이 평가됩니다. 같은 변수의 배열을 가정 예를 들어PHP 재귀 적 변수 대체

:

$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: *****' 

어떤 제안? 나는 이것을하기 위해 많은 시간을 보냈지 만 부분적인 성공 만이 달성했습니다. 불행하게도, 나의 마지막 시도는 'segmentation fault'를 생성했다 !!

도움을 많이 주시면 감사하겠습니다.

+0

쉬운 질문이 아닙니다. 예를 들어 순환 참조를 생각해 보셨습니까? '$ var [ 'a'] = '% b'; $ var [ 'b'] = '% a';'. 또는 이것이 일어나지 않을 것이라고 추측 될 수도 있습니다. – NikiC

+0

순환 참조를 감지 할 수 있다면 좋겠지 만, 그 해결책을 아는 사람이 있습니까? 우리가 그 문제에 대한 해결책을 찾지 못한다면, 우리는 그들이 일어나지 않을 것이라고 가정 할 것입니다 ... –

답변

1

참고. (예 : $vars['s'] = '%s'; ..) 따라서 데이터에 이러한 구문이 없는지 확인하십시오. 주석 처리 된 코드

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

은 사용하거나 건너 뛸 수 있습니다. 건너 뛴 경우 문자열 $str이 나중에 평가 될 경우 대체가 인용됩니다! 하지만 PHP는 자동으로 문자열을 숫자로 변환하기 때문에 (또는 그렇게해야한다고 말합니까?) 코드를 건너 뛰어도 문제가 발생하지 않아야합니다. 부울 값은 지원되지 않습니다. 그것은 폐쇄를 사용하기 때문에 PHP 5.3

<? 
    $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'] = ''; 
    $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); 
    ?> 

위의 코드는 가정 (또한 '진실'또는 적합한 논리 값을 'false'로!와 같은 문자열을 변환 PHP 수행에는 자동 변환이 없음).

출력 : PHP < 5.3 다음과 같은 적응 코드

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

사용할 수 있습니다 :

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]; 
     } 
    } 
} 
+0

감사합니다, Javaguru! 귀하의 답변은 훌륭해 보입니다! 그러나, 문자열 안에 여분의 따옴표를 추가하는 것을 피하는 방법이 있습니까 ('\'이것은 문자열 \ ''입니까?)? –

+0

예. 나는 이것이 가능해야한다고 생각한다. 그러나 확장 된 문자열이 eval-expression (-> 따옴표가 필요함) 내에서 사용되는지 아니면 그냥 바뀌어야 하는지를 알아야합니다. 다른 단어들 : preg_replace_callback에 대한 콜백은 $ str이 ^로 시작되고 $ vars [$ matches [1]]이 숫자가 아닌 경우 (-> is_numeric ($ matches [$ matches [1]]) 반환 된 값에 따옴표를 추가해야합니다.)). – Javaguru

+0

흠, 코드에 나와있는 것을 보여줄 수 있습니까? 그게 내가 원래 구현을 망쳤다는 부분이다. :(또한, 문자열에 다른 문자열/결과가 포함 된 변수가 포함 된 상황에서도 작동 할 것이라고 생각하십니까? 고마워요! –

0

실제로 MVC 프레임 워크를 구현하는 동안이 작업을 수행했습니다.

내가 한 것은 정규식을 사용하여 preg_match_all을 사용하여 바꿔야 할 모든 것을 찾은 다음 목록을 반복하고 str_replaced 코드를 사용하여 재귀 적으로 함수를 호출하는 "찾기 태그"함수를 만드는 것입니다.

VERY 간체 코드 단순히 무한 루프로 이어질 것이다 원형 대해 더 포함 체크 없다고

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); 
} 
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에 도달했습니다. (하지만 어딘가에서이 설정을 수정할 수 있습니다.)