【问题标题】:Regex parsing of iCalendar (Ruby regex)iCalendar 的正则表达式解析(Ruby 正则表达式)
【发布时间】:2012-10-17 05:08:09
【问题描述】:

我正在尝试使用正则表达式解析 iCalendar (RFC2445) 输入。

下面是输入的 [简化] 示例:

BEGIN:VEVENT
abc:123
def:456
END:VEVENT
BEGIN:VEVENT
ghi:789
END:VEVENT

我想要一个匹配数组:“外部”匹配是每个 VEVENT 块,内部匹配是每个字段:值对。

我已经尝试过这种变体:

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 匹配...

我确定这是一个新手错误(因为在正则表达式方面我似乎永远是一个新手......) - 但也许你可以指出我正确的方向?

谢谢!

【问题讨论】:

  • 你为什么要自己解析这个而不是使用像github.com/sdague/icalendar#readme这样的gem?
  • 很好的问题:事实证明我(目前)正在使用 ri_cal(另一个伟大的宝石)但是:1)它构建了所有事件的完整内存表示,这是一个巨大的东西 - 我只需要解析单个项目,并且 2)我的输入文件通常是伪造的,而 gems 往往难以处理这些。但事实上,这是我目前的方法,所以你是正确的。

标签: ruby regex icalendar rfc2445 rfc5545


【解决方案1】:

使用icalendar gem。 请参阅Parsing iCalendars 部分了解更多信息。

【讨论】:

  • 谢谢,确实,我已经在使用 iCalendar 解析器之一了 - 但出于各种原因(包括对正则表达式的好奇),我仍然很想知道原始帖子的答案。跨度>
【解决方案2】:

我认为问题在于 ruby​​ MatchData 对象,即正则表达式返回其结果的对象,没有任何规定可以提供多个具有相同名称的值。所以你的第二场比赛会覆盖第一场比赛。

【讨论】:

    【解决方案3】:

    你需要一个嵌套的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
    

    【讨论】:

      【解决方案4】:

      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(':') 分解每个结果字符串是微不足道的。

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

      【讨论】:

        【解决方案5】:

        您需要将您的正则表达式拆分为一个匹配 VEVENT 和一个匹配名称/值对。然后您可以使用嵌套的scan 来查找所有出现的事件,例如。 G。

        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

        【讨论】:

        • 这是我的方向,它似乎运作良好,并且是合理的自我记录,这比我之前尝试构建的多合一正则表达式要多!
        猜你喜欢
        • 2013-11-01
        • 1970-01-01
        • 2011-08-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多