Извлечение данных с помощью функции preg_match()

Чтобы извлечь соответствующую часть данных, нужно вызвать функцию PHP preg_match() с третьим аргументом, обычно называемым «$matches».

Фактические совпадения (если они есть) с регулярным выражением, переданным в качестве первого аргумента в preg_match(), в строке, переданной в качестве второго аргумента, будут храниться внутри $matches (третьего аргумента). Он представляет собой массив с определенной структурой. Начнем с простого примера, в котором мы используем var_dump() для просмотра структуры массива $matches после того, как было найдено совпадение.

<?php
 
$sequence1 = "CCCGAATTCTTT";
 
$sequence2 = "CCCTAATTATTT";
 
$sequences = array($sequence1, $sequence2);
 
$i = 1;
 
// Шаблон $regexp может соответствовать любой из следующих последовательностей
// GAATTC
// GAATTA
// TAATTC
// TAATTA
$regexp = "/[GT]AATT[CA]/";
 
foreach($sequences as $sequence){
    if(preg_match($regexp, $sequence, $matches)){
        echo "<p>n<strong>Match to sequence $i</strong><br>n";
        var_dump($matches);
        echo "</p>n";
    }
    $i++;
}
 
?>

Запустив этот preg match PHP пример, мы получим следующее:

Как и ожидалось, совпадение с регулярным выражением было найдено в обеих последовательностях.

Какова структура массива $matches, в котором сохраняются найденные совпадения? Поскольку регулярное выражение не содержало какой-либо части в круглых скобках (используется для создания «захватываемых групп»), $matches содержит только один элемент, совпадение, найденное для всего регулярного выражения. var_dumps начинается с «array(1)». Это означает, что $matches - это массив, содержащий один элемент. Для обеих последовательностей это единственный элемент, который имеет индекс 0 ("[0] =>") и является строкой длиной в 6 символов (“string(6)”). Тем не менее, фактическая строка отличается для двух последовательностей. Это «GAATTC» для первой последовательности и «TAATTA» для второй.

Если регулярное выражение (первый аргумент PHP preg_match()) не содержит захватываемых групп, массив $matches, переданный во время вызова функции preg_match() в качестве третьего аргумента, будет содержать только один элемент. Он представляет собой часть строки, переданной в preg_match ()+ в качестве второго элемента, которая соответствует шаблону, определенному всем регулярным выражением. Таким образом, мы не просто знаем, что в строке было найдено совпадение с регулярным выражением, но и что это за совпадение.

Еще раз рассмотрим тот же пример, но с небольшими изменениями, чтобы обеспечить более чистый вывод и пропустить var_dump:

<?php

$sequence1 = "CCCGAATTCTTT";

$sequence2 = "CCCTAATTATTT";

$sequences = array($sequence1, $sequence2);

$i = 1;

// Шаблон $regexp может соответствовать любой из следующих последовательностей
// GAATTC
// GAATTA
// TAATTC
// TAATTA
$regexp = "/[GT]AATT[CA]/";

foreach($sequences as $sequence){
    if(preg_match($regexp, $sequence, $matches)){ // Вызываем preg_match() с 3 аргументами вместо 2
        echo "<p>n<strong>Match to sequence $i</strong><br>n";
        echo "Match within sequence <span style="font-family:courier;">$sequence</span> is <span style="font-family:courier;">".$matches[0]."</span>n";
        echo "</p>n";
    }
    $i++;
}

?>

Мы получим:

Нам нужно сделать так, чтобы preg match utf 8 нашла внутри строки не только первое совпадение с регулярным выражением.
В приведенном выше примере, если целевая последовательность была найдена (возможное соответствие шаблону выделено красным цветом):

при вызове функции preg_match () может быть найдено только первое соответствие GAATTC.

Определение в регулярном выражении захватываемых групп с помощью круглых скобок

Во многих случаях может быть необходимо найти совпадение только с частью шаблона. Можно использовать шаблон для идентификации части строки, содержащей бит (или биты) информации, которая нас действительно интересует.
Рассмотрим гипотетическую строку заголовка последовательности FASTA.

Определение в регулярном выражении захватываемых групп с помощью круглых скобок

Красным выделено GI, число, которое можно извлечь из заголовка. Мы могли бы написать такое регулярное выражение для этих строк, чтобы захватить GI:

