Source code for fuzzy_search.pattern.fuzzy_patterns

"""Regex pattern definitions for recognizing Dutch person names, place names and dates, and
helper functions to combine these patterns with surrounding context strings."""

from typing import Union

dutch_person_name_patterns = {
    "name_suffixes": r"( de jonge| de oude| junior| senior)",
    "name_pattern": r"(([A-Z](\w|-)+)( [A-Z](\w|-)+)*( van| de| der| den)*( [A-Z](\w|-)+)+( de jonge| de oude| junior| senior)?)",
    "name_comma_name_pattern": r"((([A-Z](\w|-)+)( [A-Z](\w|-)+)*( van| de| der| den)*( [A-Z](\w|-)+)+( de jonge| de oude| junior| senior)?), ?)+",
    "name_comma_pattern": r"(([A-Z](\w|-)+)( [A-Z](\w|-)+)*( van| de| der| den)*( [A-Z](\w|-)+)+( de jonge| de oude| junior| senior)?), ?",
}

dutch_date_patterns = {
    "week_day": r"(maandag|dinsdag|woensdag|donderdag|vrijdag|zaterdag|zondag)",
    "month": r"(jan(uari|.)?|feb(ruari|\.)?|maart|mrt|apr(il|\.)|mei|jun(i)?|jul(i)?|aug(ustus|\.)?|sep(t|t\.|tember|\.)?|okt(ober|\.)?|nov(ember|\.)?|dec(ember|\.)?)",
    "day_month": r"(\d{1,2}) (jan(uari|.)?|feb(ruari|\.)?|maart|mrt|apr(il|\.)|mei|jun(i)?|jul(i)?|aug(ustus|\.)?|sep(t|t\.|tember|\.)?|okt(ober|\.)?|nov(ember|\.)?|dec(ember|\.)?)",
    "year": r"(\d{4})",
    "time": r"\b(\d{1,2}|een|twee|drie|vier|vijf|zes|zeven|acht|negen|tien|elf|twaalf) (uu?ren)\b",
    "day_part": r"\b's (avonds|middags|ochtends)",
}

dutch_place_name_patterns = {
    "name_pattern": r"([A-Z](\w|-)+)",
}

dutch_place_name_patterns["in_placename"] = r"(in|tot) " + dutch_place_name_patterns["name_pattern"]

dutch_date_patterns["weekday_comma_day_month"] = dutch_date_patterns["week_day"] + "(,? (de|den)?) " + \
                                                 dutch_date_patterns["day_month"]

dutch_person_name_patterns["name_and_name_pattern"] = dutch_person_name_patterns["name_pattern"] + " en " + \
                                                      dutch_person_name_patterns["name_pattern"]
dutch_person_name_patterns["name_sequence_pattern"] = dutch_person_name_patterns["name_comma_pattern"] + \
                                                      dutch_person_name_patterns["name_and_name_pattern"]

# These pattern definitions specify:
# 1. which regex patterns to use for which pattern names
# 2. which regex match groups should be extracted
pattern_definitions = {
    "name": {
        "pattern": dutch_person_name_patterns["name_pattern"],
        "group_indices": [1],
        "type": "dutch_person_name",
    },
    "name_and_name": {
        "pattern": dutch_person_name_patterns["name_and_name_pattern"],
        "group_indices": [1, 10],
        "type": "dutch_person_name",
    },
    "name_sequence": {
        "pattern": dutch_person_name_patterns["name_sequence_pattern"],
        "group_indices": [1, 10, 19],
        "type": "dutch_person_name",
    },
    # commented out because it's not useful on it own:
    # "name_comma_name": {
    #    "pattern": dutch_person_name_patterns["name_comma_name_pattern"],
    #    "group_indices": [1, 10, 19],
    #    "type": "dutch_person_name",
    # },
    "weekday_comma_day_month": {
        "pattern": dutch_date_patterns["weekday_comma_day_month"],
        "group_indices": [1, 4, 5],
        "type": "dutch_date",
    },
}


