是否可以知道主题字符串中匹配的位置

时间:2015-02-19 10:21:50

标签: php regex

我有一个文件名,其中必须替换信息。这是一个主题样本:

FileA-2014-11-01_K_1_A2_383.xxx

要处理多个文件,首先将此文件名与正则表达式匹配,例如:

/[a-zA-Z]*-\d{4}-\d{2}-\d{2}_(\w)_(\d)_A2_(\d*)\.xxx$/

这个正则表达式将使用preg_match为我提供要替换的值,这里:

  • K => A
  • 1 => 2
  • 383 => 666

我的第一次尝试是天真地使用" str_replace",但是当字符串中重复模式时它会失败:这里我会得到:

FileA-2024-22-02_A_2_A2_666.xxx

因此str_replace也修改了日期(因为它被告知要做..)

所以,我想知道是否有办法知道哪里是字符串中的给定匹配以获得干净的替换。 我现在试图恢复正则表达式以捕获非替换块,然后插入替换数据。那个正则表达式将是:

/([a-zA-Z]*-\d{4}-\d{2}-\d{2}_)\w(_)\d(_A2_)\d*(\.xxx)$/

有了这个,我可以保留未更换的部件。我现在必须找到一种索引来知道字符串中的替换位置。我想我可以实现这种方式,但似乎有点复杂且容易出错。 鉴于我只有初始正则表达式和替换的地图for =>,有没有办法以更好的方式做到这一点?

[编辑:解决方案]

<?php

$filename = "FileA-2014-11-01_K_1_A2_383.xxx";
$expected = "FileA-2014-11-01_A_2_A2_666.xxx";

$regex = "/[a-zA-Z]*-\d{4}-\d{2}-\d{2}_(\w)_(\d)_A2_(\d*)\.xxx$/";


global $replacements;

$replacements["K"] = "A";
$replacements["1"] = "2";
$replacements["383"] = "666";


$result = preg_replace_callback($regex, function($matches){
    global $replacements;
    print_r($matches);
    // ended here. no way.
}, $filename);


if(strcmp($result,$expected)==0)
    echo "preg_replace_callback() : Yep\n";
else
    echo "preg_replace_callback() : Nop\n";


preg_match($regex, $filename, $matches, PREG_OFFSET_CAPTURE);

// remove useless global string match
array_shift($matches);

$result = $filename;
foreach($matches as $matchInfo){

    $match    = $matchInfo[0];
    $position = $matchInfo[1];

    $matchLength= strlen($match);

    $beforeReplacementPart = substr($result, 0, $position);
    $afterReplacementPart = substr($result, ($position + $matchLength));
    $result = $beforeReplacementPart . $replacements[$match] . $afterReplacementPart;

}


if(strcmp($result,$expected)==0)
    echo "preg_match() and substr game : Yep\n";
else
    echo "preg_match() and substr game : Nop\n";

5 个答案:

答案 0 :(得分:1)

至少preg_match_all()提供选项

PREG_OFFSET_CAPTURE
    如果传递此标志,则对于每个发生的匹配,还将返回附加字符串偏移量。请注意,这会将匹配的值更改为一个数组,其中每个元素都是一个数组,该数组由偏移0处的匹配字符串和偏移量为1的主题的字符串偏移量组成。

答案 1 :(得分:1)

与该文件名匹配的regex

$re  = '/[a-zA-Z]*-\d{4}-\d{2}-\d{2}_(\w)_(\d)_A2_(\d*)\.xxx$/';
$str = 'FileA-2014-11-01_K_1_A2_383.xxx';

如果将PREG_OFFSET_CAPTURE作为第四个参数($flags)添加到preg_match()的调用中,它还将返回第三个参数中每个捕获的字符串的偏移量:

preg_match($re, $str, $matches, PREG_OFFSET_CAPTURE);

print_r($matches)会显示:

Array
(
    [0] => Array
        (
            [0] => FileA-2014-11-01_K_1_A2_383.xxx
            [1] => 0
        )
    [1] => Array
        (
            [0] => K
            [1] => 17
        )
    [2] => Array
        (
            [0] => 1
            [1] => 19
        )
    [3] => Array
        (
            [0] => 383
            [1] => 24
        )
)

$matches[0]是匹配整个regex的部分。 $matches[1]是第一个捕获子表达式,$matches[2]是第二个,依此类推。

$matches[1][0]是输入字符串中与第一个regex子表达式(\w)匹配的片段,$matches[1][1]是输入字符串中找到它的偏移量。对于$matches[N][0] th 子表达式,$matches[N][1]N也是如此。

如果你需要做一个简单的替换,那么你不需要担心偏移,但使用preg_replace(),或者,如果替换表达式是复杂或动态的,preg_replace_callback()

使用preg_replace(),您需要捕获要保留的部分:

$re  = '/([a-zA-Z]*-\d{4}-\d{2}-\d{2}_)\w_\d_A2_\d*(\.xxx)$/';
$str = 'FileA-2014-11-01_K_1_A2_383.xxx';

$new = preg_replace($re, '$1A_2_A2_666$2', $str);
echo($new."\n");

在替换字符串中,$1$2表示regex中的子表达式。我们将它们标记为捕获,以便在替换字符串中重复使用它们。

答案 2 :(得分:0)

也许可以在你的情况下使用它:

$str = strtr($str, array('_K_1_'=>'_A_2_', '_383.'=>'_666.'));

$str = str_replace('_K_1_A2_383.xxx', '_A_2_A2_666.xxx', $str);

因此不存在歧义,而且替换速度很快。

答案 3 :(得分:0)

您可以尝试下面的正则表达式。

([a-zA-Z]*-\d{4}-\d{2}-\d{2}(?:-\d*)?_)\w_\d(_A2)_\d*(\.xxx)$

然后用

替换匹配
\1A_2\2_666\3

DEMO

$re = "~([a-zA-Z]*-\\d{4}-\\d{2}-\\d{2}(?:-\\d*)?_)\\w_\\d(_A2)_\\d*(\\.xxx)$~m";
$str = "FileA-2014-11-01_K_1_A2_383.xxx";
$subst = "\1A_2\2_666\3";
$result = preg_replace($re, $subst, $str);

答案 4 :(得分:0)

您可以使用:

$re = "/([a-zA-Z]+-\\d{4}-\\d{2}-\\d{2}_)\\w+_\\d+(_A2_)\\d+(\\.xxx)$/m"; 
$str = "FileA-2014-11-01_K_1_A2_383.xxx"; 
$subst = "${1}A_2${2}666${3}"; 

$result = preg_replace($re, $subst, $str);
//=> FileA-2014-11-01_A_2_A2_666.xxx

RegEx Demo