PHP嵌套短代码转数组函数异常:多子元素解析错误排查
短代码转嵌套数组的解析问题排查与修复
问题现象
正常情况
解析单层嵌套短代码时,函数可生成正确的嵌套数组:
输入短代码:
[column] [row] [column][/column] [/row] [/column]
生成的正确数组结构:
Array ( [0] => Array ( [tag] => column [attributes] => Array ( ) [content] => Array ( [0] => Array ( [tag] => row [attributes] => Array ( ) [content] => Array ( [0] => Array ( [tag] => column [attributes] => Array ( ) [content] => ) ) ) ) ) )
错误情况
当子元素包含多个同标签短代码时,第二个标签会被错误解析为根级别元素:
输入短代码:
[column] [row] [column][/column] [column][/column] [/row] [/column]
生成的错误数组结构:
Array ( [0] => Array ( [tag] => column [attributes] => Array ( ) [content] => Array ( [0] => Array ( [tag] => row [attributes] => Array ( ) [content] => Array ( [0] => Array ( [tag] => column [attributes] => Array ( ) [content] => ) ) ) ) ) [1] => Array ( [tag] => column [attributes] => Array ( ) [content] => ) )
问题代码
protected function shortCodeToArray($inputString) { $itemArray = []; $openingTag = '/\[(\w+)(?:\s+([^\]]*))?\](.*?)(\[\/\1\]|$)/s'; preg_match_all($openingTag, $inputString, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $tagName = $match[1]; $paramString = isset($match[2]) ? $match[2] : ''; $content = $match[3]; $nestedShortcodes = $this->shortCodeToArray($content); $itemArray[] = [ 'tag' => $tagName, 'attributes' => $this->parseShortcodeParameters($paramString), 'content' => is_array($nestedShortcodes) && !empty($nestedShortcodes) ? $nestedShortcodes : $content, ]; } return $itemArray; } protected function parseShortcodeParameters($paramString) { $params = []; preg_match_all('/(\w+)\s*=\s*["\']([^"\']+)["\']/', $paramString, $matches); for ($i = 0; $i < count($matches[0]); $i++) { $paramName = $matches[1][$i]; $paramValue = $matches[2][$i]; $params[$paramName] = $paramValue; } return $params; }
问题根源
核心问题在于正则表达式/\[(\w+)(?:\s+([^\]]*))?\](.*?)(\[\/\1\]|$)/s的非贪婪匹配逻辑:
.*?会在遇到第一个匹配的闭合标签[/tag]时立即停止匹配,无法识别嵌套层级。- 以错误场景为例,最外层的
[column]会被匹配到第一个子[/column]就结束,剩下的第二个[column][/column]和外层的[/row][/column]被截断,导致第二个[column]被当成独立的根级别标签匹配。
修复方案
修改正则表达式为支持嵌套标签的递归匹配模式,确保能正确匹配到对应层级的闭合标签:
protected function shortCodeToArray($inputString) { $itemArray = []; // 支持嵌套标签的递归正则 $openingTag = '/\[(\w+)(?:\s+([^\]]*))?\]((?:[^[]+|\[(?!\/\1\])|(?R))*)\[\/\1\]/s'; preg_match_all($openingTag, $inputString, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $tagName = $match[1]; $paramString = isset($match[2]) ? $match[2] : ''; $content = trim($match[3]); // 去除首尾空白,避免空内容干扰 $nestedShortcodes = $this->shortCodeToArray($content); $itemArray[] = [ 'tag' => $tagName, 'attributes' => $this->parseShortcodeParameters($paramString), 'content' => !empty($nestedShortcodes) ? $nestedShortcodes : $content, ]; } return $itemArray; } // parseShortcodeParameters 函数保持不变 protected function parseShortcodeParameters($paramString) { $params = []; preg_match_all('/(\w+)\s*=\s*["\']([^"\']+)["\']/', $paramString, $matches); for ($i = 0; $i < count($matches[0]); $i++) { $paramName = $matches[1][$i]; $paramValue = $matches[2][$i]; $params[$paramName] = $paramValue; } return $params; }
正则说明
新正则/\[(\w+)(?:\s+([^\]]*))?\]((?:[^[]+|\[(?!\/\1\])|(?R))*)\[\/\1\]/s的关键部分:
(?:[^[]+|\[(?!\/\1\])|(?R))*:匹配标签内容,包含三种情况:[^[]+:匹配非[开头的文本\[(?!\/\1\]):匹配不是当前标签闭合标签的[开头内容(?R):递归匹配整个正则,处理嵌套的短代码
- 确保只会匹配到与当前开标签对应的闭合标签,不会被嵌套的同标签干扰。
修复效果
重新解析错误场景的短代码,会生成正确的嵌套数组:第二个column会被包含在row的content中,根级别只有一个column元素。
内容的提问来源于stack exchange,提问作者Ibrahim Azhar Armar
相关产品推荐
相关产品推荐