Определение в регулярном выражении захватываемых групп с помощью круглых скобок - 2

Что означает «любое число, которое повторяется один или несколько раз». Это общий шаблон. Ему будет соответствовать 5, 33, 453672 и т. д. Он захватит наш номер GI в этом заголовке, но это просто «случайность».

Вот код c PHP preg match, который можно использовать:

<?php

$header = '>gi|197107235|pdb|3CHW|P Chain P, Complex Of Dictyostelium Discoideum Actin';

preg_match("/d+/", $header, $matches);

echo "<p>".$matches[0]."</p>";

который дает следующий результат:

197107235

В этом другом гипотетическом заголовке:

тот же шаблон захватит «3», первое число, которое получит функция preg_match() при вызове, будет ему соответствовать. И это не число GI, которое нам нужно, его вообще нет в этом заголовке. Попробуйте применить следующий код:

<?php

$header = '>pdb|3CHW|P Chain P, Complex Of Dictyostelium Discoideum Acti';

preg_match("/d+/", $header, $matches);

echo "<p>".$matches[0]."</p>";

?>

Чтобы написать конкретный шаблон для GI, который найдет число в заголовке FASTA, воспользуемся синтаксическим контекстом. С его помощью число GI обычно включается в заголовки FASTA. Составим шаблон для preg match PHP примера, который включает в себя эту информацию.

Контекст числа GI:

Составим регулярное выражение, которое включает этот контекст:

<?php

$gi_regexp = "/gi|d+|/"; // Чтобы задать е соответствие горизонтальной черте | нужно добавить перед ней обратную косую черту, так как это метасимвол

?>

Но если мы выполним обычный код:

<?php

$header = '>gi|197107235|pdb|3CHW|P Chain P, Complex Of Dictyostelium Discoideum Actin';

$gi_regexp = "/gi|d+|/";

preg_match($gi_regexp, $header, $matches);

echo "<p>".$matches[0]."</p>";

?>

Вот, что мы получим:

Теперь мы получили нужное число, но оно включено в контекст, который мы определили в регулярном выражении. Не удивительно, что массив $matches теперь содержит в качестве уникального элемента совпадение со всем регулярным выражением.

Чтобы получить только число, можно использовать в регулярном выражении захватываемую группу. Для этого нужно заключить часть выражения, которое соответствует числу, в круглые скобки.

Регулярное выражение для PHP preg match теперь будет выглядеть следующим образом:

<?php

$gi_regexp = "/gi|(d+)|/"; // Чтобы задать дословное соответствие горизонтальной черте | нужно добавить перед ней обратную косую черту, так как это метасимвол 
// Как видите, теперь часть шаблона, относящая к числу, заключена в скобки

?>

Группировка элементов в скобках без создания захватываемой группы

В определенных случаях можно группировать символы в скобках без создания группы захвата. В этом случае нужно добавить «?:» сразу после первой скобки. Например, если вы хотите включить в шаблон дополнительную часть «ABC», но ее не нужно сохранять в массив $matches:

Группировка элементов в скобках без создания захватываемой группы

Второй знак вопроса означает, что предшествующее выражение является необязательным.

Еще одна хитрость: это касается захватываемых групп, которые мы определяем в регулярном выражении. Можно создать их столько, сколько нужно. Новый элемент будет автоматически добавлен в массив $matches, содержащий совпадение только с захватываемой группой.

При использовании захватываемых групп в регулярном выражении первый элемент массива $matches все равно будет соответствовать всему выражению. Последующие элементы будут соответствовать захватываемым группам в том порядке, в котором они определены в регулярном выражении.

Если мы используем в preg match PHP примере только одну захватываемую группу, $matches[0] будет содержать фрагмент, соответствующий всему выражению, а $matches[1] - фрагмент, соответствующий захватываемой группе.

Если мы используем две захватываемых группы (два набора скобок), массив $matches будет содержать три элемента: $matches[0] - соответствие всему выражению, $matches[1] - соответствие первой захватываемой группе и $matches[2] - соответствие второй захватываемой группе. Тот же принцип действует при любом количестве захватываемых групп, которые используются в выражении.

Вот пример кода, позволяющего определить номер GI:

<?php

