| from __future__ import annotations | |
| import re | |
| from typing import List | |
| from db.parsers.consitution.legal_models import Explanation | |
| class ExplanationParser: | |
| """ | |
| Constitution / Legal Explanation Parser | |
| Supports: | |
| Explanation.— | |
| Explanation I.— | |
| Explanation II.— | |
| Explanation III.— | |
| Explanation 1.— | |
| Explanation 2.— | |
| """ | |
| # ===================================================== | |
| # EXPLANATION PATTERN | |
| # ===================================================== | |
| EXPLANATION_RE = re.compile( | |
| r""" | |
| ( | |
| Explanation | |
| (?: | |
| \s+ | |
| ( | |
| [IVXLCDM]+ | |
| | | |
| \d+ | |
| ) | |
| )? | |
| \s* | |
| [:\.\-—]* | |
| .*? | |
| ) | |
| (?= | |
| Explanation | |
| (?: | |
| \s+ | |
| ( | |
| [IVXLCDM]+ | |
| | | |
| \d+ | |
| ) | |
| )? | |
| \s* | |
| [:\.\-—] | |
| | | |
| Illustration | |
| | | |
| Provided | |
| | | |
| $ | |
| ) | |
| """, | |
| re.IGNORECASE | |
| | re.DOTALL | |
| | re.VERBOSE | |
| ) | |
| # ===================================================== | |
| # CLEAN TEXT | |
| # ===================================================== | |
| def clean_text( | |
| self, | |
| text: str | |
| ) -> str: | |
| text = text.strip() | |
| text = re.sub( | |
| r"\s+", | |
| " ", | |
| text | |
| ) | |
| return text | |
| # ===================================================== | |
| # EXTRACT NUMBER | |
| # ===================================================== | |
| def extract_number( | |
| self, | |
| explanation_text: str | |
| ) -> str: | |
| m = re.match( | |
| r""" | |
| Explanation | |
| (?: | |
| \s+ | |
| ( | |
| [IVXLCDM]+ | |
| | | |
| \d+ | |
| ) | |
| )? | |
| """, | |
| explanation_text, | |
| re.IGNORECASE | |
| | re.VERBOSE | |
| ) | |
| if not m: | |
| return "" | |
| return ( | |
| m.group(1) | |
| if m.group(1) | |
| else "" | |
| ) | |
| # ===================================================== | |
| # PARSE EXPLANATIONS | |
| # ===================================================== | |
| def parse_explanations( | |
| self, | |
| article_text: str | |
| ) -> List[Explanation]: | |
| explanations = [] | |
| matches = list( | |
| self.EXPLANATION_RE.finditer( | |
| article_text | |
| ) | |
| ) | |
| for match in matches: | |
| text = self.clean_text( | |
| match.group(1) | |
| ) | |
| explanation_no = ( | |
| self.extract_number( | |
| text | |
| ) | |
| ) | |
| explanations.append( | |
| Explanation( | |
| document="constitution", | |
| explanation_no=explanation_no, | |
| text=text | |
| ) | |
| ) | |
| return explanations | |
| # ===================================================== | |
| # VALIDATION | |
| # ===================================================== | |
| def validate_explanations( | |
| self, | |
| explanations: List[Explanation] | |
| ) -> List[str]: | |
| errors = [] | |
| seen = set() | |
| for exp in explanations: | |
| key = ( | |
| exp.explanation_no | |
| or "__DEFAULT__" | |
| ) | |
| if key in seen: | |
| errors.append( | |
| f"Duplicate " | |
| f"Explanation " | |
| f"{key}" | |
| ) | |
| seen.add(key) | |
| if not exp.text: | |
| errors.append( | |
| f"Empty Explanation" | |
| ) | |
| if len(exp.text) < 15: | |
| errors.append( | |
| f"Suspiciously Short " | |
| f"Explanation " | |
| f"{key}" | |
| ) | |
| return errors | |
| # ========================================================= | |
| # EXAMPLE | |
| # ========================================================= | |
| if __name__ == "__main__": | |
| sample = """ | |
| Some legal provision. | |
| Explanation I.— | |
| For the purposes of this Article, | |
| the term citizen includes... | |
| Explanation II.— | |
| This shall not apply to... | |
| Provided that no person... | |
| """ | |
| parser = ExplanationParser() | |
| explanations = ( | |
| parser.parse_explanations( | |
| sample | |
| ) | |
| ) | |
| print( | |
| f"Found " | |
| f"{len(explanations)} " | |
| f"Explanations" | |
| ) | |
| for e in explanations: | |
| print() | |
| print( | |
| "No:", | |
| e.explanation_no | |
| ) | |
| print( | |
| e.text | |
| ) | |
| print( | |
| parser.validate_explanations( | |
| explanations | |
| ) | |
| ) | |