【问题标题】:Replacing partial regex matches in place with Ruby用 Ruby 替换部分正则表达式匹配
【发布时间】:2011-12-11 19:56:59
【问题描述】:

我想转换下面的文字

This is a ![foto](foto.jpeg), here is another ![foto](foto.png)

进入

This is a ![foto](/folder1/foto.jpeg), here is another ![foto](/folder2/foto.png)

换句话说,我想找到括在括号中的所有图像路径(文本采用 Markdown 语法)并将它们替换为其他路径。包含新路径的字符串由单独的real_path 函数返回。

我想在其块版本中使用String#gsub 来执行此操作。目前我的代码如下所示:

re = /!\[.*?\]\((.*?)\)/

rel_content = content.gsub(re) do |path|
    real_path(path)
end

这个正则表达式的问题是它会匹配![foto](foto.jpeg) 而不仅仅是foto.jpeg。我还尝试了其他正则表达式,例如(?>\!\[.*?\]\()(.*?)(?>\)),但无济于事。

我目前的解决方法是拆分路径并稍后重新组装。

是否有一个 Ruby 正则表达式只匹配括号内的路径而不匹配所有上下文所需的字符?

答案后更新:这里的主要问题是 Ruby 的正则表达式无法指定零宽度的后视。最通用的解决方案是将正则表达式之前的部分和真正匹配部分之后的部分分组,即/(pre)(matching-part)(post)/,然后重构完整的字符串。

在这种情况下,解决方案是

re = /(!\[.*?\]\()(.*?)(\))/

rel_content = content.gsub(re) do
    $1 + real_path($2) + $3
end

【问题讨论】:

    标签: ruby regex markdown replace


    【解决方案1】:

    快速解决方案(根据需要进行调整):

    s = 'This is a ![foto](foto.jpeg)'
    
    s.sub!(/!(\[.*?\])\((.*?)\)/, '\1(/folder1/\2)' )
    
    p s  # This is a [foto](/folder1/foto.jpeg)
    

    【讨论】:

    • 也许最好的办法是,正如您所建议的,将真实比赛前后的部分保存在不同的组中,并使用$1 + real_path($2) + $3 重建最终字符串。
    【解决方案2】:

    您始终可以分两步完成 - 首先提取整个图像表达式,然后替换链接:

    str = "This is a ![foto](foto.jpeg), here is another ![foto](foto.png)"
    
    str.gsub(/\!\[[^\]]*\]\(([^)]*)\)/) do |image|
      image.gsub(/(?<=\()(.*)(?=\))/) do |link|
        "/a/new/path/" + link
      end
    end
    
    #=> "This is a ![foto](/a/new/path/foto.jpeg), here is another ![foto](/a/new/path/foto.png)"
    

    我稍微更改了第一个正则表达式,但您可以使用以前的正则表达式来代替它。 image 是像![foto](foto.jpeg) 这样的图像表达式,而link 是像foto.jpeg 这样的路径。

    [编辑] 澄清:Ruby 确实有后视功能(我的回答中使用了它们):

    您可以创建lookbehinds,其中(?&lt;=regex) 表示肯定,(?&lt;!regex) 表示否定,其中regex 是符合以下条件的任意正则表达式。由于正则表达式实现的限制,后视中的正则表达式表达式必须是固定宽度,这意味着它们不能包含具有未知重复次数或具有不同宽度选择的交替的表达式。如果你尝试这样做,你会得到一个错误。 (但该限制不适用于前瞻)。

    在您的情况下,[foto] 部分的宽度是可变的(foto 可以是任何字符串),因此由于上述原因,它无法进行后视。然而,lookbehind 正是我们所需要的,因为它是一个零宽度匹配,我们在第二个正则表达式中利用了这一点,它只需要担心(固定长度)强制开括号。

    显然你可以从这里输入real_path,但我只是想要一个可测试的例子。

    我认为这种方式比通过匹配组变量重构字符串更灵活,更易读

    【讨论】:

    • 这就是我现在正在做的事情。我希望有一些我不知道的语法可以在 Ruby 的 Regexps 中创建后视:(.
    • 你可以在 ruby​​ 中做lookbehinds,但是有一个条件。更新答案以更好地解释
    【解决方案3】:

    在您的块中,使用$1 访问第一个捕获组($2 用于第二个,依此类推)。

    来自文档:

    在块形式中,当前匹配字符串作为参数传入,$1、$2、$`、$&、$'等变量会被适当设置。块返回的值将被替换为每次调用的匹配项。

    【讨论】:

      【解决方案4】:

      附带说明,有些人认为 '\1' 不适合匹配未确认字符数的情况。比如要匹配修改中间的内容,怎么保护两边的字符呢?

      这很容易。用括号括住其他东西。

      例如,我希望将a-ruby-porgramming-book-531070.png 替换为a-ruby-porgramming-book.png。删除最后一个“-”和最后一个“.”之间的上下文。

      我可以使用/.*(-.*?)\./ 匹配-531070。现在我应该如何更换它?注意 其他所有内容都没有明确的格式。

      答案是用括号括住其他东西,然后保护它们:

      "a-ruby-porgramming-book-531070.png".sub(/(.*)(-.*?)\./, '\1.') 
      # => "a-ruby-porgramming-book.png"
      

      如果你想在匹配的内容之前添加一些东西,你可以使用:

      "a-ruby-porgramming-book-531070.png".sub(/(.*)(-.*?)\./, '\1-2019\2.')
      # => "a-ruby-porgramming-book-2019-531070.png"
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-29
        • 1970-01-01
        • 2012-05-16
        • 1970-01-01
        • 1970-01-01
        • 2015-11-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多