Регулярное выражение для соответствия 1 или 2 вхождениям

#regex #elixir #pcre

#регулярное выражение #elixir #pcre

Вопрос:

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

имя_книги: Разработка программного обеспечения;автор: Джон;автор: Смит; имя_книги: шаблоны дизайна; автор: Foo;автор:Bar;

Разделитель элементов является ;

За элементом book_name могут следовать два элемента author

Там может быть от 2 до 10 книг

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

Я хотел бы извлечь book_name и отдельных авторов для каждой книги.

Я попробовал регулярное выражение с .scan методом (который собирает все совпадения):

 iex> regex = ~r/book_name:(. ?;)(author:. ?;){1,2}/
iex> text = "book_name:SoftwareEngineering;author:John;author:Smith;book_name:DesignPatterns;author:Foo;author:Bar;"

iex> Regex.scan(regex, text, capture: :all_but_first)
[["SoftwareEngineering;", "author:Smith;"], ["DesignPatterns;", "author:Bar;"]]
  

Но оно неправильно собирает авторов. Оно собирает только второго автора книги.
Кто-нибудь может помочь с проблемой?

Ответ №1:

Эта часть (author:. ?;){1,2} шаблона повторяется 1-2 раза author , включая то, что следует до точки с запятой, но повторение группы захвата подобным образом даст вам только последнюю группу захвата. Эта страница может быть полезной.

Вместо использования нежадного квантификатора .*? вы могли бы сопоставить не точку с запятой, повторяющую отрицаемый символьный класс, [^;] который не соответствует точке с запятой.

Вы также можете использовать группу захвата и обратную ссылку для author . Название книги находится в группе захвата 1, имя первого автора — в группе 3 и необязательного второго автора — в группе 4.

 book_name:([^;] );(author):([^;] );(?:2:([^;] );)?
  

Это будет соответствовать

  • book_name: Совпадение буквально
  • ([^;] ); Группа 1 не соответствует, ; затем соответствует ;
  • (author): Группа 2 author
  • ([^;] ); Группа 3 не соответствует, ; затем соответствует ;
  • (?: Группа без захвата
    • 2: обратная ссылка на то, что записано в группе 2
    • ([^;] ); Группа 4 не соответствует, ; затем соответствует ;
  • )? Закройте группу без захвата и сделайте ее необязательной

демонстрационное регулярное выражение 101

Ответ №2:

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

 book_name:(. ?;)author:(. ?);(?:author:(. ?);)?
  

https://regex101.com/r/LPgzcG/1

Ответ №3:

Для этого вам не нужно регулярное выражение, вы можете использовать String.split/3 :

 defmodule Book do
  def extract(text) do
    text
    |> String.split("book_name:", trim: true)
    |> Enum.map(amp;String.split(amp;1, [":", ";"], trim: true))
    |> Enum.map(fn [title, _, author1, _, author2] -> {title, author1, author2} end)
  end
end
  

Вывод:

 iex> Book.extract(text)
[{"SoftwareEngineering", "John", "Smith"}, {"DesignPatterns", "Foo", "Bar"}]
  

Для простоты я предположил, что авторов всегда было два. Последнее перечисление можно заменить этим, которое обрабатывает случай, когда второго автора тоже нет:

 |> Enum.map(fn
  [title, _, author1] -> {title, author1, nil}
  [title, _, author1, _, author2] -> {title, author1, author2}
end)