8bf92c04ed
git-subtree-dir: utils_v2 git-subtree-split: 41797375144632a2cdda1e4c6c4a5769e4b80306
291 lines
12 KiB
Python
291 lines
12 KiB
Python
"""
|
|
|
|
AUTHOR:
|
|
|
|
Khushal P Soonderji
|
|
|
|
DATE:
|
|
|
|
Sunday, 28th Apr., 2024
|
|
|
|
OBJECTIVE:
|
|
|
|
To provide a convenient way to perform RegEx operations like finding patterns and substituting them.
|
|
|
|
REFERENCES:
|
|
|
|
1) https://www.w3schools.com/python/python_regex.asp
|
|
|
|
DOWNLOADS:
|
|
|
|
N/A
|
|
|
|
"""
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** IMPORT ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# To make sibling directories accessible for imports:
|
|
import sys
|
|
sys.path.append(".")
|
|
sys.path.append("..")
|
|
|
|
# To work with RegEx:
|
|
import re
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** MACROS / ONE-TIME INIT ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# Common RegEx patterns:
|
|
REGEX_EMAIL_ID = r"[\d\w\-_.+]*@[\d\w\-_]*.[\d\w]{2,}"
|
|
REGEX_URL = r"^http[s]?:\/\/([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[a-zA-Z0-9-_.~%]*)*(?:\?[a-zA-Z0-9-_&=%.]*)?$"
|
|
REGEX_HTTPS_URL = r"^https:\/\/([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[a-zA-Z0-9-_.~%]*)*(?:\?[a-zA-Z0-9-_&=%.]*)?$"
|
|
REGEX_PASSWORD = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[\d])(?=.*[!@#$%^&*()_+{}\[\]:;<>,.?~\\\/-]).{8,}$"
|
|
REGEX_NAME = r"^[\d\w .\-]{1,30}$"
|
|
REGEX_USERNAME = r"^[\d\w_]{8,25}$"
|
|
REGEX_CONTACT_NUMBER = r"\+?\d{0,3}\s*\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}"
|
|
REGEX_DATE = r"\b(?:\d{4}-\d{2}-\d{2}|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2},?\s+\d{4}|\d{1,2}\/\d{1,2}\/\d{4}|\d{1,2}-\d{1,2}-\d{2}|\d{1,2}(?:st|nd|rd|th)\s+(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?),?\s+\d{4})\b"
|
|
REGEX_GSTIN = r"[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}"
|
|
REGEX_PAN = r"[A-Z]{5}[0-9]{4}[A-Z]{1}"
|
|
REGEX_IPV4 = (r"[0-9]{1,3}\." * 3) + r"[0-9]{1,3}"
|
|
REGEX_IPV6 = (r"[0-9a-fA-F]{1,4}:" * 7) + r"[0-9a-fA-F]{1,4}"
|
|
REGEX_IFSC = r"[A-Z]{4}0[A-Z0-9]{6}"
|
|
REGEX_UPI = r"[a-zA-Z0-9\.\-]{2,256}@[a-zA-Z][a-zA-Z]{2,64}"
|
|
REGEX_MAC_ADDRESS = r"([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\\.[0-9a-fA-F]{4}\\.[0-9a-fA-F]{4})"
|
|
REGEX_METRIC_WEIGHT = r"[\d\.]+[ ]?[k]?g"
|
|
|
|
|
|
# RegEx chars (append them to the patterns if needed):
|
|
REGEX_START = "^"
|
|
REGEX_END = "$"
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** VARIABLES ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# --- Nothing Yet
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** FUNCTIONS ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
def find(text, pattern, case_sensitive = True, dot_all = False):
|
|
|
|
"""
|
|
Returns a list of substrings that match the given RegEx pattern in the input text.
|
|
:param text: The text in which the pattern needs to be found.
|
|
:param pattern: The RegEx pattern to look for.
|
|
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
|
|
:param dot_all: Allow all characters to be matched in ".".
|
|
:return: An array (list) of substring that match the pattern. Can be an empty list as well.
|
|
"""
|
|
|
|
# Prepare the flags:
|
|
flags = 0
|
|
if not case_sensitive: flags |= re.IGNORECASE
|
|
if dot_all: flags |= re.DOTALL
|
|
|
|
# Perform the RegEx operation, and clean the results:
|
|
matches = [match if type(match) is str else match[1] for match in re.findall(pattern, text, flags = flags)]
|
|
matches = [match for match in matches if len(match) > 0]
|
|
|
|
# Return the results:
|
|
return matches
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def find_first(text, pattern, case_sensitive = True, dot_all = False):
|
|
|
|
"""
|
|
Returns the first substring that matches the given RegEx pattern in the input text.
|
|
:param text: The text in which the pattern needs to be found.
|
|
:param pattern: The RegEx pattern to look for.
|
|
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
|
|
:param dot_all: Allow all characters to be matched in ".".
|
|
:return: The first match as a string, or None if no match was found..
|
|
"""
|
|
|
|
matches = find(
|
|
text = text,
|
|
pattern = pattern,
|
|
case_sensitive = case_sensitive,
|
|
dot_all = dot_all
|
|
)
|
|
|
|
if not matches: return None
|
|
else: return matches[0]
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def replace(text, pattern, substitute_text, case_sensitive = True, dot_all = False):
|
|
|
|
"""
|
|
Replaces any substring in the text that matches the RegEx pattern.
|
|
:param text: The text in which the substitutions need to be made.
|
|
:param pattern: The RegEx pattern that needs to be substituted.
|
|
:param substitute_text: The text that will replace the matches that were found.
|
|
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
|
|
:param dot_all: Allow all characters to be matched in ".".
|
|
:return: The text with the substitutions. If no matches are found, the original string is returned.
|
|
"""
|
|
|
|
# Prepare the flags:
|
|
flags = 0
|
|
if not case_sensitive: flags |= re.IGNORECASE
|
|
if dot_all: flags |= re.DOTALL
|
|
|
|
# Perform the RegEx operation, and return the results:
|
|
return re.sub(pattern, substitute_text, text, flags = flags)
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def search(text, pattern, case_sensitive = True, dot_all = False):
|
|
|
|
"""
|
|
Checks if the given RegEx pattern occurs ANYWHERE in the text that was provided.
|
|
:param text: The text that needs to be matched against the pattern.
|
|
:param pattern: The RegEx pattern to look for.
|
|
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
|
|
:param dot_all: Allow all characters to be matched in ".".
|
|
:return: True if the pattern matches, else False.
|
|
"""
|
|
|
|
# Prepare the flags:
|
|
flags = 0
|
|
if not case_sensitive: flags |= re.IGNORECASE
|
|
if dot_all: flags |= re.DOTALL
|
|
|
|
# Perform the RegEx operation, and return the results:
|
|
if re.search(pattern, text, flags = flags): return True
|
|
else: return False
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def match(text, pattern, case_sensitive = True, dot_all = False):
|
|
|
|
"""
|
|
Checks if the given text matches the RegEx pattern that was provided. The check is made only at the start of the
|
|
input string.
|
|
:param text: The text that needs to be matched against the pattern.
|
|
:param pattern: The RegEx pattern to look for.
|
|
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
|
|
:param dot_all: Allow all characters to be matched in ".".
|
|
:return: True if the pattern matches, else False.
|
|
"""
|
|
|
|
# Prepare the flags:
|
|
flags = 0
|
|
if not case_sensitive: flags |= re.IGNORECASE
|
|
if dot_all: flags |= re.DOTALL
|
|
|
|
# Perform the RegEx operation, and return the results:
|
|
if re.match(pattern, text, flags = flags): return True
|
|
else: return False
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def split(text, pattern, case_sensitive = True, dot_all = False):
|
|
|
|
"""
|
|
Splits an input string based on the pattern that is being matched.
|
|
:param text: The text that needs to be matched against the pattern.
|
|
:param pattern: The RegEx pattern to look for.
|
|
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
|
|
:param dot_all: Allow all characters to be matched in ".".
|
|
:return: True if the pattern matches, else False.
|
|
"""
|
|
|
|
# Prepare the flags:
|
|
flags = 0
|
|
if not case_sensitive: flags |= re.IGNORECASE
|
|
if dot_all: flags |= re.DOTALL
|
|
|
|
# Perform the RegEx operation, and return the results:
|
|
substrings = re.split(pattern, text, flags = flags)
|
|
if len(substrings) > 0 and substrings[0] == "": substrings.pop(0)
|
|
return substrings
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def to_json(text, pattern, case_sensitive = True, dot_all = False):
|
|
|
|
"""
|
|
Gives out a dict from the extracted features in a string. It is based on the concept of Named Groups.
|
|
Consider the following example (assuming the search is case-insensitive):
|
|
TEXT: "UPI/309258561479/14:17:35/UPI/omsainurses@okhdfc"
|
|
PATTERN: "upi/.*/(?P<time>.*)/.*/(?P<ref>.*)"
|
|
RESULT: {'time': '14:17:35', 'ref': 'omsainurses@okhdfc'}
|
|
:param text: The text that needs to be matched against the pattern.
|
|
:param pattern: The RegEx pattern to look for.
|
|
:param case_sensitive: Whether, or not, you want the operation to be case-sensitive.
|
|
:param dot_all: Allow all characters to be matched in ".".
|
|
:return: A dict with all the extracted features.
|
|
"""
|
|
|
|
# Prepare the flags:
|
|
flags = 0
|
|
if not case_sensitive: flags |= re.IGNORECASE
|
|
if dot_all: flags |= re.DOTALL
|
|
|
|
# Perform the RegEx operation, and return the results:
|
|
matches = re.search(pattern, text, flags = flags)
|
|
regex_json = matches.groupdict() if matches else {}
|
|
return regex_json
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** MAIN PROGRAM ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import json_utils
|
|
|
|
for narr in [
|
|
# r"UPI/309258561479/14:17:35/UPI/omsainurses@okhdf cb",
|
|
# r"UPI/309258561479/14:17:35/omsainurses@okhdf cb",
|
|
# r"NEFT-N095232403538009-RELIGARE BROKING LIMITED MAI",
|
|
r"Product listing - My product - 75g - Super combo pack",
|
|
r"Product listing - My product - 750 g",
|
|
r"0.5 kgs mini pack"
|
|
]:
|
|
|
|
result = to_json(
|
|
narr,
|
|
r"upi/.*/(?P<time>.*)/.*/(?P<ref>.*)",
|
|
case_sensitive = False
|
|
)
|
|
print(result)
|