Различные регулярные выражения preg_match_all приводят к тесту в реальном времени и моему сценарию

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

{ 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

Решение

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

/([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 }

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

  //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));

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

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

ОБНОВИТЬ

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

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

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

Слово о Лексинге

Вложение может быть действительно сложным для регулярных выражений. Как я сказал в комментариях, лексер лучше. То, что это включает, вместо того, чтобы соответствовать большому скороговорке как: /([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 мы не можем это контролировать. Я должен упомянуть, что даже рекурсивное регулярное выражение («сопоставить пары скобок») потерпит неудачу, возвращая что-то вроде:

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

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

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

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

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

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

Удачи!

2

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

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