Ruby 字符串全局替换时,如何跳过不想替换的字符串

liuminhan · 2018年03月22日 · 最后由 gingerhot 回复于 2018年03月23日 · 3228 次阅读

问题描述

有个文本内容像下面这样,

关于ruby方面的问题,可以到<a href='https://ruby-china.org'>ruby-china</a>上搜索相关资料

我想全局替换 ruby 这个词,给它链接到 https://ruby-lang.org, 但是我又不想把已经有超链接包住的 ruby-china 中的 ruby 给替换了,有什么办法吗?我全局替换用的 gsub,想不到有什么好办法实现这个功能。

可以把不想替换的部分用 gsub(pattern) {|match| } 保存到一个字典里,字典的 key 自动生成的,和原文内容不会冲突并且容易识别,比如 <<<1>>>, <<<2>>>。用字典的 key 替换实际的内容。然后可以全局替换了,替换完了,再用 gsub(pattern) {|match| } 把满足字典 key 的 pattern 的地方替换回原来的内容。

先用 HTML 库解析,然后迭代节点只替换 TextNode 的内容。

doitian 回复

我去试下把 <a href='xxx'>ruby-china</a>整个替换然后保存

Rei 回复

比如说文本是这样的, <p> ruby first <a href="xxxx">ruby-china</a> test </p> , 类似于这样,这个<p>节点下的 ruby-china 应该也是属于<p>的 TextNode 的内容

liuminhan 回复

再加上判断父节点。

@doitian @Rei , 多谢,有两个思路可以去试试了

严格说处理 HTML 文档还是像 Rei 说的解析后处理比较好,但如果要求不高的话使用正则表达式会比较快的解决问题。比如你能确定要替换的词不包含在一些标签的属性之类的地方,否则干扰因素太多就不如其它方法干净利落了。

下面是使用正则替换 <a> 标签之外的所有文本中的 ruby 一词的示例:

$ irb
irb(main):001:0> str = "关于ruby方面的问题,可以到<a href='https://ruby-china.org'>ruby-china</a>上搜索ruby相关资料"
=> "关于ruby方面的问题,可以到<a href='https://ruby-china.org'>ruby-china</a>上搜索ruby相关资料"
irb(main):002:0> str.gsub(/ruby(?!(.(?!<a))*?<\/a>)/, "<a href='https://ruby-lang.org'>ruby</a>")
=> "关于<a href='https://ruby-lang.org'>ruby</a>方面的问题,可以到<a href='https://ruby-china.org'>ruby-china</a>上搜索<a href='https://ruby-lang.org'>ruby</a>相关资料"
irb(main):003:0>

updated。

gingerhot 回复

多谢,我是按照正则表达式的方式做的,

def update_content_chain
  hyper_links = {}
  self.content_with_chain = content
  content_with_chain.gsub!(%r{<a href=[\'"]?([^\'"> ]*)[\'"]?[^>]*>(.*?)<\/a>}) do |matcher|
    rand_string = SecureRandom.hex 16
    hyper_links[rand_string] = matcher
    rand_string
  end
  SiteChain.find_each do |site_chain|
    keyword = site_chain.keyword
    replace_chain = "<a href='#{site_chain.site_url}'>#{keyword}</a>"
    content_with_chain.gsub!(/#{Regexp.quote(keyword)}/, replace_chain.to_s)
  end
  hyper_links.keys.each do |key|
    content_with_chain.gsub!(/#{key}/, hyper_links[key])
  end
  save
end


gingerhot 回复
str.gsub(/(?!<a[^>]*?>)ruby(?![^<]*?<\/a>)/, "<a href='https://ruby-lang.org'>ruby</a>")

(?!<a[^>]*?>) 这里应该是 ?<!, 不过一般的正则库 look behind assertions 都不支持不明确长度的表达式。所以这种写法会有问题。

参考链接:https://stackoverflow.com/questions/9030305/regular-expression-lookbehind-doesnt-work-with-quantifiers-or

mlzhuyi 回复

多谢指出。我原本就是想写 lookahead,不是 lookbehind。但测试发现其实写在匹配前面的 lookahead 并未生效,已更新。

11 楼 已删除
liuminhan 关闭了讨论。 05月31日 14:50
需要 登录 后方可回复, 如果你还没有账号请 注册新账号