Различные регулярные выражения preg_match_all приводят к тесту в реальном времени и моему сценарию - PullRequest
0 голосов
/ 22 декабря 2018

У меня есть следующая строка:

{ Author = {Smith, John and James, Paul and Hanks, Tom}, Title = {{Some title}}, Journal = {{Journal name text}}, Year = {{2022}}, Volume = {{10}}, Number = {{11}}, Month = {{DEC}}, Abstract = {{Abstract text abstract text, abstract. Abstract text - abstract text? Abstract text! Abstract text abstract text abstract text abstract text abstract text abstract text abstract text abstract text, abstract text. Abstract text abstract text abstract text abstract text abstract text.}}, DOI = {{10.3390/ijms19113496}}, Article-Number = {{1234}}, ISSN = {{1234-5678}}, ORCID-Numbers = {{}}, Unique-ID = {{ISI:1234567890}}, } 

И моя цель - получить эти значения в ассоциативном массиве.Я пытаюсь это регулярное выражение:

/([a-zA-Z0-9\-\_]+)\s*=\s*(\{(.*)\}|\d{4})/

с использованием preg_match_all, без дополнительных аргументов (просто регулярное выражение, ввод и вывод), но хотя он работает правильно на онлайн-тестеров, таких как это , это не таквернуть все значения в моем скрипте .php, только некоторые из них.Особенно, аннотация и автор так или иначе никогда не совпадают.Я попытался изменить аргументы (в настоящее время используется U ( не жадное сопоставление по умолчанию ), но это не решает мою проблему. Любая помощь очень ценится.

1 Ответ

0 голосов
/ 22 декабря 2018

Измените ваш шаблон с этого:

/([a-zA-Z0-9\-\_]+)\s*=\s*(\{(.*)\}|\d{4})/

на

/([a-zA-Z0-9\-\_]+)\s*=\s*(\{[^}]+\}|\d{4})/

Или в коде:

$s = '{Author = {Smith, John and James, Paul and Hanks, Tom}, Title = {{Some title}}, Journal = {{Journal name text}}, Year = {{2022}}, Volume = {{10}}, Number = {{11}}, Month = {{DEC}}, Abstract = {{Abstract text abstract text, abstract. Abstract text - abstract text? Abstract text! Abstract text abstract text abstract text abstract text abstract text abstract text abstract text abstract text, abstract text. Abstract text abstract text abstract text abstract text abstract text.}}, DOI = {{10.3390/ijms19113496}}, Article-Number = {{1234}}, ISSN = {{1234-5678}}, ORCID-Numbers = {{}}, Unique-ID = {{ISI:1234567890}}, }';
$p = '/(\b[-\w]+)\s*=\s*(\{([^}]+)\}|\d{4})/';

preg_match_all($p, $s, $m);
print_r($m);

Песочница

Это приблизит вас, но требует немного доработки.По сути дела, вы сопоставляли первый { с последним }, потому что .* соответствует чему-либо «жадному», что означает, что он потребляет все совпадения, которые может.

Вы можете получить симулированный результат выше \{[^}]+\}, просто сделав его не жадным, как этот \{(.*?)\} вместо оригинального \{(.*)\}, но я не думаю, что он также читает.

Выходные данные

 ...
