regex — php preg_replace назначает разные шаблоны замены для каждой группы захвата

Я пытаюсь выполнить полнотекстовый поиск mysql в логическом режиме, и мне нужно подготовить текст поиска, прежде чем я создам запрос mysql.

Чтобы добиться этого, я хотя бы мог использовать функцию PHP preg_replace и замените каждую группу захвата одним конкретным шаблоном.

  1. Первый шаблон должен найти слова или предложения между кавычками ("hello world") и добавить + до (+"hello world").
  2. Второй шаблон должен найти остальные слова (без кавычек) и добавить + до и * после (+how*).

шаблон регулярных выражений

["']+([^"']+)["']+|([^\s"']+)

шаблон замещения

+"\1" +\2*

ПРИМЕР

Для следующего ввода:

"hello world" how are you?

Должен вернуться:

+"hello world" +how* +are* +you?*

Но вместо этого он возвращает что-то не так’:

+"hello world" +* +"" +how* +"" +are* +"" +you?*

Я знаю, что шаблон замены +"\1" +\2* никогда не будет работать, так как я нигде не говорю, что +"..." следует применять только к первой группе захвата и +...* ко второму.

Тестирование онлайн-регулярных выражений.

Код PHP

$query = preg_replace('~["\']+([^"\']+)["\']+|([^\s"\']+)~', '+"\1" +\2*', $query);

Есть ли способ добиться этого в PHP? Заранее спасибо.


РЕДАКТИРОВАТЬ / РЕШЕНИЕ

Благодаря @revo предложение использовать функцию PHP preg_replace_callback, Мне удалось назначить шаблон замены для каждого шаблона поиска с расширенной функцией preg_replace_callback_array. Обратите внимание, что для этой функции требуется PHP> = 7.

Здесь я публикую окончательную версию функции, которую я использую для FULLTEXT поиск через MATCH (...) AGAINST (...) IN BOOLEAN MODE, Функция объявлена ​​в class dbReader в плагине WordPress. Может быть, это может быть полезно для кого-то.

// Return maximum 100 ids of products matching $query in
// name or description searching for each word using MATCH AGAINST in BOOLEAN MODE
public function search_products($query) {

function replace_callback($m, $f) {
return sprintf($f, isset($m[1]) ? $m[1] : $m[0]);
}

// Replace simple quotes by double quotes in strings between quotes:
// iPhone '8 GB' => iPhone "8 GB"// Apple's iPhone 8 '32 GB' => Apple's iPhone 8 "32 GB"// This is necessary later when the matches are devided in two groups:
//      1. Strings not between double quotes
//      2. Strings between double quotes
$query = preg_replace("~(\s*)'+([^']+)'+(\s*)~", '$1"$2"$3', $query);

// Do some magic to take numbers with their units as one word
// iPhone 8 64 GB => iPhone 8 "64 GB"$pattern = array(
'(\b[.,0-9]+)\s*(gb\b)',
'(\b[.,0-9]+)\s*(mb\b)',
'(\b[.,0-9]+)\s*(mm\b)',
'(\b[.,0-9]+)\s*(mhz\b)',
'(\b[.,0-9]+)\s*(ghz\b)'
);
array_walk($pattern, function(&$value) {
// Surround with double quotes only if the user isn't doing manual grouping
$value = '~'.$value.'(?=(?:[^"]*"[^"]*")*[^"]*\Z)~i';
});
$query = preg_replace($pattern, '"$1 $2"', $query);

// Prepare query string for a "match against" in "boolean mode"$patterns = array(
// 1. All strings not sorrounded by double quotes
'~([^\s"]+)(?=(?:[^"]*"[^"]*")*[^"]*\Z)~'   => function($m){
return replace_callback($m, '+%s*');
},

// 2. All strings between double quotes
'~"+([^"]+)"+~'                             => function($m){
return replace_callback($m, '+"%s"');
}
);

// Replace every single word by a boolean expression: +some* +word*
// Respect quoted strings: +"iPhone 8"// preg_replace_callback_array needs PHP Version >= 7
$query = preg_replace_callback_array($patterns, $query);

$fulltext_fields = array(
'title'         => array(
'importance'    => 1.5,
'table'         => 'p',
'fields'        => array(
'field1',
'field2',
'field3',
'field4'
)
),
'description'   => array(
'importance'    => 1,
'table'         => 'p',
'fields'        => array(
'field5',
'field6',
'field7',
'field8'
)
)
);
$select_match = $match_full = $priority_order = "";

$args = array();
foreach ($fulltext_fields as $index => $obj) {
$match          = $obj['table'].".".implode(", ".$obj['table'].".", $obj['fields']);
$select_match  .= ", MATCH ($match) AGAINST (%s IN BOOLEAN MODE) AS {$index}_score";
$match_full    .= ($match_full!=""?", ":"").$match;
$priority_order.= ($priority_order==""?"ORDER BY ":" + ")."({$index}_score * {$obj['importance']})";
array_push($args, $query);
}
$priority_order .= $priority_order!=""?" DESC":"";

// User input $query is passed as %s parameter to db->prepare() in order to avoid SQL injection
array_push($args, $query, $this->model_name, $this->view_name);

return $this->db->get_col(
$this->db->prepare(
"SELECT p.__pk $select_match
FROM ankauf_... AND
MATCH ($match_full) AGAINST (%s IN BOOLEAN MODE)
INNER JOIN ...
WHERE
m.bezeichnung=%s AND
a.bezeichnung=%s
$priority_order
LIMIT 100
;",
$args
)
);
}

1

Решение

Вы должны использовать preg_replace_callback:

$str = '"hello world" how are you?';

echo preg_replace_callback('~("[^"]+")|\S+~', function($m) {
return isset($m[1]) ? "+" . $m[1] : "+" . $m[0] . "*";
}, $str);

Выход:

+"hello world" +how* +are* +you?*

Живая демо

0

Другие решения

Других решений пока нет …