(expr)
| 60 | return build_q_object(field_name, phrases.get(token, token), use_regex, whole_words) |
| 61 | |
| 62 | def parse_expression(expr): |
| 63 | expr = expr.strip() |
| 64 | |
| 65 | if '(' in expr: |
| 66 | paren_start = expr.rfind('(') |
| 67 | paren_end = expr.find(')', paren_start) |
| 68 | if paren_end == -1: |
| 69 | return Q() |
| 70 | |
| 71 | group_q = parse_expression(expr[paren_start + 1:paren_end]) |
| 72 | |
| 73 | before_str = expr[:paren_start].rstrip() |
| 74 | after_str = expr[paren_end + 1:].lstrip() |
| 75 | |
| 76 | before_op = '&' |
| 77 | if before_str.upper().endswith(' AND'): |
| 78 | before_str = before_str[:-4].rstrip() |
| 79 | elif before_str.upper().endswith(' OR'): |
| 80 | before_str = before_str[:-3].rstrip() |
| 81 | before_op = '|' |
| 82 | |
| 83 | after_op = '&' |
| 84 | after_upper = after_str.upper() |
| 85 | if after_upper.startswith('AND '): |
| 86 | after_str = after_str[4:].lstrip() |
| 87 | elif after_upper.startswith('OR '): |
| 88 | after_str = after_str[3:].lstrip() |
| 89 | after_op = '|' |
| 90 | |
| 91 | result = group_q |
| 92 | if before_str: |
| 93 | before_q = parse_expression(before_str) |
| 94 | result = (before_q | result) if before_op == '|' else (before_q & result) |
| 95 | if after_str: |
| 96 | after_q = parse_expression(after_str) |
| 97 | result = (result | after_q) if after_op == '|' else (result & after_q) |
| 98 | return result |
| 99 | |
| 100 | tokens = [] |
| 101 | operators = [] |
| 102 | remaining = expr |
| 103 | |
| 104 | while remaining: |
| 105 | upper = remaining.upper() |
| 106 | and_pos = upper.find(' AND ') |
| 107 | or_pos = upper.find(' OR ') |
| 108 | |
| 109 | if and_pos == -1 and or_pos == -1: |
| 110 | tokens.append(remaining.strip()) |
| 111 | break |
| 112 | |
| 113 | if and_pos == -1: |
| 114 | pos, op, op_len = or_pos, '|', 4 |
| 115 | elif or_pos == -1: |
| 116 | pos, op, op_len = and_pos, '&', 5 |
| 117 | elif and_pos < or_pos: |
| 118 | pos, op, op_len = and_pos, '&', 5 |
| 119 | else: |
no test coverage detected