$header = '>gi|197107235|pdb|3CHW|P Chain P, Complex Of Dictyostelium Discoideum Actin';

$gi_regexp = "/gi|(d+)|/";

preg_match($gi_regexp, $header, $matches);

echo "<p><strong>Match to the entire pattern:</strong><br>".$matches[0]."</p>";
echo "<p><strong>Just the GI number:</strong><br>".$matches[1]."</p>";
echo '<p><strong>The var_dump of the $matches array:</strong><br>';
echo var_dump($matches);
echo "</p>";

?>

Вывод:

Группировка элементов в скобках без создания захватываемой группы - 2

В PHP preg match массив $matches теперь содержит два элемента, а не один. Второй элемент содержит совпадение с первой. В этом примере только с первой захватываемой группой. Как и в предыдущих примерах, первый элемент $matches по-прежнему содержит совпадение со всем регулярным выражением.

Рассмотрим следующую последовательность FASTA:

Составим регулярное выражение, которое позволит захватить как идентификатор GI, так и идентификатор PDB. Это релевантная часть заголовка, которая предоставляет весь контекст двух идентификаторов:

Идентификатор GI состоит только из цифр. Но идентификаторы PDB могут состоять из заглавных букв или цифр, и они всегда содержат четыре символа. Ниже приведено соответствующее регулярное выражение:

<?php

$gi_plus_pdb_regexp = "/^>gi|(d+)|pdb|([A-Z1-9]{4})|/";

// ^>gi          =>   строка, начинающаяся с >gi
// |            =>   затем идет вертикальная черта. Перед ней добавляется обратная косая черта, так как вертикальная черта - это метасимвол
// (d+)         =>   один или несколько символов (идентификатор GI, захватываемая группа 1)
// |            =>   вертикальная черта
// pdb           =>   три буквы pdb
// |            =>   вертикальная черта
// ([A-Z1-9]{4}) =>   заглавная буква или цифра, точно 4 символа (идентификатор PDB, захватываемая группа 2)
// |            =>   вертикальная черта

?>

Теперь, когда у нас есть соответствующее регулярное выражение для PHP preg match, можно написать небольшой скрипт для анализа файла последовательности FASTA и извлечения идентификатора GI и идентификатора PDB.

<?php

$gi_plus_pdb_regexp = "/^>gi|(d+)|pdb|([A-Z1-9]{4})|/";

$fasta_sequence = file_get_contents("http://www.cellbiol.com/bioinformatics_web_development/wp-content/uploads/gi-28373620.fasta");

$fasta_sequence_lines = explode("n", $fasta_sequence);

$gi_id = "not found"; // если идентификатор gi не найден во время выполнения цикла foreach, у нас все равно есть для этого случая определенное значение
$pdb_id = "not found";
$match_to_whole_regexp = "not found";

foreach($fasta_sequence_lines as $line){
    if(preg_match($gi_plus_pdb_regexp, $line, $matches)){
        $match_to_whole_regexp = $matches[0]; // Первый элемент массива $matches содержит соответствие всему регулярному выражению
        $gi_id = $matches[1]; // Второй элемент массива $matches содержит первую захватываемую группу, идентификатор GI
        $pdb_id = $matches[2]; // Третий элемент массива $matches содержит вторую захватываемую группу, идентификатор PDB
        break;
    }
}

// Теперь мы можем предоставить результат
echo "<p><strong>GI ID: </strong>$gi_id</p>";
echo "<p><strong>PDB ID: </strong>$gi_id</p>";
echo "<p><strong>Whole match: </strong>$match_to_whole_regexp</p>";

?>

Вот результат этого скрипта:

В качестве завершающего preg match PHP примера разберем возможности скриптов и регулярных выражений для извлечения всех идентификаторов GI и PDB из файла FASTA, содержащего несколько последовательностей.

Вы можете скачать файл здесь. Если внимательно посмотрите на заголовки последовательностей, то заметите, что все они содержат идентификатор GI. Но только некоторые содержат идентификатор PDB. Чтобы не пропустить все идентификаторы GI и PDB, нельзя использовать составленное ранее регулярное выражение, которое захватывает оба идентификатора. Данное выражение предполагает, что оба идентификатора присутствуют в последовательности.

Составим новое регулярное выражение, в котором часть идентификатора PDB является необязательной.

<?php