[1] => Array
    (
        [0] => Author
        [1] => Title
        [2] => Journal
 ...

[2] => Array
    (
        [0] => {Smith, John and James, Paul and Hanks, Tom}
        [1] => {{Some title} //<--- lost }
        [2] => {{Journal name text} //<--- lost }

Самое простое, что можно сделать здесь, это добавить пару необязательных {} или \}? in, и тогда, по крайней мере, вы сможете собрать полные теги:

  //note the \{\{? and \}?\}
  $p = '/(\b[-\w]+)\s*=\s*(\{\{?([^}]+)\}?\}|\d{4})/';

Это меняет индекс 2 на это:

[2] => Array
    (
        [0] => {Smith, John and James, Paul and Hanks, Tom}
        [1] => {{Some title}}
        [2] => {{Journal name text}}

Но, поскольку нет примера желаемых результатов, это насколько я могу пойти.

В качестве стороны:

Другой способ сделать это (без регулярного выражения) - обрезать {}, затем взорвать его },, затем зациклить и взорвать =.И немного поиграть с форматом.

Примерно так:

$s = '{Author = {Smith, John and James, Paul and Hanks, Tom}, Title = {{Some title}}, Journal = {{Journal name text}}, Year = {{2022}}, Volume = {{10}}, Number = {{11}}, Month = {{DEC}}, Abstract = {{Abstract text abstract text, abstract. Abstract text - abstract text? Abstract text! Abstract text abstract text abstract text abstract text abstract text abstract text abstract text abstract text, abstract text. Abstract text abstract text abstract text abstract text abstract text.}}, DOI = {{10.3390/ijms19113496}}, Article-Number = {{1234}}, ISSN = {{1234-5678}}, ORCID-Numbers = {{}}, Unique-ID = {{ISI:1234567890}}, }';

function f($s,$o=[]){$e=array_map(function($v)use(&$o){if(strlen($v))$o[]=preg_split("/\s*=\s*/",$v."}");},explode('},',trim($s,'}{')));return$o;}

print_r(f($s));

Вывод

Array
(
    [0] => Array
        (
            [0] => Author
            [1] => {Smith, John and James, Paul and Hanks, Tom}
        )

    [1] => Array
        (
            [0] =>  Title
            [1] => {{Some title}}
        )

    [2] => Array
        (
            [0] =>  Journal
            [1] => {{Journal name text}}
        )
   ...

Песочница

Несжатая версия:

/* uncompressed */
function f($s, $o=[]){
    $e = array_map(
        function($v) use (&$o){
            if(strlen($v)) $o[] = preg_split("/\s*=\s*/", $v."}");
        },
        //could use preg_split for more flexibility  '/\s*\}\s*,\s*/`
        explode(
            '},',
            trim($s, '}{')
        )
    );
    return $o;
}

Это не такое «надежное» решение, но если формат всегда похож на пример, этого может быть достаточно.В любом случае, это выглядит круто.Выходной формат немного лучше, но вы можете сделать array_combine($m[1],$m[2]), чтобы исправить версию Regex.

Вы также можете передать ему массив, и он добавит его, например:

print_r(f($s,[["foo","{bar}"]]));

Вывод:

Array
(
[0] => Array
    (
        [0] => foo
        [1] => {bar}
    )

[1] => Array
    (
        [0] => Author
        [1] => {Smith, John and James, Paul and Hanks, Tom}
    )

Тогда, если вам нужны другие форматы:

//get an array of keys  ['foo', 'Author']
print_r(array_column($a,0));

//get an array of values ['{bar}', '{Smith, John ...}']
print_r(array_column($a,1));

//get an array with keys=>values ['foo'=>'{bar}', 'Author'=>'{Smith, John ...}']
print_r(array_column($a,1,0));

Какие, конечно, вы могли бы выпекать прямо в функцию return.

В любом случае этобыло весело, наслаждайтесь.

ОБНОВЛЕНИЕ

Регулярное выражение (\{[^}]+\}|\d{4}) означает следующее:

  • (...) захват группы, захватывает все совпадениязаключено в ( и )
  • \{ match { буквально
  • [^}]+ соответствует чему-либо, кроме } один или несколько раз
  • \} соответствует } буквально
  • | или
  • \d{4} соответствует 0-9 4 раза.

В основном проблема с этим (\{(.*)\}вместо \{[^}]+\} это то, что .* также соответствует } и {, и, поскольку он жадный (не тянется ?, такой как \{(.*?)\}), он будет соответствовать всему, что может.Таким образом, в действительности он будет соответствовать этому fname={foo}, lname={bar}, так что будет соответствовать всему между первым { и последним } или {foo}, lname={bar}.Регулярное выражение с "not" }, однако, соответствует только первому }, потому что [^}]+ не будет соответствовать окончанию } в foo}, вместо этого оно соответствует \}, что завершает шаблон,Если мы использовали другой (.*), он фактически совпадает с последним } и захватывает все, что находится между первым { и последним } в строке.

Слово о Lexing

Вложение может быть очень сложным для регулярных выражений.Как я уже сказал в комментариях, лексер лучше.Это означает, что вместо сопоставления с большим шаблоном, например: /([a-zA-Z0-9\-\_]+)\s*=\s*(\{[^}]+\}|\d{4})/ вы сопоставляете меньшие шаблоны, подобные этому

