Splitting a string with escapable separator?

I'm trying to come up with an *elegant* way to split a string into an
array at a separator with the additional feature that the separators
can be escaped. It should work like this

  "Hello\, World,Hi".split_escapable(',' '\')
  # => ["Hello, World", "Hi"]

Through a number of permutations with regexps, scan and the rest of the
family, I was unable to find a solution. I could parse the given string
myself, going though it character by character, but I'd prefer a less
pedestrian approach.

Michael

···

--
Michael Schuerig All good people read good books
mailto:michael@schuerig.de Now your conscience is clear
http://www.schuerig.de/michael/ --Tanita Tikaram, Twist In My Sobriety

Your above example is missing a couple of \, but I assume I know what you meant.

Is the following elegant or not?

class String
     def split_escapable( separator, escape_char=nil )
         results =
         re = /(.+?)(?:#{escape_char ? "([^\\#{escape_char}])" : ''}#{separator}|$)/
         self.scan( re ){ |str,last_char|
             results << str + last_char.to_s
         }
         results
     end
end

p "Hello\\, World,Hi".split_escapable( ',', '\\' )
#=> ["Hello\\, World", "Hi"]

Note that the above does not account for the case of:
Hello \\,World
(where an escaped backslash is intended to end the first entry)
but if that was important, that's just a matter of a bit of odd/even backslash counting.

Something like (untested):
re = /(.+?)(?:#{escape_char ? "([^\\#{escape_char}](\\#{escape_char}\\#{escape_char})*)" : ''}#{separator}|$)/

···

On Sep 27, 2005, at 5:46 PM, Michael Schuerig wrote:

I'm trying to come up with an *elegant* way to split a string into an
array at a separator with the additional feature that the separators
can be escaped. It should work like this

  "Hello\, World,Hi".split_escapable(',' '\')
  # => ["Hello, World", "Hi"]

Through a number of permutations with regexps, scan and the rest of the
family, I was unable to find a solution.

Michael Schuerig wrote:

I'm trying to come up with an *elegant* way to split a string into an
array at a separator with the additional feature that the separators
can be escaped. It should work like this

  "Hello\, World,Hi".split_escapable(',' '\')
  # => ["Hello, World", "Hi"]

Through a number of permutations with regexps, scan and the rest of the
family, I was unable to find a solution. I could parse the given string
myself, going though it character by character, but I'd prefer a less
pedestrian approach.

class String
  def split_escapable( splitter, escaper )
    escaper = escaper*2 if escaper=='\\'
    re = %r{ \G
             # Make sure at least 1 character remains.
             (?= . )
             (
               (?:
                 [^#{ splitter }#{ escaper }]
                 >
                 (?: #{ escaper } . )
               ) *
             )
             (?:
               #{ splitter }
               >
               \Z
             )

           }xm
    scan( re ).map{|x| x.first.gsub( /#{escaper}(.)/, '\1' ) }
  end
end

s = <<HERE
Hello@, World!,Hi.
Alarm rings@, lights flash.,One escaper @@
HERE
s.split("\n").each {|x|a=x.split_escapable(',','@');p a; puts a}
puts "----"

s = <<'HERE'
Hello\, World!,Hi.
Alarm rings\, lights flash.,One escaper \\
HERE
s.split("\n").each {|x|a=x.split_escapable(',','\\');p a; puts a}

I'm trying to come up with an *elegant* way to split a string into an
array at a separator with the additional feature that the separators
can be escaped. It should work like this

  "Hello\, World,Hi".split_escapable(',' '\')
  # => ["Hello, World", "Hi"]

Through a number of permutations with regexps, scan and the rest of the
family, I was unable to find a solution. I could parse the given string
myself, going though it character by character, but I'd prefer a less
pedestrian approach.

Michael

With the new Regex engine in cvs ruby you can use a negative lookback
assertion in your split:

s = "Hello\\, World, Hi"

=> "Hello\\, World, Hi"

s.split /(?<!\\),/

=> ["Hello\\, World", " Hi"]

$ ruby --v
ruby 1.9.0 (2005-09-08) [i686-linux]

Regards,
Jason
http://blog.casey-sweat.us/

···

On 9/27/05, Michael Schuerig <michael@schuerig.de> wrote:

None of the offerings so far had support for the second argument for split.
The following is probably quite efficent if the chance of an occurrence of
an escaped character is low.
(I haven't benchmarked anything, though). Also, it's not suitable for binary
strings.

class String
def split_escapable(separator, escape_char, *args)
istr = dup
impossible = "\x01"
replace = "#{escape_char}#{separator}"
changed = istr.gsub!(replace, impossible)
fields = istr.split(separator, *args)
if changed
fields.each do |f|
f.gsub!(impossible, separator)
end
end
fields
end
end
a = "Hello\\, World,Hi"
puts a.split_escapable( ',', '\\' )

Cheers,

Han Holl

···

On 9/28/05, Michael Schuerig <michael@schuerig.de> wrote:

I'm trying to come up with an *elegant* way to split a string into an
array at a separator with the additional feature that the separators
can be escaped. It should work like this

"Hello\, World,Hi".split_escapable(',' '\')
# => ["Hello, World", "Hi"]

Without 1.9 lookback, you could try:
'Hello\,World,Hi'.scan(/(?:\\,|[^,])+/).map {|e| e.tr('\\','')}
=> ["Hello,World", "Hi"]

Michael Schuerig wrote:

···

I'm trying to come up with an *elegant* way to split a string into an
array at a separator with the additional feature that the separators
can be escaped. It should work like this

  "Hello\, World,Hi".split_escapable(',' '\')
  # => ["Hello, World", "Hi"]

Through a number of permutations with regexps, scan and the rest of the
family, I was unable to find a solution. I could parse the given string
myself, going though it character by character, but I'd prefer a less
pedestrian approach.

Michael

--
Michael Schuerig All good people read good books
mailto:michael@schuerig.de Now your conscience is clear
Michael Schürig | Sentenced to making sense --Tanita Tikaram, Twist In My Sobriety

Jason Sweat wrote:

···

On 9/27/05, Michael Schuerig <michael@schuerig.de> wrote:

I'm trying to come up with an *elegant* way to split a string into an
array at a separator with the additional feature that the separators
can be escaped. It should work like this

  "Hello\, World,Hi".split_escapable(',' '\')
  # => ["Hello, World", "Hi"]

Through a number of permutations with regexps, scan and the rest of
the family, I was unable to find a solution. I could parse the given
string myself, going though it character by character, but I'd prefer
a less pedestrian approach.

Michael

With the new Regex engine in cvs ruby you can use a negative lookback
assertion in your split:

s = "Hello\\, World, Hi"

=> "Hello\\, World, Hi"

s.split /(?<!\\),/

=> ["Hello\\, World", " Hi"]

That must be the most elegant solution. Unfortunately I can't use cvs
ruby and can't wait for it either.

Michael

--
Michael Schuerig Airtight arguments have
mailto:michael@schuerig.de vacuous conclusions.
Michael Schürig | Sentenced to making sense --A.O. Rorty, Explaining Emotions

William James wrote:

Michael Schuerig wrote:

I'm trying to come up with an *elegant* way to split a string into an
array at a separator with the additional feature that the separators
can be escaped.

[snip]

class String
  def split_escapable( splitter, escaper )

[snip]

  end
end

Thanks, that appears to work indeed, although I can't claim to
understand how or why.

Michae.

···

--
Michael Schuerig The more it stays the same,
mailto:michael@schuerig.de The less it changes!
Michael Schürig | Sentenced to making sense --Spinal Tap, The Majesty of Rock

Gavin Kistner wrote:
[snip]

Note that the above does not account for the case of:
Hello \\,World
(where an escaped backslash is intended to end the first entry)
but if that was important, that's just a matter of a bit of odd/even
backslash counting.

That's a thing I'd need. Opportunistically, I'll go with William's
suggestion from a sibling post.

Something like (untested):
re = /(.+?)(?:#{escape_char ? "([^\\#{escape_char}](\\#{escape_char}\
\#{escape_char})*)" : ''}#{separator}|$)/

Thanks.

Michael

···

--
Michael Schuerig The more it stays the same,
mailto:michael@schuerig.de The less it changes!
Michael Schürig | Sentenced to making sense --Spinal Tap, The Majesty of Rock