$gi_plus_pdb_regexp = "/^>gi|(d+)|(?:pdb|([A-Z1-9]{4})|)?/";

// ^>gi          =>   строка начинается с >gi
// |            =>   затем идет вертикальная черта. Перед ней добавляется обратная косая черта, так как вертикальная черта - это метасимвол
// (d+)         =>   один или несколько символов (идентификатор GI, захватываемая группа 1)
// |            =>   вертикальная черта
// (?:           =>   группа, но не захватываемая. Отсюда мы можем начать необязательную группу символов, относящуюся к pdb. После этой группы добавляется ?, который указывает, что эта группа является необязательной
// pdb           =>   три буквы pdb
// |            =>   a pipe
// ([A-Z1-9]{4}) =>   заглавные буквы или цифры, точно четыре символа (идентификатор PDB, захватываемая группа 2)
// |            =>   вертикальная черта
// )             =>   конец "группы, но не захватываемой", относящейся к pdb
// ?             =>   указывает, что предшествующая группа является необязательной (pdb)

?>

Теперь можно написать код PHP preg match, который анализирует файл FASTA с несколькими последовательностями. Результаты будут представлены в таблице.

Также обратите внимание, что мы сохраняем все результаты в массиве $ids. Его элементы представляют собой другие массивы, состоящие из двух элементов. Первый из них - идентификатор GI, а второй - идентификатор PDB. Таким образом, структура массива $ids может быть представлена следующим образом:

<?php
 
$gi_plus_pdb_regexp = "/^>gi|(d+)|(?:pdb|([A-Z1-9]{4})|)?/"; // В такой формулировке часть PDB является необязательной
 
$fasta_sequences = file_get_contents("http://www.cellbiol.com/bioinformatics_web_development/wp-content/uploads/actin-human-partial.fasta");
 
$fasta_sequence_lines = explode("n", $fasta_sequences);
 
$ids = array();
 
$seqs_num = 0;
$gis_num = 0;
$pdb_num = 0;
 
foreach($fasta_sequence_lines as $line){
    $gi_id = "not found"; // если идентификатор gi не найден во время исполнения цикла foreach, у нас есть для этого случая отдельное значение
    $pdb_id = "not found";
    if(preg_match($gi_plus_pdb_regexp, $line, $matches)){ // Мы делаем что-нибудь, только если имеем строку, для которой найдено совпадение
        $gi_id = $matches[1];
        $gis_num++;
        if(array_key_exists(2, $matches)){ // если найдено совпадение для идентификатора PDB
            $pdb_id = $matches[2];
            $pdb_num++;
        }
        $ids[] = array($gi_id, $pdb_id); // Для каждой последовательности мы добавляем в конечный массив $ids подмассив с двумя элементами, идентификаторами GI и PDB
        $seqs_num++;       
    }
}
 
// Теперь мы можем предоставить результат
 
// На этот раз мы генерируем соответствующую HTML-страницу с полным заголовком и несколькими стилями CSS, определяющими таблицу с результатами
echo "<!DOCTYPE html>n<html lang="en">n<head>n<meta charset="utf-8">n<title>Test page</title>n<style>td{padding:5px;}thead{font-weight:bold;}</style>n</head>n<body>";
 
echo "<p>$seqs_num sequences found</p>n";
echo "<p>$gis_num GI IDs found</p>n";
echo "<p>$pdb_num PDB IDs found</p>n";
 
echo "<table><thead><tr><th>GI ID</th><th>PDB ID</th></tr><tbody>n"; // Начинаем выводить HTML-код для таблицы
 
foreach($ids as $id_couple){ // для каждой пары идентификаторов GI/PDB мы создаем новую строку таблицы
    echo "<tr><td>".$id_couple[0]."</td><td>".$id_couple[1]."</td></tr>n";
    
}
echo "</tbody>n</table>n</body>n</html>"; // Закрываем различные теги, открытые до этого
 
?>

Вы можете вставить данный код и запустить скрипт на своем сервере или запустить демо-версию.

Если понимаете последний preg match PHP пример, ваш долгий путь изучения данного руководства по регулярным выражениям PHP завершен, поздравляю!

Вадим Дворниковавтор-переводчик статьи «4-9 Regular expressions in PHP – retrieving matches to patterns with preg_match() called with the $matches argument»