用 gsub 替换智能代码

Replace smartcodes with gsub

我在 ruby > 3.0 上工作,我需要替换文本内容(使用智能代码的内联 html 文本)。 这段文字可以很长,例如它是这样的:“你好,{{viewer_name}}!你好吗?”

我有一个方法可以替换这些智能代码:

def populate_smartcodes(content)
  content.gsub(/\{{(.*?)\}}/).each do |value|
    smartcode = value[/#{Regexp.escape('{{')}(.*?)#{Regexp.escape('}}')}/m, 1]
    str_smartcode = "{{#{smartcode}}}"
    case smartcode
    when 'viewer_name'
      content = content.gsub(str_smartcode, viewer.name)
    when 'company_city'
      content = content.gsub(str_smartcode, company.city )
    end
    content
  end

company_cityviewer_name 是我需要向查看者提供 User::Viewer.

实例的变量

而且我有很多智能代码要替换...我认为这不是一个好方法,但它正在工作。

你能帮我提高性能吗?

编辑:

我向用户显示了 pdf 内容,他们只有 'keys' 或 'smartcode' 列表可用,这样每个人都可以根据需要更改他们的 pdf(通过内容)。我们只在数据库中保留一个包含 pdf 内容的文本。也许我们可以使用另一种策略? 现在我想要一种用另一个值替换字符串元素的方法。

这里是 运行 现有 gsub 块中的新 gsub,这不是必需的,因为 gsub 块无论如何都会用块的 return 值替换每个匹配项。只需两行代码即可实现相同的结果:

content.gsub!(/{{viewer_name}}/, viewer.name)
content.gsub!(/{{company_city}}/, company.city)

...但这仍然是对整个字符串进行两次分析。如果你事先知道所有的智能代码,你可以这样做:

def populate_smartcodes(content)
  content.gsub(/{{\w*}}/) do |smartcode|
    case smartcode
    when '{{viewer_name}}'
      viewer.name
    when '{{company_city}}'
      company.city
    else
      smartcode
    end
  end
end

这将 return 替换所有 {{viewer_name}}{{company_city}} 实例的输入字符串的副本。如果你想就地修改原始字符串,你也可以使用 gsub! 而不是 gsub,这样你就可以像 populate_smartcodes(original_string) 而不是 original_string = populate_smartcodes(original_string).

可以用更简单(更干净?)的方式从模板中提取智能代码。我们肯定知道,它们总是包含 4 个括号 - 每边 2 个。所以我们可以只取 gsub 匹配的内容,并将 2..-3 个字符作为实际的绑定名称:

content = "Hello, {{viewer_name}}! How are you?"

content.gsub!(/{{\w+?)}}/) do |matched_chunk|
  matched_chunk[2..-3] # => viewer_name
end

接下来,我们想将这些匹配的智能代码解析成有意义的东西。 而且我们希望动态地执行此操作以避免对 many-many 个单独的智能代码进行硬编码,正确

在您的示例中,您将 viewer_name 解析为 viewer.name,将 company_city 解析为 company.city - 它看起来像某种模式 <receiver>.<property>。如果是这种情况,我们可以使用这种“通用性”来动态解决问题,而无需为每个“智能代码”硬编码解决方案:

content.gsub!(/{{\w+?)}}/) do |matched_chunk|
  recv, prop = matched_chunk[2..-3].split("_", 2)
  instance_eval("#{recv}.#{prop}") # the receiver should be defined in the current scope, obviously
rescue
  matched_chunk
end

好吧,它可以工作。可能 :) 但它闻起来很臭:

  • 我将拆分限制为 2,以确保我们始终坚持 receiver.property 模式。因此,user_first_name 之类的内容将被解析为 user.first_name。但这个假设一般来说可能是错误的!它实际上可能类似于 user.first.name,甚至 foo_bar 应该被解析为 something_else - 所以这个解决方案是脆弱的 至少 .. .

  • instance_eval是一个潘多拉魔盒,我们要格外小心。理想情况下,我们应该在一个隔离的、安全的上下文中调用它(一些空白对象)

这两个问题都是可以解决的:我们可以创建一个特殊的模板构建器对象作为我们 instance_eval 的安全空白后期上下文;然后我们可以注入必要的绑定,以便 instance_eval 可以正确解析我们的智能代码(在这种情况下,我们甚至可以用更安全的魔法替换 instance_eval 或者可能根本没有魔法 - 硬编码有时不是 坏主意)等...

但是在这个兔子洞之后,我们迟早会重新创建像 liquidmustache 这样的东西,只是不那么成熟,更多 error-prone。

所以我的建议是不要玩正则表达式,首先尝试采用一些 battle-tested 模板引擎(只有当你有非常充分的理由时才回滚到自定义实现):)