[
  '(?P<T_WORDS>\w+)', ///matches a-zA-Z0-9_
  '(?P<T_OPEN_BRACKET>\{)', ///matches {
  '(?P<T_CLOSE_BRACKET>\})',  //matches }
  '(?P<T_EQUAL>=)',  //matches =
  '(?P<T_WHITESPACE>\s+)', //matches \r\n\t\s
  '(?P<T_EOF>\Z+)', //matches end of string
];

Вы можете соединить их с помощью или

  "(?P<T_WORD>\w+)|(?P<T_OPEN_BRACKET>'{')|(?P<T_CLOSE_BRACKET>'}')|(?P<T_EQUAL>'=')|(?P<T_WHITESPACE)\s+|(?P<T_EOF)\Z+",

(?P<name>..) является именованной группой захвата, просто делает вещи проще.Вместо простых совпадений, таких как:

[
   1 => [ 0 => 'Title', 1 => ''],
]

У вас также будет следующее:

[
   1 => [ 0 => 'Title', 1 => ''],
   'T_WORD' => [ 0 => 'Title', 1 => '']
]

Это облегчает присвоение имени токена обратно совпадению.

В любом случае цель на этом этапе будет заключаться в том, чтобы получить массив (в конце концов) с «токенами» или именем совпадения, например (что-то), например:Title = {{Some title}}

  //token stream
 [
    'T_WORD' => 'Title',   //keyword
    'T_WHITESPACE' => ' ', //ignore
    'T_EQUAL' => '=',      //instruction to end key,
    'T_WHITESPACE' => ' ', //ignore
    'T_OPEN_BRACKET' => '{', //inc a counter for open brackets
    'T_OPEN_BRACKET' => '{', //inc a counter for open brackets
    'T_WORD' => 'Some',      //capture as value
    'T_WHITESPACE' => ' ',   //capture as value
    'T_WORD' => 'title',     //capture as value
    'T_CLOSE_BRACKET' => '}', //dec a counter for open brackets
    'T_CLOST_BRACKET' => '}', //dec a counter for open brackets
   ]

Это должно быть довольно прямолинейно, но ключевое отличие в том, что в чистом регулярном выражении вы не можете сосчитать { и }, поэтому у вас нет возможности проверитьсинтаксис строки, он либо совпадает, либо нет.

С версией lexer вы можете считать эти вещи и действовать соответствующим образом.Это потому, что вы можете выполнять итерацию, хотя токен совпадает, и «проверять» строку.Например, мы можем сказать следующее:

Слово, за которым следует =, является именем атрибута.Все, что находится внутри { один или два }, должно заканчиваться тем же числом {, что и }, и все, что находится внутри { и }, кроме }, является некоторой «информацией», которая нам нужна.Игнорируйте все пробелы за пределами наших {} пар ... и т. Д. Это дает "гранулярность", необходимую для проверки данных этого типа.

Я упоминаю об этом, потому что даже пример, который я вам привожу /(\b[-\w]+)\s*=\s*(\{\{?([^}]+)\}?\}|\d{4})/потерпит неудачу в таких строках

 Author = {Smith, John and James, {Paul and Hanks}, Tom}

, в которых он будет возвращать совпадения для

 Author 
{Smith, John and James, {Paul and Hanks}

Другой пример: это не приведет к возникновению проблемы:

Title = {{Some title}, Journal = {{Journal name text}}

Который даст совпадения так:

Title 
Some title
//and
Journal 
Journal name text

Это выглядит правильно, но это не потому, что в {{Some title} отсутствует }.Что вы делаете с неверным синтаксисом в вашей строке, зависит от вас, но в версии Regex мы не можем это контролировать.Я должен упомянуть, что даже рекурсивное регулярное выражение («сопоставить пары скобок») потерпит неудачу, возвращая что-то вроде:

{{Некоторое название}, Journal = {{Текст названия журнала}

Но вВ версии лексера мы можем увеличить счетчик { +1 { +1, затем слово Some title, затем } -1, и мы останемся с 1 вместо 0. Таким образом, в нашем коде мы знаем, что мыотсутствует 1195 * там, где нужно.

Ниже приведены некоторые примеры написанных мною лексеров (там даже пустой)

https://github.com/ArtisticPhoenix/MISC/tree/master/Lexers

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

По сути, с большим сложным шаблоном вся эта сложность запекается в шаблоне, что затрудняет его изменение.С меньшими шаблонами сложность шаблона возникает в результате того, что он анализируется (ваши инструкции кода), что значительно упрощает настройку для крайних случаев и т. Д.

Удачи!

...