1

I'm trying to write a regular expression to replace <%= Name %> with "Some Person".

I'm using a regex because I want to modify it so that I don't have to worry about the spaces between = and Name as well as the E in Name and the %>

I tried:

body = %q(
  Hello <%= Name %>,
  This is a test. hello test
  some more stuff here 
  and here.

  <%= Name %>
)

parsed_body = body.gsub(/\A<%= Name %>\Z/, "Some person")
puts parsed_body

When parsed_body is printed out, the string is unchanged. What is wrong with my regex?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Rhs
  • 3,188
  • 12
  • 46
  • 84
  • 1
    Why reinvent the wheel when you can do `require "erb"`? – Jordan Running May 30 '17 at 18:19
  • This sounds like an "[XY Problem](https://meta.stackexchange.com/q/66377/153968)". Using regex isn't the right way to process templates. If you need to modify them, consider having multiple, smaller, templates ("partials") that you conditionally include. – the Tin Man May 30 '17 at 18:47

3 Answers3

2

In your Regex, you have added the \A and \z anchors. These ensure that your regex only matches, if the string only contains exactly <%= Name %> with nothing before or after.

To match the your pattern anywhere in the string, you can simply remove the anchors:

parsed_body = body.gsub(/<%= Name %>/, "Some person")
Holger Just
  • 52,918
  • 14
  • 115
  • 123
  • 2
    That said, the syntax you used there looks like an ERB template. Instead of using a regex, you might be much better of when using ERB to render your string. – Holger Just May 30 '17 at 18:14
  • 1
    The problem with this approach is a separate `gsub` has to be written for every possible target and substitution. That results in a lot of repeated code which then violates the DRY principle. – the Tin Man May 30 '17 at 19:34
1

It looks like you're trying to write your own template parser, which is asking for a lot more trouble that it's worth considering those already exist.

However, this is the basic idea for such a thing:

erb = <<EOT
Owner: <%= name %>
Location: <%= address %>
EOT

FIELD_DATA = {
  name: 'Olive Oyl',
  address: '5 Sweethaven Village'
}

FIELD_RE = Regexp.union(FIELD_DATA.keys.map(&:to_s)).source # => "name|address"

puts erb.gsub(/<%=\s+(#{FIELD_RE})\s+%>/) { |k|
  k # => "<%= name %>", "<%= address %>"
  k[/\s+(\S+)\s+/, 1]  # => "name",        "address"
  FIELD_DATA[k[/\s+(\S+)\s+/, 1].to_sym]  # => "Olive Oyl",   "5 Sweethaven Village"
}

Which, when run, outputs:

Owner: Olive Oyl
Location: 5 Sweethaven Village

This works because gsub can take a regular expression and a block. For every match of the expression it passes in the match to the block, which is then used to return the actual value being substituted in.

If you have a lot of target values, rather than use Regexp.union, instead use the RegexpTrie gem. See "Is there an efficient way to perform hundreds of text substitutions in Ruby?" for more information.

Again, template parsers exist, they've been around a long time, they're very well tested, and they handle edge cases you haven't thought about, so don't write a new partially-implemented one, instead reuse an existing one.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
1

Just another option considering what I am assuming you are trying to accomplish

tag_pattern = /(?<open_tag><%=\s*)(?<key>(\w+))(?<close_tag>\s*%>)/
body = <<-B
   Hello, 
    My name is <%= Name %> and I know some things. Just because I am a 
    <%= Occupation %> doesn't mean I know everything, but I sure can 
    <%=Duty%> just as well as anyone else.
    Also please don't replace <%= This %>

  Thank you,
    <%= Name %>
B
dict = {"Name" => "engineersmnky", "Occupation" => "Monkey", "Duty" => "drive a train"} 

 body.gsub(tag_pattern) {|m| dict[$2] || m }
 #=>  Hello, 
 #   My name is engineersmnky and I know some things. Just because I am a 
 #   Monkey doesn't mean I know everything, but I sure can 
 #   drive a train just as well as anyone else.
 #   Also please don't replace <%= This %>
 #
 # Thank you,
 #   engineersmnky

In this case I used a dictionary of the anticipated portions of the "erb" to be replaced and used the block style of String#gsub to handle the replacements where $2 is the named capture key. When there is not a matching key it just leaves the match untouched e.g. "Also please don't replace <%= This %>"

You could implement this with any pattern you choose but if you are going to use "erb" style lines maybe try leveraging erb other wise the same will work below:

tag_pattern = (?<open_tag>{!!\s*)(?<key>(\w+))(?<close_tag>\s*!\?})
body = <<-B Hello, 
     My name is {!! Name !?} and I know some things. Just because I am a 
     {!! Occupation !?} doesn't mean I know everything, but I sure can 
     {!!Duty !?} just as well as anyone else.

    Thank you,
       {!! Name !?}
B

As long as you define tag_pattern correctly the replacement is fairly simple. Rubular Example

engineersmnky
  • 25,495
  • 2
  • 36
  • 52