我正在建造一个"提及"探测器.提及采取@(user:\d+)的形式,例如@user:5.

我还想抓住周围的上下文,让我们在提到之前和之后说最多五个词.我最初 Select 的是

$before = '((?:\S+\s+){0,5})';
$mention = '@(user:\d+)';
$after = '((?:\s+\S+){0,5})';
$pattern = "/{$before}{$mention}{$after}/';

这很有效,除非在之前或之后的组中提到.例如,

Here is some text with @user:123 and @user:456 mentions

应该返回两个匹配项,但preg_match_all只返回第一个匹配项:

Before Mention After
Here is some text with @user:123 and @user:456 mentions
some text with @user:123 and @user:456 mentions

有没有办法一次通过就能做到这一点?

或者,如果我必须找到提及的内容及其位置,例如

preg_match_all('/@(\w+:\d+)/', $text, $mentions, PREG_OFFSET_CAPTURE);

然后再次循环以获得每个提及的上下文,我如何才能在不再次匹配提及的情况下获得"之前"的上下文?

推荐答案

可以使用先行断言中的捕获组获得重叠匹配:(?=(...)).但在您的例子中,不可能使用这个技巧,因为当两个提及位于字符串开头附近时,它们的"之前的上下文"从相同的偏移量0开始.

您可以将整个字符串拆分成"单词"(非空格字符序列),并向与提及匹配的分支添加一个标记.这样,preg_match_all将返回一个子数组,该数组已经使用提及的索引进行了筛选. 然后,就像 comments 中建议的那样,你可以使用implode/joinarray_slice和基础数学来构建上下文.

$text = 'Here are @user:123 and @user:456 mentions';

$contextSize = 5;
$pattern = '~ @user:[0-9]+ \s* (*:mention) | \S+ \s* ~x';

$results = [];

if (preg_match_all($pattern, $text, $matches)) {
    
    foreach ($matches['MARK'] as $k => $v) {
        $beforeIndex = max(0, $k - $contextSize);
        $beforeSize = $k - $beforeIndex;
        $afterIndex = $k + 1;
        $results[] = [
            'before' => join(array_slice($matches[0], $beforeIndex, $beforeSize)),
            'mention' => $matches[0][$k],
            'after' => join(array_slice($matches[0], $afterIndex, $contextSize))
        ];
    }
}

print_r($results);

您可以随意添加rtrim以删除尾随空格.

demo

请注意,array_slice本身处理的索引和长度超过数组的大小,不会出现错误或警告.这就是为什么在使用它之前,不需要判断在提到之后是否有单词(以及有多少).

Php相关问答推荐

如何使用查询范围在Statamic中按组过滤用户?

FatFreeFramework上的不同路由

PHP-转义字符串内的双反斜杠

如何让PHP变量跨越多个脚本调用?

Symfony装置加载:try 从命名空间加载类";ClassUtils";Doctrine\Common\Util";

致命错误:未捕获错误:Google\Auth\HttpHandler\HttpHandlerFactory::build()

EBay Inventory API createOrReplaceInventoryItem出错

在PHP中读取JSON

为什么本地主机(Xampp)上的laravel运行速度比普通html站点慢?

使用随机生成器在MYSQL中创建唯一ID-定义一个数组来存储已知ID是否安全

函数存储到变量中

如何在laravel中添加延迟?

Regex:高级删除单行PHP注释

在WooCommerce中以编程方式添加特定产品后,将价格设置为零

将自定义字段添加到管理产品中 WooCommerce 3.2+ 中的快速编辑

有没有办法像引用枚举一样引用注册表中的对象?

如何将不同的 Woocommerce 费用合并为一个名称?

invalid_grant 和无效的 JWT 签名

在WooCommerce中显示 checkout 的高级自定义字段(Advanced Custom Fields)并保存其值

从 CSV 文件 seeder 数据库时的额外行