Regex to split string excluding delimiters between escapable quotes

Viewed 42

I need to be able space separate a string unless the space is contained within escapable quotes. In other words spam spam spam "and \"eggs" should return spam, spam, spam and and "eggs. I intend to do this using the re.split method in python where you identify the characters to split on using regex.

I found this which finds everything between escapable quotes:

((?<![\\])['"])((?:.(?!(?<![\\])\1))*.?)\1

from: https://www.metaltoad.com/blog/regex-quoted-string-escapable-quotes and this which splits by character unless between quotes:

\s(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)

from: https://stackabuse.com/regex-splitting-by-character-unless-in-quotes/. This finds all spaces with an even number of doubles quotes between the space and the end of the line.

I'm struggling join those two solution together.

For ref reference I found this I found this super-useful regex cheat sheet: https://medium.com/factory-mind/regex-tutorial-a-simple-cheatsheet-by-examples-649dc1c3f285

I also found https://regex101.com/ extremely useful: allows you to test regex

1 Answers

Finally managed it:

\s(?=(?:(?:\\\"|[^\"])*(?<!\\)\"(?:\\\"|[^\"])*(?<!\\)\")*(?:\\\"|[^\"])*$)

This combines to two solutions in the question to find spaces with even numbers of unescaped double quotes to the right hand side. Explanation:

\s                 # space
(?=                # followed by (not included in match though)
  (?:              # match pattern (but don't capture)
    (?:
      \\\"         # match escaped double quotes
      |            # OR
      [^\"]        # any character that is not double quotes
    )*             # 0 or more times
    (?<!\\)\"      # followed by unescaped quotes
    (?:\\\"|[^\"])*  # as above match escaped double quotes OR any character that is not double quotes
    (?<!\\)\"      # as above - followed by unescaped quotes
                   # the above pairs of unescaped quotes
  )*               # repeated 0 or more times (acting on pairs of quotes given an even number of quotes returned)
  (?:\\\"|[^\"])*  # as above
  $                # end of the line
)

So the final python is:

import re
test_str = r'spam spam spam "and \"eggs"'
regex = r'\s(?=(?:(?:\\\"|[^\"])*(?<!\\)\"(?:\\\"|[^\"])*(?<!\\)\")*(?:\\\"|[^\"])*$)'

test_list = re.split(regex, test_str)
print(test_list)
>>> ['spam', 'spam', 'spam', '"and \\"eggs"']

The only down side to this method is that it leave leading trailing quotes, however I can easily identify and remove these with the following python:

# remove leading and trailing unescaped quotes
test_list = list(map(lambda x: re.sub(r'(?<!\\)"', '', x), test_list))
# remove escape characters - they are no longer required
test_list = list(map(lambda x: x.replace(r'\"', '"'), test_list))
print(test_list)
>>> ['spam', 'spam', 'spam', 'and "eggs']
Related