#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):
Группа 2author
([^;] );
Группа 3 не соответствует,;
затем соответствует;
(?:
Группа без захвата2:
обратная ссылка на то, что записано в группе 2([^;] );
Группа 4 не соответствует,;
затем соответствует;
)?
Закройте группу без захвата и сделайте ее необязательной
Ответ №2:
Во многих движках, включая Elixir, вы не можете повторять несколько групп захвата подобным образом и получать результат для каждой повторяющейся группы — вы получите только последний результат для любой заданной повторяющейся группы захвата. Вместо этого выпишите каждую возможную группу по отдельности, а затем отфильтруйте пустые совпадения:
book_name:(. ?;)author:(. ?);(?:author:(. ?);)?
Ответ №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)