From: Joseph McDonald Date: 2001-10-25T08:49:54+09:00 Subject: [ruby-talk:23239] Re: splitting a string with nested elements >> string = "start {{blah {{ }} outside\n{{ {{ }}\n}} bye" >> Is there a way to do such a thing with a regex, or do I need to resort >> to a scanning technique, keeping track of the nested elements? DT> You can do it with regexps fairly efficiently, but it's a tad DT> messy. What I do it flag matching delimiters first, using something DT> like: DT> count = "0000"; DT> 1 while @content.gsub!(/\{([^\{\}]*)\}/m) { DT> count = count.succ DT> "#{MAGIC}:#{count}:#$1#{MAGIC1}:#{count}:" DT> } DT> @content.gsub!(/#{MAGIC}/, '{') DT> @content.gsub!(/#{MAGIC1}/, '}') DT> The pattern matches paired braces that don't themselves contain DT> braces, so it works progressively from the deeper nestings out. It DT> replaces braces with two magic characters (I use ^A and ^B), followed DT> by a count. It then replaces the magic characters with braces, just DT> to I can read the string when debugging. [...] Thanks Dave, Robert, Jimmy, etc... I came up with a slightly different solution which I think should be pretty quick: create 2 arrays that have the locations of the beginning and ending delimiters, then just walk those arrays to create the ranges of balanced delimiters, then merge it with the stuff outside the delimiters. I'm sure this could be made much better, so if anyone wants to point out better ways of doing this, feel free. def asplit_text(pre_str, post_str, string) results = []; s = []; e = []; ar = [] slen = pre_str.length; elen = post_str.length pos = 0 s << pos while pos = string.index(pre_str,pos+slen) pos = 0 e << pos while pos = string.index(post_str,pos+elen) # get the ranges of the parts with delimiters start = s[0]; len = s.length-1 1.upto(len) do |x| if s[x] > e[x-1] ar << [start,e[x-1]] start = s[x] end end ar << [start,e[-1]] # catch the last one... # merge it with the parts outside the delimiters last = -elen ar.each do |range| if range[0] > last results << string[last+elen..range[0]-1] last = range[1] results << string[range[0]..last+elen-1] end end # catch the trailing string if needed if ar[-1][1]+elen-1 < string.length-1 results << string[ar[-1][1]+elen..string.length] end puts string p ar p results end asplit_text("{{", "}}", "start {{blah {{ {{ }} }} }} {{ asdf }} out {{ {{ }} }} bye") puts "--------------------" asplit_text("(", ")", "Hello ( ( World ) ) () ( ) ((())) Goodbye ()" ) =========================================================== prints: start {{blah {{ {{ }} }} }} {{ asdf }} out {{ {{ }} }} bye [[6, 26], [29, 37], [44, 53]] ["start ", "{{blah {{ {{ }} }} }}", " ", "{{ asdf }}", " out ", "{{ {{ }} }}", " bye"] -------------------- Hello ( ( World ) ) () ( ) ((())) Goodbye () [[6, 18], [20, 21], [23, 26], [28, 33], [43, 44]] ["Hello ", "( ( World ) )", " ", "()", " ", "( )", " ", "((()))", " Goodbye ", "()"] thanks, -joe