|
|
@@ -130,6 +130,63 @@ class _QueryParser(object): |
|
|
|
return meth(arg) |
|
|
|
return Text(self._parse_literal(term)) |
|
|
|
|
|
|
|
def _scan_query(self, query, markers): |
|
|
|
"""Scan a query (sub)string for the first occurance of some markers. |
|
|
|
|
|
|
|
Returns a 2-tuple of (first_marker_found, marker_index). |
|
|
|
""" |
|
|
|
def _is_escaped(query, index): |
|
|
|
"""Return whether a query marker is backslash-escaped.""" |
|
|
|
return (index > 0 and query[index - 1] == "\\" and |
|
|
|
(index < 2 or query[index - 2] != "\\")) |
|
|
|
|
|
|
|
best_marker, best_index = None, maxsize |
|
|
|
for marker in markers: |
|
|
|
index = query.find(marker) |
|
|
|
if _is_escaped(query, index): |
|
|
|
_, new_index = self._scan_query(query[index + 1:], marker) |
|
|
|
index += new_index + 1 |
|
|
|
if index >= 0 and index < best_index: |
|
|
|
best_marker, best_index = marker, index |
|
|
|
return best_marker, best_index |
|
|
|
|
|
|
|
def _split_query(self, query, parens=False): |
|
|
|
"""Split a query string into a nested list of query terms.""" |
|
|
|
query = query.lstrip() |
|
|
|
if not query: |
|
|
|
return [] |
|
|
|
marker, index = self._scan_query(query, " \"'()") |
|
|
|
if not marker: |
|
|
|
return [query] |
|
|
|
nest = [query[:index]] if index > 0 else [] |
|
|
|
after = query[index + 1:] |
|
|
|
|
|
|
|
if marker == " ": |
|
|
|
nest += self._split_query(after, parens) |
|
|
|
elif marker in ('"', "'"): |
|
|
|
close_marker, close_index = self._scan_query(after, marker) |
|
|
|
if close_marker: |
|
|
|
if close_index > 0: |
|
|
|
nest.append(after[:close_index]) |
|
|
|
after = after[close_index + 1:] |
|
|
|
nest += self._split_query(after, parens) |
|
|
|
elif after: |
|
|
|
nest.append(after) |
|
|
|
elif marker == "(": |
|
|
|
inner, after = self._split_query(after, True), [] |
|
|
|
if inner and isinstance(inner[-1], tuple): |
|
|
|
after = self._split_query(inner.pop()[0], parens) |
|
|
|
if inner: |
|
|
|
nest.append(inner) |
|
|
|
if after: |
|
|
|
nest += after |
|
|
|
elif marker == ")": |
|
|
|
if parens: |
|
|
|
nest.append((after,)) |
|
|
|
else: |
|
|
|
nest += self._split_query(after) |
|
|
|
return nest |
|
|
|
|
|
|
|
def parse(self, query): |
|
|
|
""" |
|
|
|
Parse a search query. |
|
|
@@ -149,60 +206,7 @@ class _QueryParser(object): |
|
|
|
## TODO: balance tree |
|
|
|
## -------------------------------------------------------------------- |
|
|
|
|
|
|
|
def SCAN_FOR_MARKERS(string, markers): |
|
|
|
best_marker, best_index = None, maxsize |
|
|
|
for marker in markers: |
|
|
|
index = string.find(marker) |
|
|
|
if index > 0 and string[index - 1] == "\\" and (index == 1 or string[index - 2] != "\\"): |
|
|
|
_, new_index = SCAN_FOR_MARKERS(string[index + 1:], marker) |
|
|
|
index += new_index + 1 |
|
|
|
if index >= 0 and index < best_index: |
|
|
|
best_marker, best_index = marker, index |
|
|
|
return best_marker, best_index |
|
|
|
|
|
|
|
def SPLIT_QUERY_STRING(string, parens=False): |
|
|
|
string = string.lstrip() |
|
|
|
if not string: |
|
|
|
return [] |
|
|
|
marker, index = SCAN_FOR_MARKERS(string, " \"'()") |
|
|
|
|
|
|
|
if not marker: |
|
|
|
return [string] |
|
|
|
|
|
|
|
nest = [string[:index]] if index > 0 else [] |
|
|
|
after = string[index + 1:] |
|
|
|
|
|
|
|
if marker == " ": |
|
|
|
nest += SPLIT_QUERY_STRING(after, parens) |
|
|
|
|
|
|
|
elif marker in ('"', "'"): |
|
|
|
close_marker, close_index = SCAN_FOR_MARKERS(after, marker) |
|
|
|
if close_marker: |
|
|
|
if close_index > 0: |
|
|
|
nest.append(after[:close_index]) |
|
|
|
after = after[close_index + 1:] |
|
|
|
nest += SPLIT_QUERY_STRING(after, parens) |
|
|
|
elif after: |
|
|
|
nest.append(after) |
|
|
|
|
|
|
|
elif marker == "(": |
|
|
|
inner, after = SPLIT_QUERY_STRING(after, True), [] |
|
|
|
if inner and isinstance(inner[-1], tuple): |
|
|
|
after = SPLIT_QUERY_STRING(inner.pop()[0], parens) |
|
|
|
if inner: |
|
|
|
nest.append(inner) |
|
|
|
if after: |
|
|
|
nest += after |
|
|
|
|
|
|
|
elif marker == ")": |
|
|
|
if parens: |
|
|
|
nest.append((after,)) |
|
|
|
else: |
|
|
|
nest += SPLIT_QUERY_STRING(after) |
|
|
|
|
|
|
|
return nest |
|
|
|
|
|
|
|
nest = SPLIT_QUERY_STRING(query.rstrip()) |
|
|
|
nest = self._split_query(query.rstrip()) |
|
|
|
if not nest: |
|
|
|
raise QueryParseException('Empty query: "%s"' % query) |
|
|
|
|
|
|
|