TheThirdMan TheThirdMan - 4 months ago 14
Ruby Question

How do I regex-match an unknown number of repeating elements?

I'm trying to write a Ruby script that replaces all rem values in a CSS file with their px equivalents. This would be an example CSS file:

body{font-size:1.6rem;margin:4rem 7rem;}


The MatchData I'd like to get would be:

# Match 1 Match 2
# 1. font-size 1. margin
# 2. 1.6 2. 4
# 3. 7


However I'm entirely clueless as to how to get multiple and different MatchData results. The RegEx that got me closest is this (you can also take a look at it at Rubular):

/([^}{;]+):\s*([0-9.]+?)rem(?=\s*;|\s*})/i


This will match single instances of value declarations (so it will properly return the desired Match 1 result), but entirely disregards multiples.

I also tried something along the lines of
([0-9.]+?rem\s*)+
, but that didn't return the desired result either, and doesn't feel like I'm on the right track, as it won't return multiple result data sets.




EDIT After the suggestions in the answers, I ended up solving the problem like this:

# search for any declarations that contain rem unit values and modify blockwise
@output.gsub!(/([^ }{;]+):\s*([^;]*[0-9.]rem+[^;]*)(?=\s*;|\s*})/i) do |match|
# search for any single rem value
string = match.gsub(/([0-9.]+)rem/i) do |value|
# convert the rem value to px by multiplying by 10 (this is not universal!)
value = sprintf('%g', Regexp.last_match[1].to_f * 10).to_s + 'px'
end
string += ';' + match # append the original match result to the replacement
match = string # overwrite the matched result
end

Answer

You can't capture a dynamic number of match groups (at least not in ruby).

Instead you could do either one of the following:

  1. Capture the whole value and split on space
  2. Use multilevel matching to capture first the whole key/value pair and secondly match the value. You can use blocks on the match method in ruby.