""" Classes representing parsed query expressions. """
from hed.models.query_util import SearchResult
[docs]class Expression:
""" Base class for parsed query expressions. """
[docs] def __init__(self, token, left=None, right=None):
self.left = left
self.right = right
self.token = token
self._match_mode = "/" in token.text
self._must_not_be_in_line = False
if token.text.startswith("@"):
self._must_not_be_in_line = True
token.text = token.text[1:]
if token.text.startswith('"') and token.text.endswith('"') and len(token.text) > 2:
self._match_mode = 1
token.text = token.text[1:-1]
if "*" in token.text:
self._match_mode = 2
token.text = token.text.replace("*", "")
@staticmethod
def _get_parent_groups(search_results):
found_parent_groups = []
if search_results:
for group in search_results:
if not group.group.is_group:
continue
if group.group._parent:
found_parent_groups.append(SearchResult(group.group._parent, group.group))
return found_parent_groups
def __str__(self):
output_str = ""
if self.left:
output_str += str(self.left)
output_str += " " + str(self.token)
if self.right:
output_str += str(self.right)
return output_str
[docs] def handle_expr(self, hed_group, exact=False):
"""Handles parsing the given expression, recursively down the list as needed.
BaseClass implementation is search terms.
Parameters:
hed_group(HedGroup): The object to search
exact(bool): If True, we are only looking for groups containing this term directly, not descendants.
"""
if self._match_mode == 2:
groups_found = hed_group.find_wildcard_tags([self.token.text], recursive=True, include_groups=2)
elif self._match_mode:
groups_found = hed_group.find_exact_tags([self.token.text], recursive=True, include_groups=2)
else:
groups_found = hed_group.find_tags_with_term(self.token.text, recursive=True, include_groups=2)
if self._must_not_be_in_line:
# If we found this, and it cannot be in the line.
if groups_found:
groups_found = []
else:
groups_found = [([], group) for group in hed_group.get_all_groups()]
# If we're checking for all groups, also need to add parents.
if exact:
all_found_groups = [SearchResult(group, tag) for tag, group in groups_found]
else:
all_found_groups = []
for tag, group in groups_found:
while group:
all_found_groups.append(SearchResult(group, tag))
# This behavior makes it eat higher level groups at higher levels
tag = group
group = group._parent
return all_found_groups
[docs]class ExpressionAnd(Expression):
[docs] def handle_expr(self, hed_group, exact=False):
groups1 = self.left.handle_expr(hed_group, exact=exact)
if not groups1:
return groups1
groups2 = self.right.handle_expr(hed_group, exact=exact)
return self.merge_and_groups(groups1, groups2)
[docs] @staticmethod
def merge_and_groups(groups1, groups2):
"""Finds any shared results
Parameters:
groups1(list): a list of search results
groups2(list): a list of search results
Returns:
combined_groups(list): groups in both lists narrowed down results to where none of the tags overlap
"""
return_list = []
for group in groups1:
for other_group in groups2:
if group.group is other_group.group:
# At this point any shared tags between the two groups invalidates it.
if any(tag is tag2 and tag is not None for tag in group.tags for tag2 in other_group.tags):
continue
# Merge the two groups tags into one new result, now that we've verified they're unique
merged_result = group.merge_and_result(other_group)
dont_add = False
# This is trash and slow
for finalized_value in return_list:
if merged_result.has_same_tags(finalized_value):
dont_add = True
break
if dont_add:
continue
return_list.append(merged_result)
return return_list
def __str__(self):
output_str = "("
if self.left:
output_str += str(self.left)
output_str += " " + str(self.token)
if self.right:
output_str += str(self.right)
output_str += ")"
return output_str
[docs]class ExpressionWildcardNew(Expression):
[docs] def handle_expr(self, hed_group, exact=False):
groups_found = []
if self.token.text == "?":
# Any tag or group
groups_searching = hed_group.get_all_groups()
for group in groups_searching:
for child in group.children:
groups_found.append((child, group))
elif self.token.text == "??":
groups_searching = hed_group.get_all_groups()
for group in groups_searching:
for child in group.tags():
groups_found.append((child, group))
elif self.token.text == "???":
# Any group
groups_searching = hed_group.get_all_groups()
for group in groups_searching:
for child in group.groups():
groups_found.append((child, group))
# Wildcards are only found in containing groups. I believe this is correct.
# todo: Is this code still needed for this kind of wildcard? We already are registering every group, just not
# every group at every level.
all_found_groups = [SearchResult(group, tag) for tag, group in groups_found]
return all_found_groups
[docs]class ExpressionOr(Expression):
[docs] def handle_expr(self, hed_group, exact=False):
groups1 = self.left.handle_expr(hed_group, exact=exact)
# Don't early out as we need to gather all groups in case tags appear more than once etc
groups2 = self.right.handle_expr(hed_group, exact=exact)
# todo: optimize this eventually
# Filter out duplicates
duplicates = []
for group in groups1:
for other_group in groups2:
if group.has_same_tags(other_group):
duplicates.append(group)
groups1 = [group for group in groups1 if not any(other_group is group for other_group in duplicates)]
return groups1 + groups2
def __str__(self):
output_str = "("
if self.left:
output_str += str(self.left)
output_str += " " + str(self.token)
if self.right:
output_str += str(self.right)
output_str += ")"
return output_str
[docs]class ExpressionNegation(Expression):
[docs] def handle_expr(self, hed_group, exact=False):
found_groups = self.right.handle_expr(hed_group, exact=exact)
# Todo: this may need more thought with respects to wildcards and negation
# negated_groups = [group for group in hed_group.get_all_groups() if group not in groups]
# This simpler version works on python >= 3.9
# negated_groups = [SearchResult(group, []) for group in hed_group.get_all_groups() if group not in groups]
# Python 3.7/8 compatible version.
negated_groups = [SearchResult(group, []) for group in hed_group.get_all_groups()
if not any(group is found_group.group for found_group in found_groups)]
return negated_groups
[docs]class ExpressionDescendantGroup(Expression):
[docs] def handle_expr(self, hed_group, exact=False):
found_groups = self.right.handle_expr(hed_group)
found_parent_groups = self._get_parent_groups(found_groups)
return found_parent_groups
[docs]class ExpressionExactMatch(Expression):
[docs] def __init__(self, token, left=None, right=None):
super().__init__(token, left, right)
self.optional = "any"
@staticmethod
def _filter_exact_matches(search_results):
filtered_list = []
for group in search_results:
if len(group.group.children) == len(group.tags):
filtered_list.append(group)
return filtered_list
[docs] def handle_expr(self, hed_group, exact=False):
found_groups = self.right.handle_expr(hed_group, exact=True)
if self.optional == "any":
return self._get_parent_groups(found_groups)
filtered_list = self._filter_exact_matches(found_groups)
if filtered_list:
return self._get_parent_groups(filtered_list)
# Basically if we don't have an exact match above, do the more complex matching including optional
if self.left:
optional_groups = self.left.handle_expr(hed_group, exact=True)
found_groups = ExpressionAnd.merge_and_groups(found_groups, optional_groups)
filtered_list = self._filter_exact_matches(found_groups)
if filtered_list:
return self._get_parent_groups(filtered_list)
return []