iCalendar的正则表达式解析(Ruby正则表达式)

4
我将使用正则表达式来解析iCalendar(RFC2445)输入。下面是一个[简化版]的示例,显示了输入的样子:
BEGIN:VEVENT
abc:123
def:456
END:VEVENT
BEGIN:VEVENT
ghi:789
END:VEVENT

我希望获得一个匹配项的数组:每个"外层"匹配项都是每个VEVENT块,而内部匹配项则是每个field:value对。

我尝试了这种变体:

BEGIN:VEVENT\n((?<field>(?<name>\S+):\s*(?<value>\S+)\n)+?)END:VEVENT

但是考虑到上面的输入内容,结果似乎只有每个匹配的VEVENT一个字段,尽管在捕获组上使用了+?:

**Match 1**
field   def:456
name    def
value   456

**Match 2**
field   ghi:789
name    ghi
value   789

在第一次匹配中,我期望有两个字段:abc:123和def:456的匹配...
我相信这是新手错误(因为当涉及到正则表达式时,我似乎永远是一个新手...)- 但也许你可以指点我正确的方向?
谢谢!

为什么你要自己解析,而不使用像 https://github.com/sdague/icalendar#readme 这样的 gem? - dpassage
很好的问题:事实证明我(目前)正在使用ri_cal(另一个同类中非常棒的宝石),但是:1)它会构建所有事件的完整内存表示,这是一个巨大的东西 - 我只需要解析单个项目,2)我的输入文件经常是虚假的,宝石往往难以处理。 但确实,这是我的当前方法,所以你说得对。 - Eric
5个回答

2
你需要将你的正则表达式拆分成一个匹配VEVENT和一个匹配名称/值对的表达式。然后,你可以使用嵌套的scan来查找所有出现的情况,例如:
str.scan(/BEGIN:VEVENT((?<vevent>.+?))END:VEVENT/m) do
  $~[:vevent].scan(/(?<field>(?<name>\S+?):\s*(?<value>\S+?))/) do
    p $~[:field], $~[:name], $~[:value]
  end
end

其中str是您的输入。 这将输出:

"abc:1"
"abc"
"1"
"def:4"
"def"
"4"
"ghi:7"
"ghi"
"7"

如果你想让代码更易读,我建议你 require 'english' 并将 $~ 替换为 $LAST_MATCH_INFO


这是我采取的方向,似乎运行良好,并且相当自我记录,这比我之前尝试构建的全能正则表达式要好得多! - Eric

2

谢谢,事实上,我已经在使用一个iCalendar解析器了 - 但出于各种原因(包括正则表达式的好奇心),我仍然很想知道原始帖子的答案。 - Eric

1
您需要一个嵌套的scan
string.scan(/^BEGIN:VEVENT\n(.*?)\nEND:VEVENT$/m).each.with_index do |item, i|
  puts
  puts "**Match #{i+1}**"
  item.first.scan(/^(.*?):(.*)$/) do |k, v|
    puts "field".ljust(7)+"#{k}:#{v}"
    puts "name".ljust(7)+"#{k}"
    puts "value".ljust(7)+"#{v}"
  end
end

将会给予:

**Match 1**
field   abc:123
name    abc
value   123
field   def:456
name    def
value   456

**Match 2**
field   ghi:789
name    ghi
value   789

0
Ruby有一个很少使用的方法叫做slice_before,非常适合这个需求:
'BEGIN:VEVENT
abc:123
def:456
END:VEVENT
BEGIN:VEVENT
ghi:789
END:VEVENT'.split("\n").slice_before(/^BEGIN:VEVENT/).to_a

结果为:

[["BEGIN:VEVENT", "abc:123", "def:456", "END:VEVENT"],
 ["BEGIN:VEVENT", "ghi:789", "END:VEVENT"]]    

从那里开始,只需轻松获取内部数组元素:

'BEGIN:VEVENT
abc:123
def:456
END:VEVENT
BEGIN:VEVENT
ghi:789
END:VEVENT'.split("\n").slice_before(/^BEGIN:VEVENT/).map{ |a| a[1 .. -2] }

这是什么:

[["abc:123", "def:456"], ["ghi:789"]]

而且,使用mapsplit(':')很容易将每个结果字符串分解。

不要被正则表达式的诱惑所迷惑,试图做所有事情。它们在特定场合非常强大和方便,但通常有更简单、更易于维护的解决方案。


0

我认为问题在于 Ruby 的 MatchData 对象,它是正则表达式返回结果的载体,没有提供多个具有相同名称的值的任何规定。因此,您的第二个匹配会覆盖第一个匹配。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接