好的,我们开始今天的讲座。今天的主题是WordPress短代码解析器do_shortcode
函数的正则匹配与递归解析机制。这是一个非常核心且重要的功能,理解它的工作方式对于开发WordPress主题、插件以及进行深度定制至关重要。
1. 短代码的概念与作用
短代码(Shortcode)是WordPress提供的一种强大的宏机制,允许用户在文章、页面、小工具等内容中嵌入动态内容或复杂功能,而无需编写任何PHP代码。 它的本质是将一段简单的标记(例如[my_shortcode]
)替换为预先定义好的HTML或其他输出。
短代码的设计目标是:
- 易用性: 普通用户也能轻松使用,无需编程知识。
- 灵活性: 允许开发者扩展WordPress的功能,并方便地插入内容。
- 安全性: 限制用户直接执行PHP代码,避免潜在的安全风险。
2. do_shortcode
函数的核心功能
do_shortcode
函数是WordPress短代码解析引擎的核心函数。它的主要职责是:
- 接收字符串: 接收包含短代码标记的字符串作为输入。
- 正则匹配: 使用正则表达式在字符串中查找短代码标记。
- 回调执行: 找到匹配的短代码后,调用与其关联的回调函数。
- 替换: 将短代码标记替换为回调函数的返回值。
- 递归解析: 如果回调函数的返回值中包含其他短代码,则递归调用
do_shortcode
进行解析。 - 返回结果: 返回解析后的字符串。
3. 正则表达式的构建与分析
do_shortcode
函数使用正则表达式来识别短代码标记。 WordPress定义的默认短代码正则表达式相对复杂,旨在处理各种不同的短代码格式,包括自闭合短代码、带属性的短代码以及嵌套短代码。
默认的短代码正则表达式定义在全局变量$shortcode_tags
中,并在do_shortcode
函数中使用。我们可以通过以下方式获得正则表达式的模式:
global $shortcode_tags;
$tagregexp = join( '|', array_map('preg_quote', array_keys($shortcode_tags)) );
$pattern =
'\[' // Opening bracket
. '(\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
. "($tagregexp)" // 2: Shortcode name
. '(?![\w-])' // Word boundary
. '(' // 3: Optional attributes
. '[^\]\/]*' // Not a closing bracket or forward slash
. '(?:'
. '\/(?!\])' // A forward slash not followed by a closing bracket
. '[^\]\/]*' // Not a closing bracket or forward slash
. ')*?'
. ')'
. '(?:'
. '(\/)' // 4: Self closing tag ...
. '\]' // ... and closing bracket
. '|'
. '\]' // Closing bracket
. '(?:'
. '(' // 5: Unclosed content
. '[^\[]*+' // Not an opening bracket
. '(?:'
. '\[(?!\/\2\])' // An opening bracket not followed by the closing shortcode tag
. '[^\[]*+' // Not an opening bracket
. ')*+'
. ')'
. '\[\/\2\]' // Closing shortcode tag
. )?'
. ')'
. '(\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
这个正则表达式可以分解为以下几个部分:
部分 | 含义 |
---|---|
\[ |
匹配左方括号[ 。 |
(\[?) |
匹配可选的第二个左方括号,用于转义短代码,例如[[shortcode]] 。这部分匹配会被捕获到第一个分组。 |
($tagregexp) |
匹配短代码的名称。$tagregexp 是由所有已注册的短代码名称组成的正则表达式,例如(shortcode1|shortcode2|shortcode3) 。这部分匹配会被捕获到第二个分组。 |
(?![\w-]) |
负向先行断言,确保短代码名称后面不是字母、数字或连字符。这用于区分短代码和其他类似的标记。 |
([^\]\/]*(?:\/(?!\])[^\]\/]*)*?) |
匹配短代码的属性。这部分允许属性包含斜杠,除非斜杠后面跟着一个右方括号。 |
(\/)\] |
匹配自闭合短代码的斜杠和右方括号,例如[shortcode /] 。这部分匹配会被捕获到第四个分组。 |
\](?:([^\[]*(?:\[(?!\/\2\])[^\[]*)*)\[/\2\])? |
匹配包含内容的短代码,例如[shortcode]content[/shortcode] 。这部分包含两个子部分:([^\[]*(?:\[(?!\/\2\])[^\[]*)*) 匹配短代码的内容,允许内容中包含左方括号,但必须不是闭合标签的开始部分。\[/\2\] 匹配闭合标签。 这部分匹配会被捕获到第五个分组。 |
(\]?) |
匹配可选的第二个右方括号,用于转义短代码,例如[[shortcode]] 。这部分匹配会被捕获到第六个分组。 |
4. do_shortcode_tag
函数:短代码回调的执行
当do_shortcode
函数找到一个匹配的短代码时,它会调用do_shortcode_tag
函数来执行与该短代码关联的回调函数。
function do_shortcode_tag( $m ) {
global $shortcode_tags;
// Allow [[foo]] syntax for escaping a tag
if ( $m[1] == '[' && $m[6] == ']' ) {
return substr( $m[0], 1, -1 );
}
$tag = $m[2];
$attr = shortcode_parse_atts( $m[3] );
$content = null;
if ( isset( $m[5] ) ) {
$content = $m[5];
} elseif ( isset( $m[4] ) ) {
$content = '';
}
if ( ! isset( $shortcode_tags[ $tag ] ) ) {
return $m[0];
}
return call_user_func( $shortcode_tags[ $tag ], $attr, $content, $tag );
}
do_shortcode_tag
函数接收一个匹配到的短代码数组作为参数($m
)。这个数组包含了正则表达式匹配到的各个分组:
$m[0]
: 完整的短代码标记字符串。$m[1]
: 可选的左方括号,用于转义。$m[2]
: 短代码的名称。$m[3]
: 短代码的属性字符串。$m[4]
: 自闭合标记的斜杠。$m[5]
: 短代码的内容。$m[6]
: 可选的右方括号,用于转义。
do_shortcode_tag
函数执行以下操作:
- 转义处理: 如果短代码被转义(例如
[[shortcode]]
),则移除转义字符并返回。 - 提取属性: 使用
shortcode_parse_atts
函数将属性字符串解析为关联数组。 - 确定内容: 获取短代码的内容。自闭合短代码的内容为空。
- 检查回调: 确认该短代码名称是否注册了回调函数。如果没有,则返回原始短代码标记。
- 执行回调: 使用
call_user_func
函数调用与短代码关联的回调函数,并将属性、内容和短代码名称作为参数传递给回调函数。 - 返回结果: 返回回调函数的返回值。
5. shortcode_parse_atts
函数:解析短代码属性
shortcode_parse_atts
函数负责将短代码的属性字符串解析为关联数组。 这是一个关键函数,因为它允许开发者在短代码中使用各种参数来控制短代码的行为。
function shortcode_parse_atts( $text ) {
$atts = array();
$pattern = '/(w+)s*=s*"([^"]*)"(?:s|$)|(w+)s*=s*'([^']*)'(?:s|$)|(w+)s*=s*([^s'"]+)(?:s|$)|"([^"]*)"(?:s|$)|(S+)(?:s|$)/';
$text = preg_replace( "/[x00-x08x0Bx0Cx0E-x1Fx7F]/", '', $text );
if ( preg_match_all( $pattern, $text, $match, PREG_SET_ORDER ) ) {
foreach ( $match as $m ) {
if ( ! empty( $m[1] ) ) {
$atts[ strtolower( $m[1] ) ] = stripcslashes( $m[2] );
} elseif ( ! empty( $m[3] ) ) {
$atts[ strtolower( $m[3] ) ] = stripcslashes( $m[4] );
} elseif ( ! empty( $m[5] ) ) {
$atts[ strtolower( $m[5] ) ] = stripcslashes( $m[6] );
} elseif ( ! empty( $m[7] ) && isset( $m[7] ) ) {
$atts[] = stripcslashes( $m[7] );
} elseif ( isset( $m[8] ) ) {
$atts[] = stripcslashes( $m[8] );
}
}
foreach ( $atts as &$value ) {
if ( false !== strpos( $value, '<' ) ) {
if ( 1 !== preg_match( '/[a-zA-Z0-9._/ -]/', $value ) ) {
$value = wp_kses_post( $value );
}
}
}
} else {
$atts = ltrim( $text );
}
return $atts;
}
这个函数使用正则表达式来匹配以下几种属性格式:
name="value"
name='value'
name=value
"value"
(匿名属性,添加到数组的索引中)value
(匿名属性,添加到数组的索引中)
shortcode_parse_atts
函数返回一个关联数组,其中键是属性名称(转换为小写),值是属性值。 如果属性没有名称,则值会添加到数组的数值索引中。 stripcslashes
函数用于移除属性值中的反斜杠转义。
6. 递归解析机制
do_shortcode
函数具有递归解析的能力。这意味着如果在短代码的回调函数返回的字符串中包含其他短代码,do_shortcode
函数会再次被调用来解析这些嵌套的短代码。
这种递归解析机制允许开发者创建复杂的短代码结构,其中一个短代码的输出可以作为另一个短代码的输入。
7. 短代码注册与使用示例
要使用短代码,首先需要注册它。可以使用add_shortcode
函数来注册一个短代码及其回调函数。
function my_shortcode_callback( $atts, $content = null ) {
$atts = shortcode_atts( array(
'name' => 'World',
), $atts );
return 'Hello, ' . esc_html( $atts['name'] ) . '! Content: ' . esc_html( $content );
}
add_shortcode( 'my_shortcode', 'my_shortcode_callback' );
在这个例子中,我们注册了一个名为my_shortcode
的短代码,并将其与my_shortcode_callback
函数关联起来。 my_shortcode_callback
函数接收属性数组$atts
和内容$content
作为参数。 shortcode_atts
函数用于设置属性的默认值。 esc_html
函数用于转义输出,以防止XSS攻击。
现在,可以在文章或页面中使用这个短代码:
[my_shortcode name="WordPress"]This is the content.[/my_shortcode]
do_shortcode
函数会将这个短代码替换为:
Hello, WordPress! Content: This is the content.
8. 完整的do_shortcode
函数代码分析
function do_shortcode( $content, $ignore_html = false ) {
global $shortcode_tags;
if ( false === strpos( $content, '[' ) ) {
return $content;
}
if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) {
return $content;
}
$pattern = get_shortcode_regex();
$content = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $content );
// Always restore square braces so we don't break things like <!--[if IE]>
$content = unescape_invalid_shortcodes( $content );
return $content;
}
让我们逐步分析do_shortcode
函数:
- 快速检查: 首先,它检查内容中是否包含左方括号
[
。如果没有,则表示内容中没有短代码,直接返回原始内容。 - 短代码注册检查: 然后,它检查全局变量
$shortcode_tags
是否为空。如果为空,则表示没有注册任何短代码,直接返回原始内容。 - 获取正则表达式: 调用
get_shortcode_regex()
函数获取短代码的正则表达式。 - 正则匹配与回调: 使用
preg_replace_callback
函数在内容中查找匹配的短代码,并为每个匹配项调用do_shortcode_tag
函数。 - 转义恢复: 调用
unescape_invalid_shortcodes
函数恢复被错误转义的方括号。 - 返回结果: 返回解析后的内容。
9. 关于get_shortcode_regex
函数
get_shortcode_regex
函数用于生成短代码的正则表达式。
function get_shortcode_regex() {
global $shortcode_tags;
$tagnames = array_keys($shortcode_tags);
$tagregexp = join( '|', array_map('preg_quote', $tagnames) );
// WARNING! Do not change this regex without changing do_shortcode_tag() and the shortcode parsing reg ex in wp-includes/js/tinymce/plugins/wordpress/plugin.js
return
'\[' // Opening bracket
. '(\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
. "($tagregexp)" // 2: Shortcode name
. '(?![\w-])' // Word boundary
. '(' // 3: Optional attributes
. '[^\]\/]*' // Not a closing bracket or forward slash
. '(?:'
. '\/(?!\])' // A forward slash not followed by a closing bracket
. '[^\]\/]*' // Not a closing bracket or forward slash
. ')*?'
. ')'
. '(?:'
. '(\/)' // 4: Self closing tag ...
. '\]' // ... and closing bracket
. '|'
. '\]' // Closing bracket
. '(?:'
. '(' // 5: Unclosed content
. '[^\[]*+' // Not an opening bracket
. '(?:'
. '\[(?!\/\2\])' // An opening bracket not followed by the closing shortcode tag
. '[^\[]*+' // Not an opening bracket
. ')*+'
. ')'
. '\[\/\2\]' // Closing shortcode tag
. )?'
. ')'
. '(\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
}
这个函数从全局变量$shortcode_tags
中获取所有已注册的短代码名称,并将它们组合成一个正则表达式。
10. 关键点的总结概括
do_shortcode
函数是WordPress短代码解析的核心,依赖于正则表达式进行匹配。do_shortcode_tag
函数执行匹配到的短代码对应的回调函数。通过递归调用,实现嵌套短代码的解析。