[docs] def list_context_pattern_types(context_type=None): """Return the names of context pattern functions registered for a given context type. :param context_type: a context type key in ``context_pattern`` (defaults to "all") :return: a list of context pattern function names :rtype: list """ if not context_type: context_type = "all" if context_type not in context_pattern: print("ERROR - Unknown context type. Pick from:") for context_type in context_pattern: print("\t{t}".format(context_type)) raise KeyError("Unknown context type") return [pattern_type for pattern_type in context_pattern[context_type]]
[docs] def list_pattern_names(name_only=True, pattern_type=None): """Return the names of registered pattern definitions, optionally filtered by pattern type. :param name_only: unused; kept for API compatibility :param pattern_type: if given, only return names of patterns with this type :return: a list of pattern names :rtype: list """ if pattern_type: return [pattern_name for pattern_name in pattern_definitions if pattern_definitions[pattern_name]["type"] == pattern_type] else: return [pattern_name for pattern_name in pattern_definitions]
[docs] def list_pattern_definitions(pattern_type=None): """Return registered pattern definitions, optionally filtered by pattern type. :param pattern_type: if given, only return definitions of patterns with this type :return: a list (if filtered) or dict (if not) of pattern definitions """ if pattern_type: return [pattern_definitions[pattern_name] for pattern_name in pattern_definitions if pattern_definitions[pattern_name]["type"] == pattern_type] else: return pattern_definitions
[docs] def pattern_comma_then_context(name, pattern_definition, context_string): """Build a pattern definition that matches the given pattern followed by a comma and the context string. :param name: the base name of the pattern :param pattern_definition: the pattern definition dictionary to extend :param context_string: the (already escaped) context string to match after the pattern :return: a new pattern definition dictionary :rtype: dict """ return { "name": name + "_comma_then_context", "pattern": pattern_definition["pattern"] + " ?, ?" + context_string, "group_indices": pattern_definition["group_indices"], }
[docs] def context_then_pattern(name, pattern_definition, context_string): """Build a pattern definition that matches the context string followed by the given pattern. :param name: the base name of the pattern :param pattern_definition: the pattern definition dictionary to extend :param context_string: the (already escaped) context string to match before the pattern :return: a new pattern definition dictionary :rtype: dict """ return { "name": "context_then_" + name, "pattern": context_string + ",? " + pattern_definition["pattern"], "group_indices": pattern_definition["group_indices"], }
[docs] def pattern_before_context(name, pattern_definition, context_string, max_distance=10): """Build a pattern definition that matches the given pattern, then up to ``max_distance`` characters, then the context string. :param name: the base name of the pattern :param pattern_definition: the pattern definition dictionary to extend :param context_string: the (already escaped) context string to match after the pattern :param max_distance: the maximum number of characters allowed between pattern and context :return: a new pattern definition dictionary :rtype: dict """ return { "name": name + "_before_context", "pattern": pattern_definition["pattern"] + ".{d}".format(d=max_distance) + context_string, "group_indices": pattern_definition["group_indices"], }
[docs] def context_before_pattern(name, pattern_definition, context_string, max_distance=10): """Build a pattern definition that matches the context string, then up to ``max_distance`` characters, then the given pattern. :param name: the base name of the pattern :param pattern_definition: the pattern definition dictionary to extend :param context_string: the (already escaped) context string to match before the pattern :param max_distance: the maximum number of characters allowed between context and pattern :return: a new pattern definition dictionary :rtype: dict """ return { "name": "context_before_" + name, "pattern": context_string + ".{d}".format(d=max_distance) + pattern_definition["pattern"], "group_indices": pattern_definition["group_indices"], }
[docs] def get_search_patterns(pattern_type=None): """Return registered pattern definitions as a dictionary, optionally filtered by pattern type. :param pattern_type: if given, only return definitions of patterns with this type :return: a dict mapping pattern name to pattern definition :rtype: dict """ if pattern_type: return {pattern_name: pattern_definitions[pattern_name] for pattern_name in pattern_definitions if pattern_definitions[pattern_name]["type"] == pattern_type} else: return pattern_definitions
[docs] def get_context_patterns(context_type: Union[None, str] = None) -> dict: """Return the dictionary of context pattern functions registered for a given context type. :param context_type: a context type key in ``context_pattern`` (defaults to "all") :type context_type: Union[None, str] :return: a dict mapping pattern function name to pattern function :rtype: dict """ if not context_type: context_type = "all" if context_type not in context_pattern: print("ERROR - Unknown context type. Pick from:") for context_type in context_pattern: print(f"\t{context_type}") raise KeyError("Unknown context type") return context_pattern[context_type]
# dictionary mapping regex pattern name to function to create context-specific regex pattern context_pattern = { "person_name": { "pattern_comma_then_context": pattern_comma_then_context, "context_then_pattern": context_then_pattern, }, "distance": { "pattern_before_context": pattern_before_context, "context_before_pattern": context_before_pattern, }, "all": { "pattern_comma_then_context": pattern_comma_then_context, "context_then_pattern": context_then_pattern, "pattern_before_context": pattern_before_context, "context_before_pattern": context_before_pattern, }, }
[docs] def escape_string(string): """Escape regex special characters in a string so it can be embedded literally in a pattern. :param string: the string to escape :return: the escaped string :rtype: str """ string = string.replace("\\", r"\\").replace("/", r"\/") string = string.replace("[", r"\[").replace("]", r"\]").replace("(", r"\(").replace(")", r"\)") string = string.replace("{", r"\{").replace("}", r"\}") string = string.replace("*", r"\*").replace("?", r"\?").replace("+", r"\+") string = string.replace(".", r"\.").replace("|", r"\|") string = string.replace("!", r"\!").replace("^", r"\^").replace("$", r"\$") return string
[docs] def make_search_context_patterns(context_string, pattern_names, context_patterns): """Generate context-specific search patterns by combining a context string with named pattern definitions using the given context pattern functions. :param context_string: the context string to embed in the generated patterns :param pattern_names: the names of the pattern definitions to combine with the context :param context_patterns: a dict mapping context pattern function name to function :return: a list of new pattern definitions :rtype: list """ context_string = escape_string(context_string) patterns = [] for context_pattern in context_patterns: for pattern_name in pattern_names: patterns += [ context_patterns[context_pattern](pattern_name, pattern_definitions[pattern_name], context_string)] return patterns
if __name__ == "__main__": pattern_names = ["name_comma_name"] pattern_types = ["pattern_comma_then_context"] context_string = "Makelaar" make_search_context_patterns(context_string, pattern_names, pattern_types)