分析 WordPress `get_blocks_for_post()` 函数的源码:如何从文章内容中提取区块对象。

各位代码界的泥石流们,晚上好!今天咱们来聊聊 WordPress 里一个挺有意思的小东西:get_blocks_for_post() 函数。这玩意儿就像个考古学家,专门从 WordPress 文章的内容里挖出各种各样的“区块化石”。

咱们的目标是:搞明白它到底是怎么挖的,挖出来的“化石”又是什么样子的。准备好,我们要开始“挖坟”了!

一、开场白:区块时代的“洛阳铲”

在古腾堡编辑器(也就是 WordPress 的区块编辑器)出现之前,咱们写文章那叫一个“自由”,各种 HTML 标签满天飞,排版样式全靠手撸。但自从有了区块,一切都变得井然有序,每个区块负责一块内容,就像搭积木一样。

get_blocks_for_post() 函数就是用来解析这种“积木式”文章内容的,它就像一把精准的“洛阳铲”,专门挖掘文章中的区块信息,然后把这些信息整理成易于理解的对象。

二、源码探秘:一步一步揭开神秘面纱

咱们先来看看 get_blocks_for_post() 函数的“真容”(简化版,保留核心逻辑):

/**
 * 获取文章的区块列表.
 *
 * @param WP_Post|int|null $post 文章对象或 ID。默认使用全局 $post 对象。
 * @return array|null 区块数组,如果文章不存在或内容为空,则返回 null。
 */
function get_blocks_for_post( $post = null ) {
    $post = get_post( $post );

    if ( ! $post || empty( $post->post_content ) ) {
        return null;
    }

    $content = $post->post_content;
    $blocks = parse_blocks( $content ); // 核心函数:解析区块

    return $blocks;
}

这段代码简洁明了,主要做了三件事:

  1. 获取文章对象: 接收文章 ID 或者文章对象,如果没传,就用全局 $post 对象。
  2. 检查文章是否存在和内容是否为空: 如果文章不存在或者内容是空的,直接返回 null,没啥好挖的。
  3. 解析区块: 这是最关键的一步,调用 parse_blocks() 函数来解析文章内容,把内容分割成一个个区块对象。

看到没?真正的“挖掘”工作是由 parse_blocks() 函数完成的。所以,咱们的重点要转移到这个函数身上。

三、parse_blocks():区块解析的“屠龙刀”

parse_blocks() 函数才是真正的“屠龙刀”,它负责把一坨字符串(文章内容)分解成一个个区块对象。这把刀的“刀法”可不简单,咱们来仔细看看:

/**
 * 将内容解析为区块数组.
 *
 * @param string $content 要解析的内容。
 * @return array 区块数组.
 */
function parse_blocks( $content ) {
    $blocks = array();
    $start = 0;
    $length = strlen( $content );

    while ( $start < $length ) {
        $block = parse_block( substr( $content, $start ) ); // 核心函数:解析单个区块

        if ( null === $block ) {
            // 如果没有找到区块,就创建一个自由格式区块,包含剩余的内容
            $text = substr( $content, $start );
            $blocks[] = array(
                'blockName' => null,
                'attrs' => array(),
                'innerBlocks' => array(),
                'innerHTML' => $text,
                'innerContent' => array( $text ),
            );
            break; // 结束循环,因为剩余的内容被视为一个整体
        }

        $blocks[] = $block;
        $start += strlen( $block[0] ); // 更新起始位置
    }

    return $blocks;
}

这段代码稍微复杂一点,但核心思想是:

  1. 循环解析: 从文章内容的开头开始,循环查找和解析区块。
  2. 解析单个区块: 调用 parse_block() 函数来解析单个区块。
  3. 处理未识别的内容: 如果 parse_block() 函数返回 null,说明没有找到区块,就把剩余的内容当作一个自由格式的区块处理。
  4. 更新起始位置: 每次成功解析一个区块后,更新起始位置,继续查找下一个区块。

