有个文本内容像下面这样,
关于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 的地方替换回原来的内容。
比如说文本是这样的,
<p> ruby first <a href="xxxx">ruby-china</a> test </p>
,
类似于这样,这个<p>
节点下的 ruby-china 应该也是属于<p>
的 TextNode 的内容
严格说处理 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。
多谢,我是按照正则表达式的方式做的,
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
str.gsub(/(?!<a[^>]*?>)ruby(?![^<]*?<\/a>)/, "<a href='https://ruby-lang.org'>ruby</a>")
(?!<a[^>]*?>)
这里应该是 ?<!
, 不过一般的正则库 look behind assertions 都不支持不明确长度的表达式。所以这种写法会有问题。
多谢指出。我原本就是想写 lookahead,不是 lookbehind。但测试发现其实写在匹配前面的 lookahead 并未生效,已更新。