用 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_city
和 viewer_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 或者可能根本没有魔法 - 硬编码有时不是 那 坏主意)等...
但是在这个兔子洞之后,我们迟早会重新创建像 liquid
或 mustache
这样的东西,只是不那么成熟,更多 error-prone。
所以我的建议是不要玩正则表达式,首先尝试采用一些 battle-tested 模板引擎(只有当你有非常充分的理由时才回滚到自定义实现):)
我在 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_city
和 viewer_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 或者可能根本没有魔法 - 硬编码有时不是 那 坏主意)等...
但是在这个兔子洞之后,我们迟早会重新创建像 liquid
或 mustache
这样的东西,只是不那么成熟,更多 error-prone。
所以我的建议是不要玩正则表达式,首先尝试采用一些 battle-tested 模板引擎(只有当你有非常充分的理由时才回滚到自定义实现):)