【问题标题】:combine regex in ruby在红宝石中结合正则表达式
【发布时间】:2009-03-17 02:47:29
【问题描述】:

鉴于此文本:

/* F004 (0309)00 */ /* 字段 1 */ /* 字段 2 */ /* F004 (0409)00 */ /* 字段 1 */ /* 字段 2 */

如何将它解析成这个数组:
[
["F004"],["0309"],["/* field 1 */\n/* field 2 */"],
["F004"],["0409"],["/* field 1 */\n/* field 2 */"]
]

我得到了解析前两项的代码:

form = /\/\*\s+(\w+)\s+\((\d{4})\)[0]{2}\s+\*\//m
text.scan(form)

[
["F004"],["0309"],
["F004"],["0409"]
]

这是我尝试解析所有三个并失败的代码,并出现无效的正则表达式错误:

form = /\/\*\s+(\w+)\s+\((\d{4})\)[0]{2}\s+\*\//m
form_and_fields = /#{form}(.[^#{form}]+)/m
text.scan(form_and_fields)


编辑: 多亏了rampion和singpolyma,这最终对我有用:
form = /
  \/\*\s+(\w+)\s+\((\d+)\)\d+\s+\*\/    #formId & edDate
  (.+?)                                 #fieldText
  (?=\/\*\s+\w+\s+\(\d+\)\d+\s+\*\/|\Z) #stop at beginning of next form
                                        # or the end of the string
/mx
text.scan(form)

【问题讨论】:

    标签: ruby regex parsing text


    【解决方案1】:

    您似乎误解了字符类(例如 [a-f0-9][^aeiouy])的工作原理。 /[^abcd]/ 不会否定模式abcd,它表示“匹配任何不是'a''b''c''d' 的字符”。

    如果要匹配模式的否定,请使用/(?!pattern)/ 构造。这是一个零宽度匹配 - 这意味着它实际上不匹配任何字符,它匹配一个位置。 类似于/^//$/ 匹配字符串的开头和结尾,或者/\b/ 匹配单词的边界。例如:/(?!xx)/ 匹配模式“xx”没有开始的每个位置。

    一般来说,在你使用模式否定之后,你需要匹配一些字符才能在字符串中向前移动。

    所以要使用你的模式:

    form = /\/\*\s+(\w+)\s+\((\d{4})\)[0]{2}\s+\*\//m
    form_and_fields = /#{form}((?:(?!#{form}).)+)/m
    text.scan(form_and_fields)
    

    由内而外(我将使用(?#comments)

    • (?!#{form}) 否定您的原始模式,因此它匹配您的原始模式无法开始的任何位置。
    • (?:(?!#{form}).)+ 表示匹配一个字符后,然后再试一次,尽可能多次,但至少一次。 (?:(?#whatever)) 是非捕获括号 - 适合分组。

    在 irb 中,这给出:

    irb> text.scan(form_and_fields)
    => [["F004", "0309", "  \n    /* field 1 */  \n    /* field 2 */  \n    ", nil, nil], ["F004", "0409", "  \n    /* field 1 */  \n    /* field 2 */  \n", nil, nil]]
    

    额外的nils 来自form 中的捕获组,这些组在否定模式(?!#{form}) 中使用,因此在成功匹配时不会捕获任何内容。

    这可以清理一些:

    form_and_fields = /#{form}\s*(.+?)\s*(?:(?=#{form})|\Z)/m
    text.scan(form_and_fields)
    

    现在,我们使用零宽度正向预测 (?=#{form}) 来匹配下一次出现 form 的位置,而不是零宽度的负前瞻。因此,在这个正则表达式中,我们匹配所有内容,直到下一次出现 form没有包括我们匹配中的下一次出现)。这让我们可以去掉字段周围的一些空白。我们还必须检查我们击中字符串末尾的情况 - /\Z/,因为这也可能发生。

    在 irb 中:

    irb> text.scan(form_and_fields)
    => [["F004", "0309", "/* field 1 */  \n    /* field 2 */", "F004", "0409"], ["F004", "0409", "/* field 1 */  \n    /* field 2 */", nil, nil]]
    

    现在请注意,最后两个字段是第一次填充的 - b/c 零宽度正前瞻中的捕获括号匹配了某些内容,即使在此过程中它没有被标记为“已使用” - 这就是为什么该位可以第二次重新匹配。

    【讨论】:

    • 很好地解释了为什么他的方式失败了。不过,我这样做对于这个(和大多数)场景来说,与整个否定模式的事情都太过分了。
    • 谢谢,你很好地解释了这一点。我确实需要使用否定,因为表单之间的文本不统一,示例过于简化。
    【解决方案2】:
    a.scan(/\/\*\s+(\S+)\s+\((\d+)\)\d+\s+\*\/\s+(\/\*.+\*\/\s+\n\s+\/\*.+\*\/)/)
    => [["F004", "0309", "/* field 1 */  \n    /* field 2 */"], ["F004", "0409", "/* field 1 */  \n    /* field 2 */"]]
    

    【讨论】:

      【解决方案3】:

      不管怎样,如果您扩展代码并使用多个更简单的正则表达式,您可能会发现您的代码最终变得更具可读性。例如(未经测试):

        transformed_lines = []
      
        text.each_line do |line|
          if line =~ /(\w|\d)+\s\(\d+)\)/
            transformed_lines << [ $1, $2, "" ]
          else
            transformed_lines.last.last << line.strip
          end
        end
      

      更好的是,考虑创建一个类或简单的结构来存储结果,以便更清楚地知道去哪里:

        transformed_lines << OpenStruct.new :thingy_one => $1, :thingy_two => $2, :fields => ""
        ...
        transformed_lines.last.fields << line.strip
      

      【讨论】:

        猜你喜欢
        • 2016-12-14
        • 2019-04-22
        • 2011-10-06
        • 1970-01-01
        • 2022-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-05
        相关资源
        最近更新 更多