WordPress短代码解析器do_shortcode函数的正则匹配与递归解析机制

好的,我们开始今天的讲座。今天的主题是WordPress短代码解析器do_shortcode函数的正则匹配与递归解析机制。这是一个非常核心且重要的功能,理解它的工作方式对于开发WordPress主题、插件以及进行深度定制至关重要。

1. 短代码的概念与作用

短代码(Shortcode)是WordPress提供的一种强大的宏机制,允许用户在文章、页面、小工具等内容中嵌入动态内容或复杂功能,而无需编写任何PHP代码。 它的本质是将一段简单的标记(例如[my_shortcode])替换为预先定义好的HTML或其他输出。

短代码的设计目标是:

  • 易用性: 普通用户也能轻松使用,无需编程知识。
  • 灵活性: 允许开发者扩展WordPress的功能,并方便地插入内容。
  • 安全性: 限制用户直接执行PHP代码,避免潜在的安全风险。

2. do_shortcode函数的核心功能

do_shortcode函数是WordPress短代码解析引擎的核心函数。它的主要职责是:

  1. 接收字符串: 接收包含短代码标记的字符串作为输入。
  2. 正则匹配: 使用正则表达式在字符串中查找短代码标记。
  3. 回调执行: 找到匹配的短代码后,调用与其关联的回调函数。
  4. 替换: 将短代码标记替换为回调函数的返回值。
  5. 递归解析: 如果回调函数的返回值中包含其他短代码,则递归调用do_shortcode进行解析。
  6. 返回结果: 返回解析后的字符串。

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函数执行以下操作:

  1. 转义处理: 如果短代码被转义(例如[[shortcode]]),则移除转义字符并返回。
  2. 提取属性: 使用shortcode_parse_atts函数将属性字符串解析为关联数组。
  3. 确定内容: 获取短代码的内容。自闭合短代码的内容为空。
  4. 检查回调: 确认该短代码名称是否注册了回调函数。如果没有,则返回原始短代码标记。
  5. 执行回调: 使用call_user_func函数调用与短代码关联的回调函数,并将属性、内容和短代码名称作为参数传递给回调函数。
  6. 返回结果: 返回回调函数的返回值。

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函数:

  1. 快速检查: 首先,它检查内容中是否包含左方括号[。如果没有,则表示内容中没有短代码,直接返回原始内容。
  2. 短代码注册检查: 然后,它检查全局变量$shortcode_tags是否为空。如果为空,则表示没有注册任何短代码,直接返回原始内容。
  3. 获取正则表达式: 调用get_shortcode_regex()函数获取短代码的正则表达式。
  4. 正则匹配与回调: 使用preg_replace_callback函数在内容中查找匹配的短代码,并为每个匹配项调用do_shortcode_tag函数。
  5. 转义恢复: 调用unescape_invalid_shortcodes函数恢复被错误转义的方括号。
  6. 返回结果: 返回解析后的内容。

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函数执行匹配到的短代码对应的回调函数。通过递归调用,实现嵌套短代码的解析。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注