PHP PREG_REPLACE根据检查顺序返回错误结果

2022-03-22 00:00:00 regex php preg-replace

我在PHP的preg_place函数和一些正则表达式模式中偶然发现了一个非常奇怪的错误。我要做的是替换由括号分隔的自定义标记,并将它们转换为HTML。正则表达式必须考虑自定义的"填充"标记,这些标记将保留在输出的HTML语言中,以便在页面加载时可以在飞翔上替换(例如,替换为站点名称)。

每个正则表达式模式都会自行工作,但是由于某些原因,如果先检查其他模式之一,则其中一些模式会提前退出函数。当我偶然发现这一点时,我使用了preg_match和foreach循环来检查模式,然后再继续,如果找到就返回结果-所以假设它对每个模式都是新的。

这也不起作用。

校验码:

function replaceLTags($originalString){
    $patterns = array(
                '#^[l]([^s]+)[/l]$#i' => '<a href="$1">$1</a>',
                '#^[l=([^s]+)]([^[]+)[/l]$#i'=> '<a href="$1">$2</a>',
                '#^[l=([^s]+) title=([^[]+)]([^[]+)[/l]$#i' => '<a href="$1" title="$2">$3</a>',
                '#^[l=([^s]+) rel=([^[]+)]([^[]+)[/l]$#i' => '<a href="$1" rel="$2">$3</a>',
                '#^[l=([^s]+) onClick=([^[]+)]([^[]+)[/l]$#i' => '<a href="$1" onClick="$2">$3</a>',
                '#^[l=([^s]+) style=([^[]+)]([^[]+)[/l]$#i' => '<a href="$1" style="$2">$3</a>',
                '#^[l=([^s]+) onClick=([^[]+) style=([^[]+)]([^[]+)[/l]$#i' => '<a href="$1" onClick="$2" style="$3">$4</a>',
                '#^[l=([^s]+) class=([^[]+) style=([^[]+)]([^[]+)[/l]$#i' => '<a href="$1" class="$2" style="$3">$4</a>',
                '#^[l=([^s]+) class=([^[]+) rel=([^[]+)] target=([^[]+)]([^[]+)[/l]$#i' => '<a href="$1" class="$2" rel="$3" target="$4">$5</a>'
            );

    foreach ($patterns as $pattern => $replace){
        if (preg_match($pattern, $originalString)){
            return preg_replace($pattern, $replace, $originalString);
        }
    }
}

$string = '[l=[site_url]/site-category/ class=hello rel=nofollow target=_blank]Hello there[/l]';

echo $alteredString = $format->replaceLTags($string);

上述"字符串"将显示为:

<a href="[site_url">/site-category/ class=hello rel=nofollow target=_blank]Hello there</a>

何时应显示为:

<a href="[site_url]/site-category/" class="hello" rel="nofollow" target="_blank">Hello there</a>

但是,如果将该模式在列表中进一步向上移动以更快地进行检查,它的格式将正确。

我被难住了,因为每次检查字符串时似乎都会以某种方式覆盖它,尽管这没有任何意义。


解决方案

在我看来,您做的工作比您需要做的多得多。与其对每个可能的属性列表使用单独的正则表达式/替换,为什么不使用preg_replace_callback在单独的步骤中处理属性呢?例如:

function replaceLTags($originalString){
  return preg_replace_callback('#[l=((?>[^s[]]+|[site_url])+)([^]]*)](.*?)[/l]#',
                               replaceWithinTags, $originalString);
}

function replaceWithinTags($groups){
  return '<a href="' . $groups[1] . '"' . 
         preg_replace('#(s+w+)=(S+)#', '$1="$2"', $groups[2]) .
         '>' . $groups[3] . '</a>';
}

查看完整演示here(已更新;请参阅评论)。

以下是基于注释中提供的新信息的代码的更新版本:

function replaceLTags($originalString){
  return preg_replace_callback('#[l=((?>[^s[]]+|[w+])+)([^]]*)](.*?)[/l]#',
                               replaceWithinTags, $originalString);
}

function replaceWithinTags($groups){
  return '<a href="' . $groups[1] . '"' . 
         preg_replace(
             '#(s+[^s=]+)s*=s*([^s=]+(?>s+[^s=]+)*(?!s*=))#',
             '$1="$2"', $groups[2]) .
         '>' . $groups[3] . '</a>';
}

demo

在第一个正则表达式中,我将[site_url]更改为[w+],以便它可以匹配任何自定义填充标记。

以下是第二个正则表达式的细目:

(s+[^s=]+)   # the attribute name and its leading whitespace
s*=s*
(
  [^s=]+   # the first word of the attribute value
  (?>s+[^s=]+)*  # the second and subsequent words, if any
  (?!s*=)  # prevents the group above from consuming tag names
)

最棘手的部分是匹配多词属性值。(?>s+[^s=]+)*将始终使用下一个标记名(如果有),但前视会强制它回溯。正常情况下,它一次只能后退一个字符,但原子组有效地迫使它通过整个单词或根本不后退。

相关文章