使用递归和正则表达式替换字符串中的文本
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,同时更好地控制模板引擎的搜索和替换算法。考虑以下示例:
resolvePlaceholder()
生成替换内容
interpolate()
解析模板字符串。它支持最多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}}
我在浏览器中显示输出之前使用标签替换文本,类似于 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,同时更好地控制模板引擎的搜索和替换算法。考虑以下示例:
resolvePlaceholder()
生成替换内容interpolate()
解析模板字符串。它支持最多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}}