使用递归和正则表达式替换字符串中的文本

Replacing text in a string with recursion and regex

我在浏览器中显示输出之前使用标签替换文本,类似于 Wordpress 的短代码。

示例字符串:Hi, this is a block of text {{block:welcome}} and this is a system variable {{variable:system_version}}

我有相应的函数来替换这些块,我意识到 foreach 或 while 函数将是处理它的最佳方法,但不幸的是,替换一个 {{...}} 可能会引入另一个。因此,我选择递归,直到找不到更多为止。典型的递归只有一次,但我在一个场景中有两个。也许调用该函数 3 次会起作用,但听起来 "wrong"。

这就是问题所在:当它们出现在以下位置时我不想替换它们:

1) A page where the URL you are calling contains something
2) Any form element such as `<input>` or `<textarea>`.

我需要有关如何通过正则表达式从上面的 #2 中排除的帮助。

我的正则表达式目前看起来像这样:^\{\{((?!keep).)*$(我意识到它可能仍然是错误的,或者需要修改 - 还不能正常工作)。

如果该项目包含 "keep",例如 {{block:welcome:keep}},则不应替换它,但这样做时,递归永远不会停止,因为我一直在寻找要替换的项目,因此 运行 内存不足,或出现最大嵌套级别错误。

我之所以要这样做,是因为我不想在管理页面上或编辑表单内容时替换内容。

有人愿意尝试一下吗?如果重要的话,我正在使用 PHP。

谢谢!

编辑 1

由于@Pablo 的回答是在聊天中给我的,我决定编辑我的问题以反映为什么他的回答被标记为正确的。

我的正则表达式现在看起来像这样:/(?:<(?:textarea|select)[\s\S]*?>[\s\S]*?)?({{variable:(.*?)}})[\s\S]*?(?:<\/(?:textarea|select)>)?|(?:<(?:input)[\s\S]*?)?{{variable:(.*?)}}(?:[\s\S]*?>)?/im

然后我检查匹配项是否包含输入 select 或文本区域,如果是,则暂时将 {{ 替换为其他内容,然后进行替换,完成后更改按照 Pablo 的建议,"something else" 回到 {{。我的正则表达式归功于这个问题的答案:.

如果以上编辑不属于,请随时删除。

与其寻找完美的正则表达式,我建议您考虑使用 preg_replace_callback()。它应该允许您使用更简单的 RegEx,同时更好地控制模板引擎的搜索和替换算法。考虑以下示例:

  1. resolvePlaceholder()生成替换内容
  2. interpolate() 解析模板字符串。它支持最多4层的嵌套解析。
  3. 停止对以 !.
  4. 开头的标签进行递归解析

<?php

function resolvePlaceholder($name)
{
    $store = [
        'user:first'              => 'John',
        'user:last'               => 'Doe',
        'user:full_name'          => '{{user:first}} {{user:last}}',
        'block:welcome'           => 'Welcome {{user:full_name}}',
        'variable:system_version' => '2019.1',
        'nest-test'               => '{{level1}}',
        'level1'                  => '{{level2}}',
        'level2'                  => '{{level3}}',
        'level3'                  => '{{level4}}',
        'level4'                  => '{{level5}}',
        'level5'                  => 'Nesting Limit Test Failed',
        'user-template'           => 'This is a user template with {{weird-placeholder}} that will not be replaced in edit mode {{user:first}}',
    ];

    return $store[$name] ?? '';
}

function interpolate($text, $level = 1)
{
    // Limit interpolation recursion
    if ($level > 5) {
        return $text;
    }

    // Replace placeholders
    return preg_replace_callback('/{{([^}]*)}}/', function ($match) use ($level) {
        list($tag, $name) = $match;
        // Do not replace tags with :keep
        if (strpos($name, ':keep')) {
            // Remove :keep?
            return $tag;
        }

        if (strpos($name, '!') === 0) {
            return resolvePlaceholder(trim($name, '!'));
        }

        return interpolate(resolvePlaceholder($name), $level + 1);
    }, $text);
}

$sample = 'Hi, this is a block of text {{block:welcome}} and this is a system variable {{variable:system_version}}. ' .
    'This is a placeholder {{variable:web_url:keep}}. Nest value test {{nest-test}}. User Template: {{!user-template}}';

echo interpolate($sample);
// Hi, this is a block of text Welcome John Doe and this is a system variable 2019.1. This is a placeholder {{variable:web_url:keep}}. Nest value test {{level5}}. User Template: This is a user template with {{weird-placeholder}} that will not be replaced in edit mode {{user:first}}