看到没?parse_block() 函数才是真正的“刀尖”,它负责解析单个区块。所以,咱们的重点要再次转移到这个函数身上。

四、parse_block():区块解析的“显微镜”

parse_block() 函数就像一个“显微镜”,它负责分析文章内容中的每一个细节,找出区块的开始标记、属性、内容等等。

/**
 * 解析一个区块.
 *
 * @param string $content 要解析的内容.
 * @return array|null 区块数据数组,如果未找到有效的区块开始标记,则返回 null。
 */
function parse_block( $content ) {
    // 匹配区块的开始标记
    if (
        ! preg_match(
            '/<!--s+wp:([a-z0-9/-]+)s+({(?:[^{}]++|(?1))*})?s+-->/',
            $content,
            $matches,
            PREG_OFFSET_CAPTURE
        )
    ) {
        return null; // 没有找到区块开始标记
    }

    $block_name = $matches[1][0]; // 区块名称
    $block_start = $matches[0][1]; // 区块开始标记的位置
    $block_length = strlen( $matches[0][0] ); // 区块开始标记的长度

    $attrs = array();
    if ( isset( $matches[2] ) && '' !== $matches[2][0] ) {
        // 解析区块属性
        $attrs = json_decode( $matches[2][0], true );
        if ( ! is_array( $attrs ) ) {
            $attrs = array(); // 如果解析失败,则使用空数组
        }
    }

    // 获取 innerHTML 和 innerContent
    $inner_blocks = array();
    $inner_html = '';
    $inner_content = array();

    // 匹配区块的结束标记
    $end_tag = "<!-- /wp:$block_name -->";
    $end_tag_pos = strpos( $content, $end_tag, $block_start + $block_length );

    if ( false !== $end_tag_pos ) {
        // 找到结束标记
        $block_content = substr( $content, $block_start + $block_length, $end_tag_pos - ($block_start + $block_length) );
        $inner_html = $block_content;

        // 递归解析内部区块
        $inner_blocks = parse_blocks( $block_content );

        // 构建 innerContent 数组
        $inner_content = array( $inner_html );
    } else {
        // 没有找到结束标记,可能是自闭合区块
        $inner_html = '';
        $inner_content = array();
    }

    // 构建区块数据
    $block = array(
        'blockName' => $block_name,
        'attrs' => $attrs,
        'innerBlocks' => $inner_blocks,
        'innerHTML' => $inner_html,
        'innerContent' => $inner_content,
    );

    // 获取整个区块的内容(包括开始和结束标记)
    $full_block = substr( $content, $block_start, (false === $end_tag_pos ? strlen( $content ) : $end_tag_pos + strlen( $end_tag ) ) - $block_start );
    $block[0] = $full_block;

    return $block;
}

这段代码是整个流程中最复杂的部分,咱们来分解一下:

  1. 匹配区块开始标记: 使用正则表达式匹配区块的开始标记,例如 <!-- wp:paragraph {"align":"center"} -->
  2. 提取区块名称和属性: 从匹配结果中提取区块的名称(例如 paragraph)和属性(例如 {"align":"center"})。
  3. 解析区块属性: 使用 json_decode() 函数解析区块属性,将其转换为 PHP 数组。
  4. 匹配区块结束标记: 查找区块的结束标记,例如 <!-- /wp:paragraph -->
  5. 提取内部内容: 如果找到结束标记,提取开始标记和结束标记之间的内容,作为区块的内部内容。
  6. 递归解析内部区块: 再次调用 parse_blocks() 函数,递归解析内部内容,找出嵌套的区块。
  7. 构建区块数据: 将区块的名称、属性、内部区块、内部 HTML 和内部内容等信息组合成一个数组,作为区块的数据。
  8. 返回区块数据: 返回包含区块信息的数组。

五、区块数据的结构:积木的蓝图

经过 get_blocks_for_post() 函数的“挖掘”和“解析”,咱们最终得到的是一个区块数组,每个区块都是一个包含以下信息的数组:

键名 类型 描述 示例

发表回复

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