PHP:preg_replace(x)发生?

时间:2011-04-01 22:42:32

标签: php regex

我最近问了一个类似的问题,但没有得到一个明确的答案,因为我太具体了。这个更广泛。

有谁知道如何用正则表达式模式替换(x)事件?

示例:假设我想替换字符串中第5次出现的正则表达式。我该怎么做?

这是模式: preg_replace('/{(.*?)\|\:(.*?)}/', 'replacement', $this->source);

@anubhava请求的示例代码(最后一个函数不起作用):


$sample = 'blah asada asdas  {load|:title} steve jobs {load|:css} windows apple ';


$syntax = new syntax();
$syntax->parse($sample);


class syntax {

    protected $source;
    protected $i;
    protected $r;

        // parse source
    public function parse($source) {
                // set source to protected class var
        $this->source = $source;

        // match all occurrences for regex and run loop
        $output = array();
        preg_match_all('/\{(.*?)\|\:(.*?)\}/', $this->source, $output);

                // run loop
        $i = 0;
        foreach($output[0] as $key):
            // perform run function for each occurrence, send first match before |: and second match after |:
            $this->run($output[1][$i], $output[2][$i], $i);

            $i++;
        endforeach;

        echo $this->source;

    }

        // run function
    public function run($m, $p, $i) {
                // if method is load perform actions and run inject
        switch($m):

            case 'load':
                $this->inject($i, 'content');
            break;

        endswitch;

    }

        // this function should inject the modified data, but I'm still working on this.
    private function inject($i, $r) {

          $output = preg_replace('/\{(.*?)\|\:(.*?)\}/', $r, $this->source);

    }


}


6 个答案:

答案 0 :(得分:8)

你误解了正则表达式:它们是无状态的,没有记忆,没有能力计数,没有,所以你不能知道匹配是字符串中的第x个匹配 - 正则表达式引擎没有我有一个线索。出于同样的原因你不能用正则表达式来做这种事情,因为不可能写一个正则表达式来查看字符串是否有平衡括号:问题需要一个内存,根据定义,正则表达式没有。< / p>

但是,正则表达式引擎可以告诉您所有匹配项,因此最好使用preg_match()来获取匹配项列表,然后自己使用该信息修改字符串。

更新:这更接近您的想法吗?

<?php
class Parser {

    private $i;

    public function parse($source) {
        $this->i = 0;
        return preg_replace_callback('/\{(.*?)\|\:(.*?)\}/', array($this, 'on_match'), $source);
    }

    private function on_match($m) {
        $this->i++;

        // Do what you processing you need on the match.
        print_r(array('m' => $m, 'i' => $this->i));

        // Return what you want the replacement to be.
        return $m[0] . '=>' . $this->i;
    }
}

$sample = 'blah asada asdas  {load|:title} steve jobs {load|:css} windows apple ';
$parse = new Parser();
$result = $parse->parse($sample);
echo "Result is: [$result]\n";

这给了......

Array
(
    [m] => Array
        (
            [0] => {load|:title}
            [1] => load
            [2] => title
        )

    [i] => 1
)
Array
(
    [m] => Array
        (
            [0] => {load|:css}
            [1] => load
            [2] => css
        )

    [i] => 2
)
Result is: [blah asada asdas  {load|:title}=>1 steve jobs {load|:css}=>2 windows apple ]

答案 1 :(得分:4)

一个更简单,更清晰的解决方案,它也处理反向引用:

function preg_replace_nth($pattern, $replacement, $subject, $nth=1) {
    return preg_replace_callback($pattern,
        function($found) use (&$pattern, &$replacement, &$nth) {
                $nth--;
                if ($nth==0) return preg_replace($pattern, $replacement, reset($found) );
                return reset($found);
        }, $subject,$nth  );
}


echo preg_replace_nth("/(\w+)\|/", '${1} is the 4th|', "|aa|b|cc|dd|e|ff|gg|kkk|", 4);   

输出| aa | b | cc | dd是第4个| e | ff | gg | kkk |

答案 2 :(得分:1)

没有文字方法来匹配模式/pat/的出现5。但您可以匹配/^(.*?(?:pat.*?){4,4})pat/并替换为\1repl。这将替换前4次出现,加上后面的任何内容,使用相同的内容,第五次使用repl。

如果/pat/包含捕获组,则需要对前N-1个匹配使用非捕获等效项。替换模式应从\\2开始引用捕获的组。

实现如下:

function replace_occurrence($pat_cap,$pat_noncap,$repl,$sample,$n)
{
    $nmin = $n-1;
    return preg_replace("/^(.*?(?:$pat_noncap.*?){".
                        "$nmin,$nmin".
                        "})$pat_cap/",$r="\\1$repl",$sample);
}

答案 3 :(得分:1)

这是替代方法:

$parts = preg_split('/\{((?:.*?)\|\:(?:.*?))\}/', $this->source, PREG_SPLIT_DELIM_CAPTURE);

$ parts将包含偶数偏移的原始字符串部分[0] [2] [4] [6] [8] [10] ......

匹配的分隔符将在[1] [3] [5] [7] [9]

例如,要查找第5次出现,您可以修改元素$n*2 - 1,在这种情况下将是元素[9]:

$parts[5*2 - 1] = $replacement.

然后重新组装一切:

$output = implode($parts);

答案 4 :(得分:1)

正如已经说过的,正则表达式没有状态,你不能通过传递一个整数来确定替换的完全匹配...你可以将替换包装到一个找到所有匹配并仅替换的方法中以整数

给出的第n个匹配
<? 

function replace_nth_occurence ( &$haystack, $pattern, $replacement, $occurence) {

    preg_match_all($pattern, $haystack, $matches, PREG_OFFSET_CAPTURE);
    if(array_key_exists($occurence-1, $matches[0])) {
        $haystack = substr($haystack, 0, $matches[0][$occurence-1][1]).
                      $replacement.
                    substr($haystack, 
                        $matches[0][$occurence-1][1] +
                        strlen($matches[0][$occurence-1][0])
                      );
    }

}


$haystack = "test0|:test1|test2|:test3|:test4|test5|test6"; 

printf("%s \n", $haystack);

replace_nth_occurence( $haystack, '/\|:/', "<=>", 2);

printf("%s \n", $haystack);

?>

答案 5 :(得分:0)

我的第一个想法是使用preg_replace进行回调并在回调中进行计数,正如其他用户所做的那样(非常好)。

或者,您可以使用preg_split使用PREG_SPLIT_DELIM_CAPTURE来保留分隔符,并在结果数组中进行实际替换。 PHP只捕获捕获parens之间的内容,因此您要么必须自己调整正则表达式,要么自己处理其他捕获。假设有1个捕获对,那么捕获的分隔符将始终位于奇数编号的索引中:1,3,5,7,9,....你需要索引9;并再次implode

这意味着您需要进行一次捕获

$sample = "blah asada asdas  {load|:title} steve jobs {load|:css} windows apple\n";
$sample .= $sample . $sample;   # at least 5 occurrences

$parts = preg_split('/(\{.*?\|\:.*?\})/', $sample, -1, PREG_SPLIT_DELIM_CAPTURE);
$parts[9] = 'replacement';
$return = implode('', $parts);