regular expression to split a string with comma outside parentheses with more than one level python

Viewed 119

I have a string like this in python

filter="eq(Firstname,test),eq(Lastname,ltest),OR(eq(ContactID,12345),eq(ContactID,123456))"
    rx_comma = re.compile(r"(?:[^,(]|\([^)]*\))+")
    result = rx_comma.findall(filter)

Actual result is:

['eq(Firstname,test)', 'eq(Lastname,ltest)', 'OR(eq(ContactID,12345)', 'eq(ContactID,123456))']

Expected result is:

['eq(Firstname,test)', 'eq(Lastname,ltest)', 'OR(eq(ContactID,12345),eq(ContactID,123456))']

Any help is appreciated.

2 Answers

The OP's issue was already solved by using the regex module though, I'd like to introduce pyparsing as an alternative solution here. It can be installed by the following command:

pip install pyparsing

Code:

import pyparsing as pp
s = "eq(Firstname,test),eq(Lastname,ltest),OR(eq(ContactID,12345),eq(ContactID,123456))"
expr = pp.delimited_list(pp.original_text_for(pp.Regex(r'.*?(?=\()') + pp.nested_expr('(', ')')))
output = expr.parse_string(s).as_list()
assert output == ['eq(Firstname,test)', 'eq(Lastname,ltest)', 'OR(eq(ContactID,12345),eq(ContactID,123456))']

Explanation:

The key point is the expr in the above code. I added some explanatory comments to its definition as follows:

pp.delimited_list( # Separate a given string at the default comma delimiter
    pp.original_text_for( # Get original text instead of completely parsed elements.
        pp.Regex(r'.*?(?=\()') # Search everything before the first opening parenthesis '('
        + pp.nested_expr('(', ')') # Parse nested parentheses
    )
)

With PyPi regex module, you can use the code like

import regex
s = "eq(Firstname,test),eq(Lastname,ltest),OR(eq(ContactID,12345),eq(ContactID,123456))"
for x in regex.split(r"(\((?:[^()]++|(?1))*\))(*SKIP)(*F)|,", s):
    if x is not None:
        print( x )

Output:

eq(Firstname,test)
eq(Lastname,ltest)
OR(eq(ContactID,12345),eq(ContactID,123456))

See the Python and the regex demo.

Details:

  • (\((?:[^()]++|(?1))*\)) - Group 1 capturing a string between nested paired parentheses
  • (*SKIP)(*F) - the match is skipped and the next match is searched for from the failure position
  • | - or
  • , - a comma.
Related