我有以下结构的文字:
BOOK_NAME:软件工程;作者:John;作者:史密斯; BOOK_NAME:DesignPatterns;作者:富;作者:酒吧;
元素分隔符是;
两个作者元素可以遵循book_name元素
可能有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;"]]
但它没有正确地收集作者。它只收集该书的第二作者。任何人都可以帮助解决这个问题吗?
在许多引擎中,包括Elixir,您不能重复这样的多个捕获组并获得每个重复组的结果 - 您将只获得任何给定重复捕获组的最后结果。而是单独写出每个可能的组,然后过滤掉空匹配:
book_name:(.+?;)author:(.+?);(?:author:(.+?);)?
你不需要正则表达式,你可以使用String.split/3
:
defmodule Book do
def extract(text) do
text
|> String.split("book_name:", trim: true)
|> Enum.map(&String.split(&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可以替换为这个,它处理没有第二个作者的情况:
|> Enum.map(fn
[title, _, author1] -> {title, author1, nil}
[title, _, author1, _, author2] -> {title, author1, author2}
end)
这部分(author:.+?;){1,2}
模式重复1-2次author
包括跟随分号但是重复捕获组这样只会给你最后一个捕获组。 This page可能会有所帮助。
而不是使用非贪婪的量词.*?
,你可以匹配不是分号重复一个与分号不匹配的否定字符类[^;]+
。
您也可以使用author
的捕获组和反向引用。该书的名称在于捕获组1,组3中第一作者的名称和组4中的可选第二作者。
book_name:([^;]+);(author):([^;]+);(?:\2:([^;]+);)?
这将匹配
book_name:
字面意思匹配([^;]+);
第1组匹配而不是;
然后匹配;
(author):
Group 2 author
([^;]+);
Group 3匹配而不是;
然后匹配;
(?:
非捕获组
\2:
反对第2组中捕获的内容
([^;]+);
Group 4匹配而不是;
然后匹配;
)?
关闭非捕获组并使其成为可选项