mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-09-13 04:04:09 +00:00
refactor(normalize): enhance documentation and code quality for normalize module
- Add comprehensive module-level docstrings with features and examples - Enhance class and method docstrings with detailed parameter descriptions - Improve error handling with specific exception types - Add type hints for better code clarity - Enhance logging with debug/info messages - Improve code organization and maintainability - Add graceful handling of optional dependencies (BeautifulSoup, langdetect, chardet) Refactored files: - __init__.py: Module-level documentation and exports - data_cleaner.py: Data cleaning, duplicate detection, validation, missing values - date_normalizer.py: Date/time normalization, timezone handling, relative dates - encoding_handler.py: Encoding detection, UTF-8 conversion, BOM removal - entity_normalizer.py: Entity normalization, alias resolution, disambiguation - language_detector.py: Language detection with optional langdetect support - number_normalizer.py: Number normalization, unit conversion, currency handling - text_cleaner.py: Text cleaning, HTML removal, Unicode normalization - text_normalizer.py: Text normalization, Unicode/whitespace/special character processing
This commit is contained in:
@@ -1,9 +1,20 @@
|
||||
"""
|
||||
Data Normalization Module
|
||||
|
||||
This module provides comprehensive data normalization and cleaning capabilities.
|
||||
This module provides comprehensive data normalization and cleaning capabilities
|
||||
for the Semantica framework, enabling standardization and quality improvement
|
||||
of various data types.
|
||||
|
||||
Exports:
|
||||
Key Features:
|
||||
- Text normalization and cleaning (Unicode, whitespace, special characters)
|
||||
- Entity name normalization (aliases, variants, disambiguation)
|
||||
- Date and time normalization (formats, timezones, relative dates)
|
||||
- Number and quantity normalization (formats, units, currency, scientific notation)
|
||||
- Data cleaning (duplicates, validation, missing values)
|
||||
- Language detection (multi-language support)
|
||||
- Encoding handling (detection, conversion, BOM removal)
|
||||
|
||||
Main Classes:
|
||||
- TextNormalizer: Text cleaning and normalization
|
||||
- EntityNormalizer: Entity name normalization
|
||||
- DateNormalizer: Date and time normalization
|
||||
@@ -12,6 +23,16 @@ Exports:
|
||||
- TextCleaner: Text cleaning utilities
|
||||
- LanguageDetector: Language detection
|
||||
- EncodingHandler: Encoding detection and conversion
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.normalize import TextNormalizer, EntityNormalizer
|
||||
>>> text_norm = TextNormalizer()
|
||||
>>> normalized = text_norm.normalize_text("Hello World")
|
||||
>>> entity_norm = EntityNormalizer()
|
||||
>>> canonical = entity_norm.normalize_entity("John Doe")
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
from .text_normalizer import (
|
||||
|
||||
+408
-103
@@ -1,20 +1,32 @@
|
||||
"""
|
||||
Data Cleaning Module
|
||||
|
||||
Handles general data cleaning and quality improvement.
|
||||
This module provides comprehensive data cleaning and quality improvement
|
||||
capabilities for the Semantica framework, enabling detection and resolution
|
||||
of data quality issues.
|
||||
|
||||
Key Features:
|
||||
- Data quality assessment
|
||||
- Duplicate detection and removal
|
||||
- Data validation and correction
|
||||
- Missing value handling
|
||||
- Duplicate detection and removal (fuzzy matching, similarity scoring)
|
||||
- Data validation and correction (schema validation, type checking)
|
||||
- Missing value handling (removal, filling, imputation)
|
||||
- Data consistency checking
|
||||
- Batch processing support
|
||||
|
||||
Main Classes:
|
||||
- DataCleaner: Main data cleaning class
|
||||
- DataCleaner: Main data cleaning coordinator
|
||||
- DuplicateDetector: Duplicate detection engine
|
||||
- DataValidator: Data validation engine
|
||||
- MissingValueHandler: Missing value processor
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.normalize import DataCleaner
|
||||
>>> cleaner = DataCleaner()
|
||||
>>> cleaned = cleaner.clean_data(dataset, remove_duplicates=True, validate=True)
|
||||
>>> duplicates = cleaner.detect_duplicates(dataset, threshold=0.8)
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
@@ -27,7 +39,18 @@ from ..utils.logging import get_logger
|
||||
|
||||
@dataclass
|
||||
class DuplicateGroup:
|
||||
"""Duplicate record group."""
|
||||
"""
|
||||
Duplicate record group dataclass.
|
||||
|
||||
This dataclass represents a group of duplicate records identified during
|
||||
duplicate detection, containing the records, similarity score, and canonical
|
||||
record.
|
||||
|
||||
Attributes:
|
||||
records: List of duplicate record dictionaries
|
||||
similarity_score: Average similarity score for the group (0.0 to 1.0)
|
||||
canonical_record: Canonical/representative record (typically first record)
|
||||
"""
|
||||
records: List[Dict[str, Any]]
|
||||
similarity_score: float
|
||||
canonical_record: Optional[Dict[str, Any]] = None
|
||||
@@ -35,7 +58,17 @@ class DuplicateGroup:
|
||||
|
||||
@dataclass
|
||||
class ValidationResult:
|
||||
"""Data validation result."""
|
||||
"""
|
||||
Data validation result dataclass.
|
||||
|
||||
This dataclass represents the result of data validation, containing
|
||||
validation status, errors, and warnings.
|
||||
|
||||
Attributes:
|
||||
valid: Whether the data is valid (True if no errors)
|
||||
errors: List of error dictionaries (critical validation failures)
|
||||
warnings: List of warning dictionaries (non-critical issues)
|
||||
"""
|
||||
valid: bool
|
||||
errors: List[Dict[str, Any]] = field(default_factory=list)
|
||||
warnings: List[Dict[str, Any]] = field(default_factory=list)
|
||||
@@ -43,23 +76,37 @@ class ValidationResult:
|
||||
|
||||
class DataCleaner:
|
||||
"""
|
||||
General data cleaning and quality improvement handler.
|
||||
Data cleaning and quality improvement coordinator.
|
||||
|
||||
• Cleans and improves data quality
|
||||
• Detects and removes duplicates
|
||||
• Validates data integrity
|
||||
• Handles missing values
|
||||
• Ensures data consistency
|
||||
• Supports various data types
|
||||
This class provides comprehensive data cleaning capabilities, coordinating
|
||||
duplicate detection, data validation, and missing value handling to
|
||||
improve data quality.
|
||||
|
||||
Features:
|
||||
- Data quality improvement
|
||||
- Duplicate detection and removal
|
||||
- Data validation against schemas
|
||||
- Missing value handling (multiple strategies)
|
||||
- Data consistency checking
|
||||
- Support for various data types
|
||||
|
||||
Example Usage:
|
||||
>>> cleaner = DataCleaner()
|
||||
>>> cleaned = cleaner.clean_data(dataset, remove_duplicates=True, validate=True)
|
||||
>>> duplicates = cleaner.detect_duplicates(dataset)
|
||||
>>> validation = cleaner.validate_data(dataset, schema)
|
||||
"""
|
||||
|
||||
def __init__(self, config=None, **kwargs):
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
"""
|
||||
Initialize data cleaner.
|
||||
|
||||
Sets up the cleaner with duplicate detector, data validator, and
|
||||
missing value handler components.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary
|
||||
**kwargs: Additional configuration options
|
||||
config: Configuration dictionary (optional)
|
||||
**kwargs: Additional configuration options (merged into config)
|
||||
"""
|
||||
self.logger = get_logger("data_cleaner")
|
||||
self.config = config or {}
|
||||
@@ -68,37 +115,52 @@ class DataCleaner:
|
||||
self.duplicate_detector = DuplicateDetector(**self.config)
|
||||
self.data_validator = DataValidator(**self.config)
|
||||
self.missing_value_handler = MissingValueHandler(**self.config)
|
||||
|
||||
self.logger.debug("Data cleaner initialized")
|
||||
|
||||
def clean_data(self, dataset: List[Dict[str, Any]], **options) -> List[Dict[str, Any]]:
|
||||
def clean_data(
|
||||
self,
|
||||
dataset: List[Dict[str, Any]],
|
||||
remove_duplicates: bool = True,
|
||||
validate: bool = True,
|
||||
handle_missing: bool = True,
|
||||
**options
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Clean dataset with various cleaning operations.
|
||||
|
||||
This method performs comprehensive data cleaning by applying missing
|
||||
value handling, validation, and duplicate removal in sequence.
|
||||
|
||||
Args:
|
||||
dataset: List of data records
|
||||
**options: Cleaning options:
|
||||
- remove_duplicates: Remove duplicates (default: True)
|
||||
- validate: Validate data (default: True)
|
||||
- handle_missing: Handle missing values (default: True)
|
||||
dataset: List of data record dictionaries
|
||||
remove_duplicates: Whether to remove duplicate records (default: True)
|
||||
validate: Whether to validate data against schema (default: True)
|
||||
handle_missing: Whether to handle missing values (default: True)
|
||||
**options: Additional cleaning options:
|
||||
- missing_strategy: Strategy for missing values ("remove", "fill", "impute")
|
||||
- schema: Validation schema dictionary
|
||||
- duplicate_criteria: Criteria for duplicate detection
|
||||
|
||||
Returns:
|
||||
Cleaned dataset
|
||||
list: Cleaned dataset (list of record dictionaries)
|
||||
"""
|
||||
cleaned = list(dataset)
|
||||
|
||||
# Handle missing values
|
||||
if options.get("handle_missing", True):
|
||||
if handle_missing:
|
||||
strategy = options.get("missing_strategy", "remove")
|
||||
cleaned = self.missing_value_handler.handle_missing_values(cleaned, strategy=strategy)
|
||||
|
||||
# Validate data
|
||||
if options.get("validate", True):
|
||||
if validate:
|
||||
schema = options.get("schema")
|
||||
validation = self.data_validator.validate_dataset(cleaned, schema)
|
||||
if not validation.valid:
|
||||
self.logger.warning(f"Validation found {len(validation.errors)} errors")
|
||||
|
||||
# Remove duplicates
|
||||
if options.get("remove_duplicates", True):
|
||||
if remove_duplicates:
|
||||
criteria = options.get("duplicate_criteria", {})
|
||||
duplicates = self.detect_duplicates(cleaned, **criteria)
|
||||
|
||||
@@ -114,83 +176,158 @@ class DataCleaner:
|
||||
|
||||
return cleaned
|
||||
|
||||
def detect_duplicates(self, dataset: List[Dict[str, Any]], **criteria) -> List[DuplicateGroup]:
|
||||
def detect_duplicates(
|
||||
self,
|
||||
dataset: List[Dict[str, Any]],
|
||||
threshold: Optional[float] = None,
|
||||
key_fields: Optional[List[str]] = None,
|
||||
**criteria
|
||||
) -> List[DuplicateGroup]:
|
||||
"""
|
||||
Detect duplicate records in dataset.
|
||||
|
||||
This method identifies duplicate records using similarity matching
|
||||
based on specified criteria and threshold.
|
||||
|
||||
Args:
|
||||
dataset: List of data records
|
||||
**criteria: Duplicate detection criteria
|
||||
dataset: List of data record dictionaries
|
||||
threshold: Similarity threshold for duplicates (0.0 to 1.0, optional,
|
||||
uses detector's default if not provided)
|
||||
key_fields: List of field names to use for comparison (optional,
|
||||
uses all common fields if not provided)
|
||||
**criteria: Additional duplicate detection criteria
|
||||
|
||||
Returns:
|
||||
List of duplicate groups
|
||||
list: List of DuplicateGroup objects, each containing duplicate
|
||||
records with similarity scores
|
||||
"""
|
||||
return self.duplicate_detector.detect_duplicates(dataset, **criteria)
|
||||
return self.duplicate_detector.detect_duplicates(
|
||||
dataset, threshold=threshold, key_fields=key_fields, **criteria
|
||||
)
|
||||
|
||||
def validate_data(self, dataset: List[Dict[str, Any]], schema=None) -> ValidationResult:
|
||||
def validate_data(
|
||||
self,
|
||||
dataset: List[Dict[str, Any]],
|
||||
schema: Optional[Dict[str, Any]] = None
|
||||
) -> ValidationResult:
|
||||
"""
|
||||
Validate data against schema or rules.
|
||||
|
||||
This method validates all records in the dataset against the provided
|
||||
schema, checking required fields, data types, and constraints.
|
||||
|
||||
Args:
|
||||
dataset: List of data records
|
||||
schema: Validation schema
|
||||
dataset: List of data record dictionaries
|
||||
schema: Validation schema dictionary (optional) containing:
|
||||
- fields: Dictionary mapping field names to field schemas with:
|
||||
- type: Expected data type
|
||||
- required: Whether field is required (bool)
|
||||
|
||||
Returns:
|
||||
Validation result
|
||||
ValidationResult: Validation result containing:
|
||||
- valid: True if no errors, False otherwise
|
||||
- errors: List of error dictionaries with record_index and field info
|
||||
- warnings: List of warning dictionaries
|
||||
"""
|
||||
return self.data_validator.validate_dataset(dataset, schema)
|
||||
|
||||
def handle_missing_values(self, dataset: List[Dict[str, Any]], **strategy) -> List[Dict[str, Any]]:
|
||||
def handle_missing_values(
|
||||
self,
|
||||
dataset: List[Dict[str, Any]],
|
||||
strategy: str = "remove",
|
||||
**options
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Handle missing values in dataset.
|
||||
|
||||
This method processes missing values in the dataset using the specified
|
||||
strategy (remove, fill, or impute).
|
||||
|
||||
Args:
|
||||
dataset: List of data records
|
||||
**strategy: Missing value handling strategy
|
||||
dataset: List of data record dictionaries
|
||||
strategy: Handling strategy:
|
||||
- "remove": Remove records with missing values (default)
|
||||
- "fill": Fill missing values with default value
|
||||
- "impute": Impute missing values using statistical methods
|
||||
**options: Additional strategy options:
|
||||
- fill_value: Value to use for filling (for "fill" strategy)
|
||||
- method: Imputation method ("mean", "median", "mode", "zero")
|
||||
|
||||
Returns:
|
||||
Processed dataset
|
||||
list: Processed dataset with missing values handled
|
||||
"""
|
||||
return self.missing_value_handler.handle_missing_values(dataset, **strategy)
|
||||
return self.missing_value_handler.handle_missing_values(dataset, strategy=strategy, **options)
|
||||
|
||||
|
||||
class DuplicateDetector:
|
||||
"""
|
||||
Duplicate detection engine.
|
||||
|
||||
• Detects duplicate records
|
||||
• Calculates similarity scores
|
||||
• Handles fuzzy matching
|
||||
• Manages duplicate resolution
|
||||
This class provides duplicate detection capabilities using similarity
|
||||
matching and fuzzy comparison algorithms.
|
||||
|
||||
Features:
|
||||
- Duplicate record detection
|
||||
- Similarity score calculation
|
||||
- Fuzzy string matching
|
||||
- Duplicate group formation
|
||||
- Duplicate resolution strategies
|
||||
|
||||
Example Usage:
|
||||
>>> detector = DuplicateDetector(similarity_threshold=0.8)
|
||||
>>> duplicates = detector.detect_duplicates(dataset, threshold=0.85)
|
||||
>>> resolved = detector.resolve_duplicates(duplicates, strategy="merge")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize duplicate detector.
|
||||
|
||||
Sets up the detector with similarity threshold and key fields for
|
||||
comparison.
|
||||
|
||||
Args:
|
||||
**config: Configuration options:
|
||||
- similarity_threshold: Minimum similarity for duplicates (default: 0.8)
|
||||
- key_fields: Fields to use for comparison
|
||||
- similarity_threshold: Minimum similarity for duplicates
|
||||
(default: 0.8, range: 0.0 to 1.0)
|
||||
- key_fields: List of field names to use for comparison
|
||||
(optional, uses all common fields if empty)
|
||||
"""
|
||||
self.logger = get_logger("duplicate_detector")
|
||||
self.config = config
|
||||
self.similarity_threshold = config.get("similarity_threshold", 0.8)
|
||||
self.key_fields = config.get("key_fields", [])
|
||||
|
||||
self.logger.debug(f"Duplicate detector initialized (threshold={self.similarity_threshold})")
|
||||
|
||||
def detect_duplicates(self, dataset: List[Dict[str, Any]], **criteria) -> List[DuplicateGroup]:
|
||||
def detect_duplicates(
|
||||
self,
|
||||
dataset: List[Dict[str, Any]],
|
||||
threshold: Optional[float] = None,
|
||||
key_fields: Optional[List[str]] = None,
|
||||
**criteria
|
||||
) -> List[DuplicateGroup]:
|
||||
"""
|
||||
Detect duplicates in dataset.
|
||||
|
||||
This method identifies duplicate records by comparing records pairwise
|
||||
using similarity matching. Records with similarity above the threshold
|
||||
are grouped together.
|
||||
|
||||
Args:
|
||||
dataset: List of records
|
||||
**criteria: Detection criteria
|
||||
dataset: List of record dictionaries
|
||||
threshold: Similarity threshold for duplicates (optional, uses
|
||||
instance threshold if not provided)
|
||||
key_fields: List of field names for comparison (optional, uses
|
||||
instance key_fields if not provided)
|
||||
**criteria: Additional detection criteria (unused)
|
||||
|
||||
Returns:
|
||||
List of duplicate groups
|
||||
list: List of DuplicateGroup objects containing duplicate records
|
||||
with similarity scores
|
||||
"""
|
||||
threshold = criteria.get("threshold", self.similarity_threshold)
|
||||
key_fields = criteria.get("key_fields", self.key_fields)
|
||||
threshold = threshold if threshold is not None else self.similarity_threshold
|
||||
key_fields = key_fields if key_fields is not None else self.key_fields
|
||||
|
||||
duplicate_groups = []
|
||||
processed = set()
|
||||
@@ -229,19 +366,31 @@ class DuplicateDetector:
|
||||
|
||||
return duplicate_groups
|
||||
|
||||
def calculate_similarity(self, record1: Dict[str, Any], record2: Dict[str, Any], **options) -> float:
|
||||
def calculate_similarity(
|
||||
self,
|
||||
record1: Dict[str, Any],
|
||||
record2: Dict[str, Any],
|
||||
key_fields: Optional[List[str]] = None,
|
||||
**options
|
||||
) -> float:
|
||||
"""
|
||||
Calculate similarity between records.
|
||||
|
||||
This method calculates a similarity score between two records by
|
||||
comparing values in key fields. Uses exact matching for non-string
|
||||
values and string similarity for string values.
|
||||
|
||||
Args:
|
||||
record1: First record
|
||||
record2: Second record
|
||||
**options: Similarity calculation options
|
||||
record1: First record dictionary
|
||||
record2: Second record dictionary
|
||||
key_fields: List of field names to compare (optional, uses
|
||||
instance key_fields or all common fields)
|
||||
**options: Additional similarity calculation options (unused)
|
||||
|
||||
Returns:
|
||||
Similarity score (0.0 to 1.0)
|
||||
float: Similarity score between 0.0 and 1.0 (higher is more similar)
|
||||
"""
|
||||
key_fields = options.get("key_fields", self.key_fields)
|
||||
key_fields = key_fields if key_fields is not None else self.key_fields
|
||||
|
||||
if not key_fields:
|
||||
# Use all common fields
|
||||
@@ -273,7 +422,19 @@ class DuplicateDetector:
|
||||
return sum(similarities) / len(similarities) if similarities else 0.0
|
||||
|
||||
def _string_similarity(self, s1: str, s2: str) -> float:
|
||||
"""Calculate string similarity using simple ratio."""
|
||||
"""
|
||||
Calculate string similarity using character overlap.
|
||||
|
||||
This method calculates similarity between two strings using character
|
||||
set intersection over union (Jaccard-like similarity).
|
||||
|
||||
Args:
|
||||
s1: First string
|
||||
s2: Second string
|
||||
|
||||
Returns:
|
||||
float: Similarity score between 0.0 and 1.0
|
||||
"""
|
||||
if not s1 or not s2:
|
||||
return 0.0
|
||||
|
||||
@@ -296,19 +457,30 @@ class DuplicateDetector:
|
||||
|
||||
return intersection / union if union > 0 else 0.0
|
||||
|
||||
def resolve_duplicates(self, duplicate_groups: List[DuplicateGroup], **strategy) -> List[Dict[str, Any]]:
|
||||
def resolve_duplicates(
|
||||
self,
|
||||
duplicate_groups: List[DuplicateGroup],
|
||||
strategy: str = "keep_first",
|
||||
**options
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Resolve duplicate groups.
|
||||
|
||||
This method resolves duplicate groups by applying a resolution strategy,
|
||||
returning a single record per group.
|
||||
|
||||
Args:
|
||||
duplicate_groups: List of duplicate groups
|
||||
**strategy: Resolution strategy
|
||||
duplicate_groups: List of DuplicateGroup objects
|
||||
strategy: Resolution strategy:
|
||||
- "keep_first": Keep first record in group (default)
|
||||
- "merge": Merge all records into one
|
||||
**options: Additional resolution options (unused)
|
||||
|
||||
Returns:
|
||||
List of resolved records
|
||||
list: List of resolved record dictionaries (one per duplicate group)
|
||||
"""
|
||||
resolved = []
|
||||
strategy_type = strategy.get("strategy", "keep_first")
|
||||
strategy_type = strategy
|
||||
|
||||
for group in duplicate_groups:
|
||||
if strategy_type == "keep_first":
|
||||
@@ -322,7 +494,18 @@ class DuplicateDetector:
|
||||
return resolved
|
||||
|
||||
def _merge_records(self, records: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""Merge multiple records into one."""
|
||||
"""
|
||||
Merge multiple records into one.
|
||||
|
||||
This method merges multiple records by combining their fields, keeping
|
||||
the first non-null value for each field.
|
||||
|
||||
Args:
|
||||
records: List of record dictionaries to merge
|
||||
|
||||
Returns:
|
||||
dict: Merged record dictionary
|
||||
"""
|
||||
merged = {}
|
||||
|
||||
for record in records:
|
||||
@@ -340,32 +523,56 @@ class DataValidator:
|
||||
"""
|
||||
Data validation engine.
|
||||
|
||||
• Validates data integrity
|
||||
• Checks data types and formats
|
||||
• Validates constraints
|
||||
• Handles validation errors
|
||||
This class provides data validation capabilities, checking data integrity,
|
||||
types, formats, and constraints against schemas.
|
||||
|
||||
Features:
|
||||
- Data integrity validation
|
||||
- Data type checking
|
||||
- Format validation
|
||||
- Constraint validation
|
||||
- Error and warning reporting
|
||||
|
||||
Example Usage:
|
||||
>>> validator = DataValidator()
|
||||
>>> result = validator.validate_dataset(dataset, schema)
|
||||
>>> if not result.valid:
|
||||
... print(f"Errors: {result.errors}")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize data validator.
|
||||
|
||||
Sets up the validator with configuration options.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("data_validator")
|
||||
self.config = config
|
||||
|
||||
self.logger.debug("Data validator initialized")
|
||||
|
||||
def validate_dataset(self, dataset: List[Dict[str, Any]], schema: Optional[Dict[str, Any]] = None) -> ValidationResult:
|
||||
def validate_dataset(
|
||||
self,
|
||||
dataset: List[Dict[str, Any]],
|
||||
schema: Optional[Dict[str, Any]] = None
|
||||
) -> ValidationResult:
|
||||
"""
|
||||
Validate entire dataset.
|
||||
|
||||
This method validates all records in the dataset against the provided
|
||||
schema, collecting errors and warnings for each record.
|
||||
|
||||
Args:
|
||||
dataset: List of records
|
||||
schema: Validation schema
|
||||
dataset: List of record dictionaries to validate
|
||||
schema: Validation schema dictionary (optional) containing:
|
||||
- fields: Dictionary mapping field names to field schemas
|
||||
|
||||
Returns:
|
||||
Validation result
|
||||
ValidationResult: Validation result with errors and warnings
|
||||
aggregated across all records
|
||||
"""
|
||||
errors = []
|
||||
warnings = []
|
||||
@@ -388,16 +595,26 @@ class DataValidator:
|
||||
warnings=warnings
|
||||
)
|
||||
|
||||
def validate_record(self, record: Dict[str, Any], schema: Optional[Dict[str, Any]] = None) -> ValidationResult:
|
||||
def validate_record(
|
||||
self,
|
||||
record: Dict[str, Any],
|
||||
schema: Optional[Dict[str, Any]] = None
|
||||
) -> ValidationResult:
|
||||
"""
|
||||
Validate individual record.
|
||||
|
||||
This method validates a single record against the provided schema,
|
||||
checking required fields and data types.
|
||||
|
||||
Args:
|
||||
record: Record to validate
|
||||
schema: Validation schema
|
||||
record: Record dictionary to validate
|
||||
schema: Validation schema dictionary (optional) containing:
|
||||
- fields: Dictionary mapping field names to field schemas with:
|
||||
- type: Expected data type (str, int, float, bool, list, dict)
|
||||
- required: Whether field is required (bool)
|
||||
|
||||
Returns:
|
||||
Validation result
|
||||
ValidationResult: Validation result for this record
|
||||
"""
|
||||
errors = []
|
||||
warnings = []
|
||||
@@ -429,16 +646,27 @@ class DataValidator:
|
||||
warnings=warnings
|
||||
)
|
||||
|
||||
def check_data_types(self, data: Any, expected_types: Union[type, List[type]]) -> bool:
|
||||
def check_data_types(
|
||||
self,
|
||||
data: Any,
|
||||
expected_types: Union[type, List[type], str, List[str]]
|
||||
) -> bool:
|
||||
"""
|
||||
Check data types against expected types.
|
||||
|
||||
This method validates that data matches one of the expected types.
|
||||
Supports both type objects and type name strings.
|
||||
|
||||
Args:
|
||||
data: Data to check
|
||||
expected_types: Expected type(s)
|
||||
data: Data value to check
|
||||
expected_types: Expected type(s) - can be:
|
||||
- Single type object (e.g., str, int)
|
||||
- List of type objects
|
||||
- Type name string (e.g., "str", "int")
|
||||
- List of type name strings
|
||||
|
||||
Returns:
|
||||
True if type matches
|
||||
bool: True if data type matches one of the expected types, False otherwise
|
||||
"""
|
||||
if isinstance(expected_types, type):
|
||||
expected_types = [expected_types]
|
||||
@@ -469,33 +697,54 @@ class MissingValueHandler:
|
||||
"""
|
||||
Missing value processing engine.
|
||||
|
||||
• Identifies missing values
|
||||
• Applies handling strategies
|
||||
• Fills missing data
|
||||
• Removes incomplete records
|
||||
This class provides missing value handling capabilities, including
|
||||
identification, removal, filling, and imputation strategies.
|
||||
|
||||
Features:
|
||||
- Missing value identification
|
||||
- Multiple handling strategies (remove, fill, impute)
|
||||
- Statistical imputation (mean, median, mode)
|
||||
- Missing value statistics
|
||||
|
||||
Example Usage:
|
||||
>>> handler = MissingValueHandler()
|
||||
>>> missing_info = handler.identify_missing_values(dataset)
|
||||
>>> processed = handler.handle_missing_values(dataset, strategy="impute", method="mean")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize missing value handler.
|
||||
|
||||
Sets up the handler with configuration and missing value definitions.
|
||||
|
||||
Args:
|
||||
**config: Configuration options:
|
||||
- missing_values: List of values considered missing (default: [None, "", "N/A", "null"])
|
||||
- missing_values: List of values considered missing
|
||||
(default: [None, "", "N/A", "null", "NULL"])
|
||||
"""
|
||||
self.logger = get_logger("missing_value_handler")
|
||||
self.config = config
|
||||
self.missing_values = config.get("missing_values", [None, "", "N/A", "null", "NULL"])
|
||||
|
||||
self.logger.debug("Missing value handler initialized")
|
||||
|
||||
def identify_missing_values(self, dataset: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""
|
||||
Identify missing values in dataset.
|
||||
|
||||
This method analyzes the dataset to identify missing values across
|
||||
all fields, providing counts and percentages.
|
||||
|
||||
Args:
|
||||
dataset: List of records
|
||||
dataset: List of record dictionaries
|
||||
|
||||
Returns:
|
||||
Missing value information
|
||||
dict: Missing value information containing:
|
||||
- total_records: Total number of records
|
||||
- missing_counts: Dictionary mapping field names to missing counts
|
||||
- missing_percentages: Dictionary mapping field names to
|
||||
missing percentages (0.0 to 100.0)
|
||||
"""
|
||||
missing_info = defaultdict(int)
|
||||
total_records = len(dataset)
|
||||
@@ -524,28 +773,55 @@ class MissingValueHandler:
|
||||
}
|
||||
}
|
||||
|
||||
def handle_missing_values(self, dataset: List[Dict[str, Any]], strategy: str = "remove") -> List[Dict[str, Any]]:
|
||||
def handle_missing_values(
|
||||
self,
|
||||
dataset: List[Dict[str, Any]],
|
||||
strategy: str = "remove",
|
||||
**options
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Handle missing values using specified strategy.
|
||||
|
||||
This method processes missing values in the dataset using the specified
|
||||
strategy: remove records, fill with default values, or impute using
|
||||
statistical methods.
|
||||
|
||||
Args:
|
||||
dataset: List of records
|
||||
strategy: Handling strategy ('remove', 'fill', 'impute')
|
||||
dataset: List of record dictionaries
|
||||
strategy: Handling strategy:
|
||||
- "remove": Remove records with any missing values (default)
|
||||
- "fill": Fill missing values with default value
|
||||
- "impute": Impute missing values using statistical methods
|
||||
**options: Strategy-specific options:
|
||||
- fill_value: Value to use for filling (for "fill" strategy)
|
||||
- method: Imputation method for "impute" strategy
|
||||
("mean", "median", "mode", "zero")
|
||||
|
||||
Returns:
|
||||
Processed dataset
|
||||
list: Processed dataset with missing values handled
|
||||
"""
|
||||
if strategy == "remove":
|
||||
return self._remove_missing(dataset)
|
||||
elif strategy == "fill":
|
||||
return self._fill_missing(dataset)
|
||||
return self._fill_missing(dataset, fill_value=options.get("fill_value", ""))
|
||||
elif strategy == "impute":
|
||||
return self.impute_values(dataset)
|
||||
return self.impute_values(dataset, method=options.get("method", "mean"))
|
||||
else:
|
||||
return dataset
|
||||
|
||||
def _remove_missing(self, dataset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""Remove records with missing values."""
|
||||
"""
|
||||
Remove records with missing values.
|
||||
|
||||
This method filters out records that contain any missing values
|
||||
(as defined in missing_values list).
|
||||
|
||||
Args:
|
||||
dataset: List of record dictionaries
|
||||
|
||||
Returns:
|
||||
list: Dataset with records containing missing values removed
|
||||
"""
|
||||
return [
|
||||
record for record in dataset
|
||||
if not any(
|
||||
@@ -554,8 +830,26 @@ class MissingValueHandler:
|
||||
)
|
||||
]
|
||||
|
||||
def _fill_missing(self, dataset: List[Dict[str, Any]], fill_value: Any = "") -> List[Dict[str, Any]]:
|
||||
"""Fill missing values with default value."""
|
||||
def _fill_missing(
|
||||
self,
|
||||
dataset: List[Dict[str, Any]],
|
||||
fill_value: Optional[Any] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Fill missing values with default value.
|
||||
|
||||
This method replaces all missing values in records with the specified
|
||||
fill value.
|
||||
|
||||
Args:
|
||||
dataset: List of record dictionaries
|
||||
fill_value: Value to use for filling missing values (default: "")
|
||||
|
||||
Returns:
|
||||
list: Dataset with missing values filled
|
||||
"""
|
||||
if fill_value is None:
|
||||
fill_value = ""
|
||||
filled = []
|
||||
for record in dataset:
|
||||
filled_record = {}
|
||||
@@ -567,16 +861,27 @@ class MissingValueHandler:
|
||||
filled.append(filled_record)
|
||||
return filled
|
||||
|
||||
def impute_values(self, dataset: List[Dict[str, Any]], method: str = "mean") -> List[Dict[str, Any]]:
|
||||
def impute_values(
|
||||
self,
|
||||
dataset: List[Dict[str, Any]],
|
||||
method: str = "mean"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Impute missing values using specified method.
|
||||
|
||||
This method imputes missing numeric values using statistical methods
|
||||
(mean, median, mode, or zero). Only numeric fields are imputed.
|
||||
|
||||
Args:
|
||||
dataset: List of records
|
||||
method: Imputation method ('mean', 'median', 'mode', 'zero')
|
||||
dataset: List of record dictionaries
|
||||
method: Imputation method:
|
||||
- "mean": Use mean of non-missing values (default)
|
||||
- "median": Use median of non-missing values
|
||||
- "zero": Use zero
|
||||
- "mode": Use mode (most frequent value)
|
||||
|
||||
Returns:
|
||||
Imputed dataset
|
||||
list: Dataset with missing numeric values imputed
|
||||
"""
|
||||
if not dataset:
|
||||
return dataset
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
"""
|
||||
Date and Time Normalization Module
|
||||
|
||||
Handles normalization of dates, times, and temporal expressions.
|
||||
This module provides comprehensive date and time normalization capabilities
|
||||
for the Semantica framework, enabling standardization of temporal data
|
||||
across various formats and conventions.
|
||||
|
||||
Key Features:
|
||||
- Date format standardization
|
||||
- Time zone normalization
|
||||
- Relative date processing
|
||||
- Temporal expression parsing
|
||||
- Date range handling
|
||||
- Date format standardization (ISO8601, custom formats)
|
||||
- Time zone normalization and UTC conversion
|
||||
- Relative date processing ("yesterday", "3 days ago", etc.)
|
||||
- Temporal expression parsing (natural language dates)
|
||||
- Date range handling ("from X to Y")
|
||||
- Support for multiple calendar systems
|
||||
- Optional dateutil integration for advanced parsing
|
||||
|
||||
Main Classes:
|
||||
- DateNormalizer: Main date normalization class
|
||||
- TimeZoneNormalizer: Time zone processing
|
||||
- RelativeDateProcessor: Relative date handling
|
||||
- DateNormalizer: Main date normalization coordinator
|
||||
- TimeZoneNormalizer: Time zone processing engine
|
||||
- RelativeDateProcessor: Relative date handling engine
|
||||
- TemporalExpressionParser: Temporal expression parser
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.normalize import DateNormalizer
|
||||
>>> normalizer = DateNormalizer()
|
||||
>>> normalized = normalizer.normalize_date("2023-01-15", format="ISO8601")
|
||||
>>> relative = normalizer.process_relative_date("3 days ago")
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
import re
|
||||
@@ -37,23 +50,36 @@ except ImportError:
|
||||
|
||||
class DateNormalizer:
|
||||
"""
|
||||
Date and time normalization handler.
|
||||
Date and time normalization coordinator.
|
||||
|
||||
• Normalizes dates and times to standard formats
|
||||
• Handles various date formats and conventions
|
||||
• Processes time zones and UTC conversion
|
||||
• Manages relative dates and temporal expressions
|
||||
• Standardizes date representations
|
||||
• Supports multiple calendar systems
|
||||
This class provides comprehensive date and time normalization capabilities,
|
||||
coordinating timezone normalization, relative date processing, and temporal
|
||||
expression parsing.
|
||||
|
||||
Features:
|
||||
- Date and time format standardization
|
||||
- Multiple date format support
|
||||
- Timezone normalization and UTC conversion
|
||||
- Relative date processing
|
||||
- Temporal expression parsing
|
||||
- Date range handling
|
||||
|
||||
Example Usage:
|
||||
>>> normalizer = DateNormalizer()
|
||||
>>> normalized = normalizer.normalize_date("2023-01-15", format="ISO8601")
|
||||
>>> relative = normalizer.process_relative_date("3 days ago")
|
||||
"""
|
||||
|
||||
def __init__(self, config=None, **kwargs):
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
"""
|
||||
Initialize date normalizer.
|
||||
|
||||
Sets up the normalizer with timezone normalizer, relative date processor,
|
||||
and temporal expression parser components.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary
|
||||
**kwargs: Additional configuration options
|
||||
config: Configuration dictionary (optional)
|
||||
**kwargs: Additional configuration options (merged into config)
|
||||
"""
|
||||
self.logger = get_logger("date_normalizer")
|
||||
self.config = config or {}
|
||||
@@ -62,19 +88,38 @@ class DateNormalizer:
|
||||
self.timezone_normalizer = TimeZoneNormalizer(**self.config)
|
||||
self.relative_date_processor = RelativeDateProcessor(**self.config)
|
||||
self.temporal_parser = TemporalExpressionParser(**self.config)
|
||||
|
||||
self.logger.debug("Date normalizer initialized")
|
||||
|
||||
def normalize_date(self, date_input: Any, **options) -> str:
|
||||
def normalize_date(
|
||||
self,
|
||||
date_input: Any,
|
||||
format: str = "ISO8601",
|
||||
timezone: str = "UTC",
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Normalize date to standard format.
|
||||
|
||||
This method normalizes date input (string or datetime) to a standard
|
||||
format, handling timezone conversion and relative date expressions.
|
||||
|
||||
Args:
|
||||
date_input: Date input (string, datetime, or other)
|
||||
**options: Normalization options:
|
||||
- format: Output format (default: 'ISO8601')
|
||||
- timezone: Target timezone (default: 'UTC')
|
||||
date_input: Date input - can be:
|
||||
- String (e.g., "2023-01-15", "yesterday")
|
||||
- datetime object
|
||||
format: Output format (default: "ISO8601"):
|
||||
- "ISO8601": ISO 8601 format (e.g., "2023-01-15T10:30:00")
|
||||
- "date": Date only (e.g., "2023-01-15")
|
||||
- Custom format string (strftime format)
|
||||
timezone: Target timezone (default: "UTC")
|
||||
**options: Additional normalization options (unused)
|
||||
|
||||
Returns:
|
||||
Normalized date string
|
||||
str: Normalized date string in specified format
|
||||
|
||||
Raises:
|
||||
ValidationError: If date input type is unsupported or parsing fails
|
||||
"""
|
||||
if not date_input:
|
||||
return ""
|
||||
@@ -96,14 +141,13 @@ class DateNormalizer:
|
||||
raise ValidationError(f"Unsupported date input type: {type(date_input)}")
|
||||
|
||||
# Normalize timezone
|
||||
target_tz = options.get("timezone", "UTC")
|
||||
if target_tz != "UTC":
|
||||
dt = self.timezone_normalizer.normalize_timezone(dt, target_tz)
|
||||
if timezone != "UTC":
|
||||
dt = self.timezone_normalizer.normalize_timezone(dt, timezone)
|
||||
else:
|
||||
dt = self.timezone_normalizer.convert_to_utc(dt)
|
||||
|
||||
# Format output
|
||||
output_format = options.get("format", "ISO8601")
|
||||
output_format = format
|
||||
if output_format == "ISO8601":
|
||||
return dt.isoformat()
|
||||
elif output_format == "date":
|
||||
@@ -115,12 +159,17 @@ class DateNormalizer:
|
||||
"""
|
||||
Normalize time to standard format.
|
||||
|
||||
This method normalizes time input to ISO format (HH:MM:SS).
|
||||
|
||||
Args:
|
||||
time_input: Time input
|
||||
**options: Normalization options
|
||||
time_input: Time input - can be:
|
||||
- String (e.g., "10:30:00", "10:30 AM")
|
||||
- datetime object
|
||||
**options: Additional normalization options (unused)
|
||||
|
||||
Returns:
|
||||
Normalized time string
|
||||
str: Normalized time string in ISO format (HH:MM:SS), or empty
|
||||
string if parsing fails
|
||||
"""
|
||||
if isinstance(time_input, str):
|
||||
try:
|
||||
@@ -138,29 +187,50 @@ class DateNormalizer:
|
||||
|
||||
return dt.time().isoformat()
|
||||
|
||||
def process_relative_date(self, relative_expression: str, reference_date: Optional[datetime] = None) -> datetime:
|
||||
def process_relative_date(
|
||||
self,
|
||||
relative_expression: str,
|
||||
reference_date: Optional[datetime] = None
|
||||
) -> datetime:
|
||||
"""
|
||||
Process relative date expressions.
|
||||
|
||||
This method processes relative date expressions like "yesterday",
|
||||
"3 days ago", "2 weeks from now", etc.
|
||||
|
||||
Args:
|
||||
relative_expression: Relative date expression
|
||||
reference_date: Reference date (default: now)
|
||||
relative_expression: Relative date expression (e.g., "yesterday",
|
||||
"3 days ago", "2 weeks from now")
|
||||
reference_date: Reference date for calculation (default: current
|
||||
datetime)
|
||||
|
||||
Returns:
|
||||
Calculated date
|
||||
datetime: Calculated absolute date
|
||||
"""
|
||||
return self.relative_date_processor.process_relative_expression(relative_expression, reference_date)
|
||||
|
||||
def parse_temporal_expression(self, temporal_text: str, **context) -> Dict[str, Any]:
|
||||
def parse_temporal_expression(
|
||||
self,
|
||||
temporal_text: str,
|
||||
**context
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Parse temporal expressions and references.
|
||||
|
||||
This method parses natural language temporal expressions, extracting
|
||||
date, time, and range information.
|
||||
|
||||
Args:
|
||||
temporal_text: Temporal expression text
|
||||
**context: Context information
|
||||
temporal_text: Temporal expression text (e.g., "from January to
|
||||
March", "last week")
|
||||
**context: Context information (optional)
|
||||
|
||||
Returns:
|
||||
Parsed temporal data
|
||||
dict: Parsed temporal data containing:
|
||||
- date: Date string (if found)
|
||||
- time: Time string (if found)
|
||||
- range: Range dictionary with start/end (if found)
|
||||
- relative: Whether expression is relative (bool)
|
||||
"""
|
||||
return self.temporal_parser.parse_temporal_expression(temporal_text, **context)
|
||||
|
||||
@@ -169,33 +239,55 @@ class TimeZoneNormalizer:
|
||||
"""
|
||||
Time zone normalization engine.
|
||||
|
||||
• Handles time zone conversion and normalization
|
||||
• Manages UTC conversion
|
||||
• Processes time zone abbreviations
|
||||
• Handles daylight saving time
|
||||
• Manages time zone databases
|
||||
This class provides timezone conversion and normalization capabilities,
|
||||
handling UTC conversion, timezone abbreviations, and daylight saving
|
||||
time transitions.
|
||||
|
||||
Features:
|
||||
- Timezone conversion and normalization
|
||||
- UTC conversion
|
||||
- Timezone abbreviation processing
|
||||
- Daylight saving time handling
|
||||
- ZoneInfo integration (Python 3.9+)
|
||||
|
||||
Example Usage:
|
||||
>>> tz_normalizer = TimeZoneNormalizer()
|
||||
>>> utc_dt = tz_normalizer.convert_to_utc(datetime_obj)
|
||||
>>> normalized = tz_normalizer.normalize_timezone(dt, "America/New_York")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize time zone normalizer.
|
||||
|
||||
Sets up the normalizer with configuration options.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("timezone_normalizer")
|
||||
self.config = config
|
||||
|
||||
self.logger.debug("Time zone normalizer initialized")
|
||||
|
||||
def normalize_timezone(self, datetime_obj: datetime, target_timezone: str = "UTC") -> datetime:
|
||||
def normalize_timezone(
|
||||
self,
|
||||
datetime_obj: datetime,
|
||||
target_timezone: str = "UTC"
|
||||
) -> datetime:
|
||||
"""
|
||||
Normalize datetime to target timezone.
|
||||
|
||||
This method converts a datetime object to the specified timezone,
|
||||
using ZoneInfo if available (Python 3.9+), or falling back to UTC.
|
||||
|
||||
Args:
|
||||
datetime_obj: Datetime object
|
||||
target_timezone: Target timezone
|
||||
datetime_obj: Datetime object to normalize
|
||||
target_timezone: Target timezone string (e.g., "America/New_York",
|
||||
"UTC", "Europe/London")
|
||||
|
||||
Returns:
|
||||
Normalized datetime
|
||||
datetime: Normalized datetime in target timezone
|
||||
"""
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
@@ -207,16 +299,25 @@ class TimeZoneNormalizer:
|
||||
# Fallback if zoneinfo not available
|
||||
return datetime_obj
|
||||
|
||||
def convert_to_utc(self, datetime_obj: datetime, source_timezone: Optional[str] = None) -> datetime:
|
||||
def convert_to_utc(
|
||||
self,
|
||||
datetime_obj: datetime,
|
||||
source_timezone: Optional[str] = None
|
||||
) -> datetime:
|
||||
"""
|
||||
Convert datetime to UTC.
|
||||
|
||||
This method converts a datetime object to UTC timezone. If the datetime
|
||||
has no timezone info, it assumes UTC or uses the source_timezone if
|
||||
provided.
|
||||
|
||||
Args:
|
||||
datetime_obj: Datetime object
|
||||
source_timezone: Source timezone (auto-detect if None)
|
||||
datetime_obj: Datetime object to convert
|
||||
source_timezone: Source timezone string (optional, used if datetime
|
||||
has no timezone info)
|
||||
|
||||
Returns:
|
||||
UTC datetime
|
||||
datetime: Datetime object in UTC timezone
|
||||
"""
|
||||
if datetime_obj.tzinfo is None:
|
||||
if source_timezone:
|
||||
@@ -226,16 +327,23 @@ class TimeZoneNormalizer:
|
||||
|
||||
return datetime_obj.astimezone(timezone.utc)
|
||||
|
||||
def handle_dst_transitions(self, datetime_obj: datetime, timezone_str: str) -> datetime:
|
||||
def handle_dst_transitions(
|
||||
self,
|
||||
datetime_obj: datetime,
|
||||
timezone_str: str
|
||||
) -> datetime:
|
||||
"""
|
||||
Handle daylight saving time transitions.
|
||||
|
||||
This method handles DST transitions by normalizing the datetime to
|
||||
the specified timezone, which automatically accounts for DST.
|
||||
|
||||
Args:
|
||||
datetime_obj: Datetime object
|
||||
timezone_str: Timezone string
|
||||
timezone_str: Timezone string (e.g., "America/New_York")
|
||||
|
||||
Returns:
|
||||
Adjusted datetime
|
||||
datetime: Adjusted datetime accounting for DST
|
||||
"""
|
||||
return self.normalize_timezone(datetime_obj, timezone_str)
|
||||
|
||||
@@ -244,18 +352,30 @@ class RelativeDateProcessor:
|
||||
"""
|
||||
Relative date processing engine.
|
||||
|
||||
• Processes relative date expressions
|
||||
• Calculates absolute dates from relative terms
|
||||
• Handles various relative formats
|
||||
• Manages date arithmetic
|
||||
This class provides relative date expression processing, converting
|
||||
natural language relative dates (e.g., "yesterday", "3 days ago")
|
||||
into absolute datetime objects.
|
||||
|
||||
Features:
|
||||
- Relative date expression processing
|
||||
- Natural language date parsing
|
||||
- Date arithmetic (days, weeks, months, years)
|
||||
- Support for common relative terms
|
||||
|
||||
Example Usage:
|
||||
>>> processor = RelativeDateProcessor()
|
||||
>>> date = processor.process_relative_expression("3 days ago")
|
||||
>>> offset = processor.calculate_date_offset("2 weeks from now", ref_date)
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize relative date processor.
|
||||
|
||||
Sets up the processor with relative terms dictionary and configuration.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("relative_date_processor")
|
||||
self.config = config
|
||||
@@ -266,17 +386,29 @@ class RelativeDateProcessor:
|
||||
"tomorrow": 1,
|
||||
"now": 0,
|
||||
}
|
||||
|
||||
self.logger.debug("Relative date processor initialized")
|
||||
|
||||
def process_relative_expression(self, expression: str, reference_date: Optional[datetime] = None) -> datetime:
|
||||
def process_relative_expression(
|
||||
self,
|
||||
expression: str,
|
||||
reference_date: Optional[datetime] = None
|
||||
) -> datetime:
|
||||
"""
|
||||
Process relative date expression.
|
||||
|
||||
This method processes relative date expressions like "yesterday",
|
||||
"3 days ago", "2 weeks from now", etc., converting them to absolute
|
||||
datetime objects.
|
||||
|
||||
Args:
|
||||
expression: Relative date expression
|
||||
reference_date: Reference date (default: now)
|
||||
expression: Relative date expression (e.g., "yesterday",
|
||||
"3 days ago", "2 weeks from now")
|
||||
reference_date: Reference date for calculation (default: current
|
||||
datetime)
|
||||
|
||||
Returns:
|
||||
Calculated date
|
||||
datetime: Calculated absolute date
|
||||
"""
|
||||
if reference_date is None:
|
||||
reference_date = datetime.now()
|
||||
@@ -331,29 +463,44 @@ class RelativeDateProcessor:
|
||||
except Exception:
|
||||
return reference_date
|
||||
|
||||
def calculate_date_offset(self, expression: str, reference_date: datetime) -> datetime:
|
||||
def calculate_date_offset(
|
||||
self,
|
||||
expression: str,
|
||||
reference_date: datetime
|
||||
) -> datetime:
|
||||
"""
|
||||
Calculate date offset from expression.
|
||||
|
||||
This method calculates a date offset from a relative expression,
|
||||
using the provided reference date.
|
||||
|
||||
Args:
|
||||
expression: Offset expression
|
||||
reference_date: Reference date
|
||||
expression: Offset expression (e.g., "3 days ago",
|
||||
"2 weeks from now")
|
||||
reference_date: Reference date for calculation
|
||||
|
||||
Returns:
|
||||
Calculated date
|
||||
datetime: Calculated date with offset applied
|
||||
"""
|
||||
return self.process_relative_expression(expression, reference_date)
|
||||
|
||||
def handle_relative_terms(self, term: str, reference_date: datetime) -> datetime:
|
||||
def handle_relative_terms(
|
||||
self,
|
||||
term: str,
|
||||
reference_date: datetime
|
||||
) -> datetime:
|
||||
"""
|
||||
Handle specific relative terms.
|
||||
|
||||
This method processes specific relative terms like "today", "yesterday",
|
||||
"tomorrow", using the provided reference date.
|
||||
|
||||
Args:
|
||||
term: Relative term
|
||||
reference_date: Reference date
|
||||
term: Relative term (e.g., "today", "yesterday", "tomorrow")
|
||||
reference_date: Reference date for calculation
|
||||
|
||||
Returns:
|
||||
Calculated date
|
||||
datetime: Calculated date based on relative term
|
||||
"""
|
||||
return self.process_relative_expression(term, reference_date)
|
||||
|
||||
@@ -362,32 +509,57 @@ class TemporalExpressionParser:
|
||||
"""
|
||||
Temporal expression parsing engine.
|
||||
|
||||
• Parses natural language temporal expressions
|
||||
• Extracts date and time components
|
||||
• Handles complex temporal references
|
||||
• Processes temporal ranges and periods
|
||||
This class provides natural language temporal expression parsing,
|
||||
extracting date, time, and range information from text.
|
||||
|
||||
Features:
|
||||
- Natural language temporal expression parsing
|
||||
- Date and time component extraction
|
||||
- Temporal range processing
|
||||
- Complex temporal reference handling
|
||||
|
||||
Example Usage:
|
||||
>>> parser = TemporalExpressionParser()
|
||||
>>> result = parser.parse_temporal_expression("from January to March")
|
||||
>>> date_components = parser.extract_date_components("2023-01-15")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize temporal expression parser.
|
||||
|
||||
Sets up the parser with configuration options.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("temporal_expression_parser")
|
||||
self.config = config
|
||||
|
||||
self.logger.debug("Temporal expression parser initialized")
|
||||
|
||||
def parse_temporal_expression(self, text: str, **context) -> Dict[str, Any]:
|
||||
def parse_temporal_expression(
|
||||
self,
|
||||
text: str,
|
||||
**context
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Parse temporal expression from text.
|
||||
|
||||
This method parses natural language temporal expressions, extracting
|
||||
date, time, and range information.
|
||||
|
||||
Args:
|
||||
text: Temporal expression text
|
||||
**context: Context information
|
||||
text: Temporal expression text (e.g., "from January to March",
|
||||
"last week", "2023-01-15")
|
||||
**context: Context information (optional)
|
||||
|
||||
Returns:
|
||||
Parsed temporal data
|
||||
dict: Parsed temporal data containing:
|
||||
- date: Date string (if found)
|
||||
- time: Time string (if found)
|
||||
- range: Range dictionary with start/end (if found)
|
||||
- relative: Whether expression is relative (bool)
|
||||
"""
|
||||
result = {
|
||||
"date": None,
|
||||
@@ -417,11 +589,19 @@ class TemporalExpressionParser:
|
||||
"""
|
||||
Extract date components from text.
|
||||
|
||||
This method extracts date components (year, month, day) from text
|
||||
using date parsing.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
text: Input text containing date information
|
||||
|
||||
Returns:
|
||||
Date components
|
||||
dict: Date components containing:
|
||||
- date: Date string in ISO format
|
||||
- year: Year (int)
|
||||
- month: Month (int, 1-12)
|
||||
- day: Day (int, 1-31)
|
||||
Returns empty dict if parsing fails
|
||||
"""
|
||||
try:
|
||||
if HAS_DATEUTIL and date_parser:
|
||||
@@ -442,11 +622,19 @@ class TemporalExpressionParser:
|
||||
"""
|
||||
Extract time components from text.
|
||||
|
||||
This method extracts time components (hour, minute, second) from
|
||||
text using date parsing.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
text: Input text containing time information
|
||||
|
||||
Returns:
|
||||
Time components
|
||||
dict: Time components containing:
|
||||
- time: Time string in ISO format (HH:MM:SS)
|
||||
- hour: Hour (int, 0-23)
|
||||
- minute: Minute (int, 0-59)
|
||||
- second: Second (int, 0-59)
|
||||
Returns empty dict if parsing fails
|
||||
"""
|
||||
try:
|
||||
if HAS_DATEUTIL and date_parser:
|
||||
@@ -467,11 +655,18 @@ class TemporalExpressionParser:
|
||||
"""
|
||||
Process temporal ranges and periods.
|
||||
|
||||
This method extracts temporal ranges from text patterns like
|
||||
"from X to Y" or "between X and Y".
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
text: Input text containing temporal range (e.g., "from January
|
||||
to March", "between 2023-01-01 and 2023-03-31")
|
||||
|
||||
Returns:
|
||||
Range information or None
|
||||
dict: Range information containing:
|
||||
- start: Start date string in ISO format
|
||||
- end: End date string in ISO format
|
||||
Returns None if no range is found
|
||||
"""
|
||||
# Look for range patterns like "from X to Y", "between X and Y"
|
||||
range_patterns = [
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
"""
|
||||
Encoding handling utilities for Semantica framework.
|
||||
Encoding Handling Module
|
||||
|
||||
This module provides encoding detection, conversion, and handling
|
||||
for UTF-8 conversion and BOM handling.
|
||||
This module provides comprehensive encoding detection, conversion, and handling
|
||||
capabilities for the Semantica framework, enabling robust text processing across
|
||||
various character encodings.
|
||||
|
||||
Key Features:
|
||||
- Encoding detection (chardet integration)
|
||||
- UTF-8 conversion with fallback support
|
||||
- BOM (Byte Order Mark) removal
|
||||
- File encoding detection and conversion
|
||||
- Encoding validation
|
||||
- Error handling strategies
|
||||
|
||||
Main Classes:
|
||||
- EncodingHandler: Encoding detection and conversion coordinator
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.normalize import EncodingHandler
|
||||
>>> handler = EncodingHandler()
|
||||
>>> encoding, confidence = handler.detect(data)
|
||||
>>> utf8_text = handler.convert_to_utf8(data)
|
||||
>>> content = handler.convert_file_to_utf8("input.txt", "output.txt")
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
import chardet
|
||||
@@ -14,30 +36,65 @@ from ..utils.logging import get_logger
|
||||
|
||||
|
||||
class EncodingHandler:
|
||||
"""Encoding handling utilities."""
|
||||
"""
|
||||
Encoding detection and conversion coordinator.
|
||||
|
||||
This class provides comprehensive encoding handling capabilities, including
|
||||
detection, conversion, BOM removal, and validation.
|
||||
|
||||
Features:
|
||||
- Encoding detection using chardet
|
||||
- UTF-8 conversion with fallback encodings
|
||||
- BOM removal (UTF-8, UTF-16)
|
||||
- File encoding detection and conversion
|
||||
- Encoding validation
|
||||
- Graceful error handling
|
||||
|
||||
Example Usage:
|
||||
>>> handler = EncodingHandler()
|
||||
>>> encoding, confidence = handler.detect(data)
|
||||
>>> utf8_text = handler.convert_to_utf8(data, source_encoding="latin-1")
|
||||
>>> content = handler.convert_file_to_utf8("input.txt")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize encoding handler.
|
||||
|
||||
Sets up the handler with default encoding and fallback encodings.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options:
|
||||
- default_encoding: Default encoding (default: "utf-8")
|
||||
- fallback_encodings: List of fallback encodings to try
|
||||
(default: ["latin-1", "cp1252", "iso-8859-1"])
|
||||
"""
|
||||
self.logger = get_logger("encoding_handler")
|
||||
self.config = config
|
||||
self.default_encoding = config.get("default_encoding", "utf-8")
|
||||
self.fallback_encodings = config.get("fallback_encodings", ["latin-1", "cp1252", "iso-8859-1"])
|
||||
|
||||
self.logger.debug(f"Encoding handler initialized (default={self.default_encoding})")
|
||||
|
||||
def detect(self, data: Union[str, bytes], **options) -> Tuple[str, float]:
|
||||
def detect(
|
||||
self,
|
||||
data: Union[str, bytes],
|
||||
**options
|
||||
) -> Tuple[str, float]:
|
||||
"""
|
||||
Detect encoding of data.
|
||||
|
||||
This method detects the character encoding of input data using chardet.
|
||||
If data is a string, it's first encoded to UTF-8 bytes for detection.
|
||||
|
||||
Args:
|
||||
data: Input data (string or bytes)
|
||||
**options: Detection options
|
||||
|
||||
data: Input data - can be string or bytes
|
||||
**options: Detection options (unused)
|
||||
|
||||
Returns:
|
||||
tuple: (encoding, confidence)
|
||||
tuple: (encoding_name, confidence_score) where:
|
||||
- encoding_name: Detected encoding name (e.g., "utf-8", "latin-1")
|
||||
- confidence_score: Confidence score between 0.0 and 1.0
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
@@ -57,25 +114,37 @@ class EncodingHandler:
|
||||
self.logger.warning(f"Failed to detect encoding: {e}")
|
||||
return (self.default_encoding, 0.0)
|
||||
|
||||
def detect_file(self, file_path: Union[str, Path], **options) -> Tuple[str, float]:
|
||||
def detect_file(
|
||||
self,
|
||||
file_path: Union[str, Path],
|
||||
sample_size: int = 10000,
|
||||
**options
|
||||
) -> Tuple[str, float]:
|
||||
"""
|
||||
Detect encoding of file.
|
||||
|
||||
This method detects the character encoding of a file by reading a sample
|
||||
of bytes and using chardet for detection.
|
||||
|
||||
Args:
|
||||
file_path: Path to file
|
||||
**options: Detection options
|
||||
|
||||
file_path: Path to file (string or Path object)
|
||||
sample_size: Number of bytes to read for detection (default: 10000)
|
||||
**options: Additional detection options (unused)
|
||||
|
||||
Returns:
|
||||
tuple: (encoding, confidence)
|
||||
tuple: (encoding_name, confidence_score) where:
|
||||
- encoding_name: Detected encoding name
|
||||
- confidence_score: Confidence score between 0.0 and 1.0
|
||||
|
||||
Raises:
|
||||
ValidationError: If file does not exist
|
||||
ProcessingError: If file reading or detection fails
|
||||
"""
|
||||
file_path = Path(file_path)
|
||||
|
||||
if not file_path.exists():
|
||||
raise ValidationError(f"File not found: {file_path}")
|
||||
|
||||
# Read sample bytes for detection
|
||||
sample_size = options.get("sample_size", 10000)
|
||||
|
||||
try:
|
||||
with open(file_path, 'rb') as f:
|
||||
sample = f.read(sample_size)
|
||||
@@ -83,19 +152,32 @@ class EncodingHandler:
|
||||
return self.detect(sample, **options)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to detect file encoding: {e}")
|
||||
raise ProcessingError(f"Failed to detect file encoding: {e}")
|
||||
raise ProcessingError(f"Failed to detect file encoding: {e}") from e
|
||||
|
||||
def convert_to_utf8(self, data: Union[str, bytes], source_encoding: Optional[str] = None, **options) -> str:
|
||||
def convert_to_utf8(
|
||||
self,
|
||||
data: Union[str, bytes],
|
||||
source_encoding: Optional[str] = None,
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Convert data to UTF-8.
|
||||
|
||||
This method converts input data (string or bytes) to UTF-8 encoding.
|
||||
If source_encoding is not provided, it's auto-detected. Falls back to
|
||||
multiple encodings if initial conversion fails.
|
||||
|
||||
Args:
|
||||
data: Input data
|
||||
source_encoding: Source encoding (auto-detected if None)
|
||||
**options: Conversion options
|
||||
|
||||
data: Input data - can be string or bytes
|
||||
source_encoding: Source encoding name (optional, auto-detected if None)
|
||||
**options: Additional conversion options (unused)
|
||||
|
||||
Returns:
|
||||
str: UTF-8 encoded string
|
||||
|
||||
Note:
|
||||
Uses 'replace' error handling strategy to handle invalid characters
|
||||
gracefully, replacing them with replacement characters.
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
# Already a string, just ensure it's valid UTF-8
|
||||
@@ -128,13 +210,21 @@ class EncodingHandler:
|
||||
"""
|
||||
Convert file to UTF-8.
|
||||
|
||||
This method reads a file, detects its encoding, converts it to UTF-8,
|
||||
and optionally writes it to an output file.
|
||||
|
||||
Args:
|
||||
file_path: Path to input file
|
||||
output_path: Path to output file (overwrites if None)
|
||||
**options: Conversion options
|
||||
|
||||
file_path: Path to input file (string or Path object)
|
||||
output_path: Path to output file (optional, if provided, writes
|
||||
converted content to this file)
|
||||
**options: Additional conversion options (unused)
|
||||
|
||||
Returns:
|
||||
str: Converted content as string
|
||||
str: Converted content as UTF-8 string
|
||||
|
||||
Raises:
|
||||
ValidationError: If input file does not exist
|
||||
ProcessingError: If file reading or conversion fails
|
||||
"""
|
||||
file_path = Path(file_path)
|
||||
|
||||
@@ -161,7 +251,7 @@ class EncodingHandler:
|
||||
|
||||
if content is None:
|
||||
self.logger.error(f"Failed to read file with any encoding: {e}")
|
||||
raise ProcessingError(f"Failed to read file: {e}")
|
||||
raise ProcessingError(f"Failed to read file: {e}") from e
|
||||
|
||||
# Ensure content is UTF-8
|
||||
utf8_content = content.encode('utf-8', errors='replace').decode('utf-8')
|
||||
@@ -180,11 +270,14 @@ class EncodingHandler:
|
||||
"""
|
||||
Remove BOM (Byte Order Mark) from data.
|
||||
|
||||
This method removes BOM markers from the beginning of data, supporting
|
||||
UTF-8, UTF-16 LE, and UTF-16 BE BOMs.
|
||||
|
||||
Args:
|
||||
data: Input data
|
||||
|
||||
data: Input data - can be string or bytes
|
||||
|
||||
Returns:
|
||||
Data without BOM
|
||||
Union[str, bytes]: Data without BOM (same type as input)
|
||||
"""
|
||||
if isinstance(data, bytes):
|
||||
# Remove UTF-8 BOM
|
||||
@@ -204,18 +297,29 @@ class EncodingHandler:
|
||||
else:
|
||||
return data
|
||||
|
||||
def handle_encoding_error(self, data: bytes, **options) -> str:
|
||||
def handle_encoding_error(
|
||||
self,
|
||||
data: bytes,
|
||||
error_strategy: str = "replace",
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Handle encoding errors gracefully.
|
||||
|
||||
This method attempts to decode bytes data using detected encoding and
|
||||
fallback encodings, applying the specified error handling strategy.
|
||||
|
||||
Args:
|
||||
data: Input bytes data
|
||||
**options: Error handling options
|
||||
|
||||
error_strategy: Error handling strategy (default: "replace"):
|
||||
- "replace": Replace invalid characters with replacement char
|
||||
- "ignore": Ignore invalid characters
|
||||
- "strict": Raise exception on errors
|
||||
**options: Additional error handling options (unused)
|
||||
|
||||
Returns:
|
||||
str: Decoded string with errors handled
|
||||
str: Decoded string with errors handled according to strategy
|
||||
"""
|
||||
error_strategy = options.get("error_strategy", "replace")
|
||||
|
||||
# Try detected encoding first
|
||||
encoding, _ = self.detect(data, **options)
|
||||
@@ -233,17 +337,25 @@ class EncodingHandler:
|
||||
# Final fallback
|
||||
return data.decode(self.default_encoding, errors=error_strategy)
|
||||
|
||||
def validate_encoding(self, data: Union[str, bytes], encoding: str, **options) -> bool:
|
||||
def validate_encoding(
|
||||
self,
|
||||
data: Union[str, bytes],
|
||||
encoding: str,
|
||||
**options
|
||||
) -> bool:
|
||||
"""
|
||||
Validate that data can be decoded with given encoding.
|
||||
Validate that data can be decoded/encoded with given encoding.
|
||||
|
||||
This method validates that the data can be successfully decoded (for bytes)
|
||||
or encoded (for strings) using the specified encoding without errors.
|
||||
|
||||
Args:
|
||||
data: Input data
|
||||
encoding: Encoding to validate
|
||||
**options: Validation options
|
||||
|
||||
data: Input data - can be string or bytes
|
||||
encoding: Encoding name to validate (e.g., "utf-8", "latin-1")
|
||||
**options: Additional validation options (unused)
|
||||
|
||||
Returns:
|
||||
bool: True if encoding is valid for data
|
||||
bool: True if encoding is valid for data, False otherwise
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
try:
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
"""
|
||||
Entity Normalization Module
|
||||
|
||||
Handles normalization of named entities and proper nouns.
|
||||
This module provides comprehensive entity normalization capabilities for the
|
||||
Semantica framework, enabling standardization of named entities and proper nouns
|
||||
across various formats and naming conventions.
|
||||
|
||||
Key Features:
|
||||
- Entity name standardization
|
||||
- Alias resolution and mapping
|
||||
- Entity disambiguation
|
||||
- Name variant handling
|
||||
- Entity disambiguation (context-aware)
|
||||
- Name variant handling (titles, honorifics, formats)
|
||||
- Entity linking and resolution
|
||||
- Support for multiple entity types (Person, Organization, etc.)
|
||||
|
||||
Main Classes:
|
||||
- EntityNormalizer: Main entity normalization class
|
||||
- AliasResolver: Entity alias resolution
|
||||
- EntityDisambiguator: Entity disambiguation
|
||||
- NameVariantHandler: Name variant processing
|
||||
- EntityNormalizer: Main entity normalization coordinator
|
||||
- AliasResolver: Entity alias resolution engine
|
||||
- EntityDisambiguator: Entity disambiguation engine
|
||||
- NameVariantHandler: Name variant processing engine
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.normalize import EntityNormalizer
|
||||
>>> normalizer = EntityNormalizer()
|
||||
>>> normalized = normalizer.normalize_entity("John Doe", entity_type="Person")
|
||||
>>> canonical = normalizer.resolve_aliases("J. Doe")
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
import re
|
||||
@@ -26,23 +38,36 @@ from ..utils.logging import get_logger
|
||||
|
||||
class EntityNormalizer:
|
||||
"""
|
||||
Entity normalization and standardization handler.
|
||||
Entity normalization and standardization coordinator.
|
||||
|
||||
• Normalizes entity names and proper nouns
|
||||
• Resolves entity aliases and variants
|
||||
• Handles entity disambiguation
|
||||
• Standardizes entity formats
|
||||
• Links entities to canonical forms
|
||||
• Supports multiple entity types
|
||||
This class provides comprehensive entity normalization capabilities, coordinating
|
||||
alias resolution, disambiguation, and name variant handling.
|
||||
|
||||
Features:
|
||||
- Entity name normalization and standardization
|
||||
- Alias resolution and mapping
|
||||
- Entity disambiguation using context
|
||||
- Name format standardization
|
||||
- Entity linking to canonical forms
|
||||
- Support for multiple entity types
|
||||
|
||||
Example Usage:
|
||||
>>> normalizer = EntityNormalizer()
|
||||
>>> normalized = normalizer.normalize_entity("John Doe", entity_type="Person")
|
||||
>>> canonical = normalizer.resolve_aliases("J. Doe")
|
||||
>>> linked = normalizer.link_entities(["John Doe", "J. Doe", "Johnny Doe"])
|
||||
"""
|
||||
|
||||
def __init__(self, config=None, **kwargs):
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
"""
|
||||
Initialize entity normalizer.
|
||||
|
||||
Sets up the normalizer with alias resolver, disambiguator, and variant
|
||||
handler components.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary
|
||||
**kwargs: Additional configuration options
|
||||
config: Configuration dictionary (optional)
|
||||
**kwargs: Additional configuration options (merged into config)
|
||||
"""
|
||||
self.logger = get_logger("entity_normalizer")
|
||||
self.config = config or {}
|
||||
@@ -51,18 +76,30 @@ class EntityNormalizer:
|
||||
self.alias_resolver = AliasResolver(**self.config)
|
||||
self.disambiguator = EntityDisambiguator(**self.config)
|
||||
self.variant_handler = NameVariantHandler(**self.config)
|
||||
|
||||
self.logger.debug("Entity normalizer initialized")
|
||||
|
||||
def normalize_entity(self, entity_name: str, entity_type: Optional[str] = None, **options) -> str:
|
||||
def normalize_entity(
|
||||
self,
|
||||
entity_name: str,
|
||||
entity_type: Optional[str] = None,
|
||||
resolve_aliases: bool = True,
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Normalize entity name to standard form.
|
||||
|
||||
This method normalizes an entity name by cleaning whitespace, resolving
|
||||
aliases, and standardizing the format based on entity type.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name to normalize
|
||||
entity_type: Entity type (optional)
|
||||
**options: Normalization options
|
||||
entity_type: Entity type (optional, e.g., "Person", "Organization")
|
||||
resolve_aliases: Whether to resolve aliases (default: True)
|
||||
**options: Additional normalization options (unused)
|
||||
|
||||
Returns:
|
||||
Normalized entity name
|
||||
str: Normalized entity name in standard form
|
||||
"""
|
||||
if not entity_name:
|
||||
return ""
|
||||
@@ -74,7 +111,7 @@ class EntityNormalizer:
|
||||
normalized = normalized.title() if entity_type == "Person" else normalized
|
||||
|
||||
# Resolve aliases
|
||||
if options.get("resolve_aliases", True):
|
||||
if resolve_aliases:
|
||||
resolved = self.alias_resolver.resolve_aliases(normalized, entity_type=entity_type)
|
||||
if resolved:
|
||||
normalized = resolved
|
||||
@@ -84,42 +121,67 @@ class EntityNormalizer:
|
||||
|
||||
return normalized
|
||||
|
||||
def resolve_aliases(self, entity_name: str, **context) -> Optional[str]:
|
||||
def resolve_aliases(
|
||||
self,
|
||||
entity_name: str,
|
||||
**context
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Resolve entity aliases and variants.
|
||||
|
||||
This method attempts to resolve an entity name to its canonical form
|
||||
using alias mapping.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
**context: Context information
|
||||
entity_name: Entity name to resolve
|
||||
**context: Context information (e.g., entity_type)
|
||||
|
||||
Returns:
|
||||
Resolved canonical form or None
|
||||
Optional[str]: Resolved canonical form if found, None otherwise
|
||||
"""
|
||||
return self.alias_resolver.resolve_aliases(entity_name, **context)
|
||||
|
||||
def disambiguate_entity(self, entity_name: str, **context) -> Dict[str, Any]:
|
||||
def disambiguate_entity(
|
||||
self,
|
||||
entity_name: str,
|
||||
**context
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Disambiguate entity when multiple candidates exist.
|
||||
|
||||
This method disambiguates an entity name when multiple candidates exist,
|
||||
using context information to select the most likely candidate.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
**context: Context information
|
||||
entity_name: Entity name to disambiguate
|
||||
**context: Context information (e.g., entity_type, context text)
|
||||
|
||||
Returns:
|
||||
Disambiguation result
|
||||
dict: Disambiguation result containing:
|
||||
- entity_name: Original entity name
|
||||
- entity_type: Detected entity type
|
||||
- confidence: Confidence score (0.0 to 1.0)
|
||||
- candidates: List of candidate entity names
|
||||
"""
|
||||
return self.disambiguator.disambiguate(entity_name, **context)
|
||||
|
||||
def link_entities(self, entities: List[str], **options) -> Dict[str, str]:
|
||||
def link_entities(
|
||||
self,
|
||||
entities: List[str],
|
||||
**options
|
||||
) -> Dict[str, str]:
|
||||
"""
|
||||
Link entities to canonical forms.
|
||||
|
||||
This method links a list of entity names to their canonical forms,
|
||||
creating a mapping from original names to normalized names.
|
||||
|
||||
Args:
|
||||
entities: List of entity names
|
||||
**options: Linking options
|
||||
entities: List of entity names to link
|
||||
**options: Linking options (passed to normalize_entity)
|
||||
|
||||
Returns:
|
||||
Dictionary mapping entities to canonical forms
|
||||
dict: Dictionary mapping original entity names to canonical forms
|
||||
"""
|
||||
linked = {}
|
||||
|
||||
@@ -134,33 +196,54 @@ class AliasResolver:
|
||||
"""
|
||||
Entity alias resolution engine.
|
||||
|
||||
• Resolves entity aliases and nicknames
|
||||
• Maps name variations to canonical forms
|
||||
• Handles different naming conventions
|
||||
• Processes cultural and linguistic variations
|
||||
This class provides alias resolution capabilities, mapping entity name
|
||||
variations and aliases to canonical forms.
|
||||
|
||||
Features:
|
||||
- Entity alias and nickname resolution
|
||||
- Name variation mapping
|
||||
- Support for different naming conventions
|
||||
- Cultural and linguistic variation handling
|
||||
|
||||
Example Usage:
|
||||
>>> resolver = AliasResolver(alias_map={"j. doe": "John Doe"})
|
||||
>>> canonical = resolver.resolve_aliases("J. Doe")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize alias resolver.
|
||||
|
||||
Sets up the resolver with alias mapping dictionary.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options:
|
||||
- alias_map: Dictionary mapping aliases to canonical forms
|
||||
"""
|
||||
self.logger = get_logger("alias_resolver")
|
||||
self.config = config
|
||||
self.alias_map = config.get("alias_map", {})
|
||||
|
||||
self.logger.debug(f"Alias resolver initialized ({len(self.alias_map)} aliases)")
|
||||
|
||||
def resolve_aliases(self, entity_name: str, **context) -> Optional[str]:
|
||||
def resolve_aliases(
|
||||
self,
|
||||
entity_name: str,
|
||||
**context
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Resolve entity aliases to canonical form.
|
||||
|
||||
This method looks up an entity name in the alias map and returns
|
||||
its canonical form if found.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
**context: Context information
|
||||
entity_name: Entity name to resolve
|
||||
**context: Context information (e.g., entity_type, currently unused)
|
||||
|
||||
Returns:
|
||||
Resolved canonical form or None
|
||||
Optional[str]: Resolved canonical form if found in alias map,
|
||||
None otherwise
|
||||
"""
|
||||
# Check alias map
|
||||
entity_lower = entity_name.lower()
|
||||
@@ -175,30 +258,46 @@ class AliasResolver:
|
||||
|
||||
return None
|
||||
|
||||
def map_variants(self, entity_name: str, entity_type: str) -> str:
|
||||
def map_variants(
|
||||
self,
|
||||
entity_name: str,
|
||||
entity_type: str
|
||||
) -> str:
|
||||
"""
|
||||
Map entity name variants.
|
||||
|
||||
This method maps entity name variants to a standard form based on
|
||||
entity type. Currently returns the name as-is; can be extended for
|
||||
variant mapping.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
entity_type: Entity type
|
||||
entity_name: Entity name to map
|
||||
entity_type: Entity type (e.g., "Person", "Organization")
|
||||
|
||||
Returns:
|
||||
Mapped variant
|
||||
str: Mapped variant (currently returns entity_name as-is)
|
||||
"""
|
||||
# Simple variant mapping
|
||||
# Simple variant mapping - can be extended
|
||||
return entity_name
|
||||
|
||||
def handle_cultural_variations(self, entity_name: str, culture: Optional[str] = None) -> str:
|
||||
def handle_cultural_variations(
|
||||
self,
|
||||
entity_name: str,
|
||||
culture: Optional[str] = None
|
||||
) -> str:
|
||||
"""
|
||||
Handle cultural and linguistic variations.
|
||||
|
||||
This method handles cultural and linguistic variations in entity names.
|
||||
Currently returns the name as-is; can be extended for cultural
|
||||
normalization.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
culture: Culture identifier
|
||||
entity_name: Entity name to process
|
||||
culture: Culture identifier (optional, e.g., "en-US", "zh-CN")
|
||||
|
||||
Returns:
|
||||
Culturally appropriate form
|
||||
str: Culturally appropriate form (currently returns entity_name as-is)
|
||||
"""
|
||||
return entity_name
|
||||
|
||||
@@ -207,32 +306,59 @@ class EntityDisambiguator:
|
||||
"""
|
||||
Entity disambiguation engine.
|
||||
|
||||
• Disambiguates entities with multiple meanings
|
||||
• Uses contextual information for disambiguation
|
||||
• Applies machine learning models
|
||||
• Handles entity type classification
|
||||
This class provides entity disambiguation capabilities, using context
|
||||
information to resolve ambiguous entity references.
|
||||
|
||||
Features:
|
||||
- Context-aware entity disambiguation
|
||||
- Entity type classification
|
||||
- Confidence score calculation
|
||||
- Candidate entity generation
|
||||
|
||||
Example Usage:
|
||||
>>> disambiguator = EntityDisambiguator()
|
||||
>>> result = disambiguator.disambiguate("Apple", context="technology company")
|
||||
>>> entity_type = disambiguator.classify_entity_type("John Doe")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize entity disambiguator.
|
||||
|
||||
Sets up the disambiguator with configuration options.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("entity_disambiguator")
|
||||
self.config = config
|
||||
|
||||
self.logger.debug("Entity disambiguator initialized")
|
||||
|
||||
def disambiguate(self, entity_name: str, **context) -> Dict[str, Any]:
|
||||
def disambiguate(
|
||||
self,
|
||||
entity_name: str,
|
||||
**context
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Disambiguate entity using context.
|
||||
|
||||
This method disambiguates an entity name using context information.
|
||||
Currently provides a basic implementation; can be extended with
|
||||
machine learning models for improved disambiguation.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
**context: Context information
|
||||
entity_name: Entity name to disambiguate
|
||||
**context: Context information containing:
|
||||
- entity_type: Entity type (optional)
|
||||
- context: Text context (optional)
|
||||
|
||||
Returns:
|
||||
Disambiguation result
|
||||
dict: Disambiguation result containing:
|
||||
- entity_name: Original entity name
|
||||
- entity_type: Detected entity type
|
||||
- confidence: Confidence score (0.0 to 1.0)
|
||||
- candidates: List of candidate entity names
|
||||
"""
|
||||
entity_type = context.get("entity_type")
|
||||
text_context = context.get("context", "")
|
||||
@@ -244,17 +370,30 @@ class EntityDisambiguator:
|
||||
"candidates": [entity_name]
|
||||
}
|
||||
|
||||
def classify_entity_type(self, entity_name: str, **context) -> str:
|
||||
def classify_entity_type(
|
||||
self,
|
||||
entity_name: str,
|
||||
**context
|
||||
) -> str:
|
||||
"""
|
||||
Classify entity type for disambiguation.
|
||||
|
||||
This method classifies the entity type using simple heuristics based
|
||||
on name format. Can be extended with more sophisticated classification.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
**context: Context information
|
||||
entity_name: Entity name to classify
|
||||
**context: Context information (currently unused)
|
||||
|
||||
Returns:
|
||||
Entity type
|
||||
str: Entity type classification:
|
||||
- "Person": If name starts with uppercase and contains space
|
||||
- "Organization": If name starts with uppercase
|
||||
- "Entity": Otherwise
|
||||
"""
|
||||
if not entity_name:
|
||||
return "Entity"
|
||||
|
||||
# Simple heuristic-based classification
|
||||
if entity_name[0].isupper() and ' ' in entity_name:
|
||||
return "Person"
|
||||
@@ -263,16 +402,25 @@ class EntityDisambiguator:
|
||||
else:
|
||||
return "Entity"
|
||||
|
||||
def calculate_confidence(self, candidates: List[str], **context) -> Dict[str, float]:
|
||||
def calculate_confidence(
|
||||
self,
|
||||
candidates: List[str],
|
||||
**context
|
||||
) -> Dict[str, float]:
|
||||
"""
|
||||
Calculate confidence scores for candidates.
|
||||
|
||||
This method calculates confidence scores for candidate entities.
|
||||
Currently returns a default confidence of 0.8 for all candidates;
|
||||
can be extended with more sophisticated scoring.
|
||||
|
||||
Args:
|
||||
candidates: List of candidate entities
|
||||
**context: Context information
|
||||
candidates: List of candidate entity names
|
||||
**context: Context information (currently unused)
|
||||
|
||||
Returns:
|
||||
Dictionary of confidence scores
|
||||
dict: Dictionary mapping candidate names to confidence scores
|
||||
(0.0 to 1.0)
|
||||
"""
|
||||
return {candidate: 0.8 for candidate in candidates}
|
||||
|
||||
@@ -281,33 +429,54 @@ class NameVariantHandler:
|
||||
"""
|
||||
Name variant processing engine.
|
||||
|
||||
• Handles different name formats and variations
|
||||
• Processes formal and informal names
|
||||
• Manages name order variations
|
||||
• Handles title and honorific processing
|
||||
This class provides name variant handling capabilities, processing different
|
||||
name formats, titles, and honorifics.
|
||||
|
||||
Features:
|
||||
- Name format normalization (standard, title, lower)
|
||||
- Title and honorific handling
|
||||
- Name variant generation
|
||||
- Format standardization
|
||||
|
||||
Example Usage:
|
||||
>>> handler = NameVariantHandler()
|
||||
>>> normalized = handler.normalize_name_format("Dr. John Doe", "standard")
|
||||
>>> title_info = handler.handle_titles_and_honorifics("Mr. John Doe")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize name variant handler.
|
||||
|
||||
Sets up the handler with titles dictionary and configuration.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options:
|
||||
- titles: Set of title strings (optional, uses default if not provided)
|
||||
"""
|
||||
self.logger = get_logger("name_variant_handler")
|
||||
self.config = config
|
||||
self.titles = {"Mr.", "Mrs.", "Ms.", "Dr.", "Prof.", "Sir", "Madam"}
|
||||
self.titles = config.get("titles", {"Mr.", "Mrs.", "Ms.", "Dr.", "Prof.", "Sir", "Madam"})
|
||||
|
||||
self.logger.debug(f"Name variant handler initialized ({len(self.titles)} titles)")
|
||||
|
||||
def process_variants(self, entity_name: str, **options) -> List[str]:
|
||||
def process_variants(
|
||||
self,
|
||||
entity_name: str,
|
||||
**options
|
||||
) -> List[str]:
|
||||
"""
|
||||
Process entity name variants.
|
||||
|
||||
This method generates a list of name variants for an entity, including
|
||||
the original name and normalized forms.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
**options: Processing options
|
||||
entity_name: Entity name to process
|
||||
**options: Processing options (unused)
|
||||
|
||||
Returns:
|
||||
List of variants
|
||||
list: List of name variant strings
|
||||
"""
|
||||
variants = [entity_name]
|
||||
|
||||
@@ -318,16 +487,26 @@ class NameVariantHandler:
|
||||
|
||||
return variants
|
||||
|
||||
def normalize_name_format(self, entity_name: str, format_type: str = "standard") -> str:
|
||||
def normalize_name_format(
|
||||
self,
|
||||
entity_name: str,
|
||||
format_type: str = "standard"
|
||||
) -> str:
|
||||
"""
|
||||
Normalize name format.
|
||||
|
||||
This method normalizes the format of an entity name, removing titles
|
||||
and applying the specified format type.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
format_type: Format type ('standard', 'title', 'lower')
|
||||
entity_name: Entity name to normalize
|
||||
format_type: Format type (default: "standard"):
|
||||
- "standard": Title case for each word part
|
||||
- "title": Title case for entire name
|
||||
- "lower": Lowercase
|
||||
|
||||
Returns:
|
||||
Formatted name
|
||||
str: Formatted name with titles removed
|
||||
"""
|
||||
# Remove titles
|
||||
name = entity_name
|
||||
@@ -347,15 +526,23 @@ class NameVariantHandler:
|
||||
|
||||
return name
|
||||
|
||||
def handle_titles_and_honorifics(self, entity_name: str) -> Dict[str, Any]:
|
||||
def handle_titles_and_honorifics(
|
||||
self,
|
||||
entity_name: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Handle titles and honorifics in names.
|
||||
|
||||
This method extracts titles and honorifics from entity names, returning
|
||||
the name without title and the extracted title.
|
||||
|
||||
Args:
|
||||
entity_name: Entity name
|
||||
entity_name: Entity name with potential title
|
||||
|
||||
Returns:
|
||||
Dictionary with name and title
|
||||
dict: Dictionary containing:
|
||||
- name: Name without title
|
||||
- title: Extracted title (None if no title found)
|
||||
"""
|
||||
title = None
|
||||
name = entity_name
|
||||
|
||||
@@ -1,48 +1,111 @@
|
||||
"""
|
||||
Language detection utilities for Semantica framework.
|
||||
Language Detection Module
|
||||
|
||||
This module provides multi-language detection capabilities
|
||||
using langdetect and other language identification libraries.
|
||||
This module provides comprehensive language detection capabilities for the
|
||||
Semantica framework, enabling identification of text language using the
|
||||
langdetect library.
|
||||
|
||||
Key Features:
|
||||
- Multi-language detection (50+ languages)
|
||||
- Confidence scoring
|
||||
- Batch processing
|
||||
- Top N language detection
|
||||
- Language code to name mapping
|
||||
|
||||
Main Classes:
|
||||
- LanguageDetector: Language detection coordinator
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.normalize import LanguageDetector
|
||||
>>> detector = LanguageDetector()
|
||||
>>> language = detector.detect("Hello world")
|
||||
>>> lang, confidence = detector.detect_with_confidence("Bonjour le monde")
|
||||
>>> languages = detector.detect_multiple(text, top_n=3)
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from langdetect import detect, detect_langs
|
||||
from langdetect.lang_detect_exception import LangDetectException
|
||||
try:
|
||||
from langdetect import detect, detect_langs
|
||||
from langdetect.lang_detect_exception import LangDetectException
|
||||
LANGDETECT_AVAILABLE = True
|
||||
except ImportError:
|
||||
LANGDETECT_AVAILABLE = False
|
||||
LangDetectException = Exception
|
||||
|
||||
from ..utils.exceptions import ProcessingError
|
||||
from ..utils.logging import get_logger
|
||||
|
||||
|
||||
class LanguageDetector:
|
||||
"""Language detection utilities."""
|
||||
"""
|
||||
Language detection coordinator.
|
||||
|
||||
This class provides language detection capabilities using the langdetect
|
||||
library, supporting detection with confidence scores and batch processing.
|
||||
|
||||
Features:
|
||||
- Multi-language detection (50+ languages)
|
||||
- Confidence scoring
|
||||
- Batch processing
|
||||
- Top N language detection
|
||||
- Language code to name mapping
|
||||
|
||||
Example Usage:
|
||||
>>> detector = LanguageDetector()
|
||||
>>> language = detector.detect("Hello world")
|
||||
>>> lang, confidence = detector.detect_with_confidence("Bonjour")
|
||||
>>> is_english = detector.is_language(text, "en")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize language detector.
|
||||
|
||||
Sets up the detector with default language and minimum confidence threshold.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options:
|
||||
- default_language: Default language code (default: "en")
|
||||
- min_confidence: Minimum confidence threshold (default: 0.5)
|
||||
"""
|
||||
self.logger = get_logger("language_detector")
|
||||
self.config = config
|
||||
self.default_language = config.get("default_language", "en")
|
||||
self.min_confidence = config.get("min_confidence", 0.5)
|
||||
|
||||
if not LANGDETECT_AVAILABLE:
|
||||
self.logger.warning("langdetect library not available, language detection will be limited")
|
||||
|
||||
self.logger.debug(f"Language detector initialized (default={self.default_language})")
|
||||
|
||||
def detect(self, text: str, **options) -> str:
|
||||
"""
|
||||
Detect language of text.
|
||||
|
||||
This method detects the language of input text using langdetect.
|
||||
Returns the default language if detection fails or text is too short.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
**options: Detection options
|
||||
|
||||
text: Input text to analyze
|
||||
**options: Detection options (unused)
|
||||
|
||||
Returns:
|
||||
str: Detected language code
|
||||
str: Detected language code (e.g., "en", "fr", "de")
|
||||
|
||||
Note:
|
||||
Requires minimum text length of 10 characters for reliable detection.
|
||||
Returns default_language if text is too short or detection fails.
|
||||
"""
|
||||
if not text or len(text.strip()) < 10:
|
||||
return self.default_language
|
||||
|
||||
if not LANGDETECT_AVAILABLE:
|
||||
return self.default_language
|
||||
|
||||
try:
|
||||
language = detect(text)
|
||||
return language
|
||||
@@ -53,20 +116,33 @@ class LanguageDetector:
|
||||
self.logger.error(f"Language detection error: {e}")
|
||||
return self.default_language
|
||||
|
||||
def detect_with_confidence(self, text: str, **options) -> Tuple[str, float]:
|
||||
def detect_with_confidence(
|
||||
self,
|
||||
text: str,
|
||||
**options
|
||||
) -> Tuple[str, float]:
|
||||
"""
|
||||
Detect language with confidence score.
|
||||
|
||||
This method detects the language of text and returns both the language
|
||||
code and confidence score. Only returns detected language if confidence
|
||||
meets the minimum threshold.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
**options: Detection options
|
||||
|
||||
text: Input text to analyze
|
||||
**options: Detection options (unused)
|
||||
|
||||
Returns:
|
||||
tuple: (language_code, confidence_score)
|
||||
tuple: (language_code, confidence_score) where:
|
||||
- language_code: Detected language code
|
||||
- confidence_score: Confidence score between 0.0 and 1.0
|
||||
"""
|
||||
if not text or len(text.strip()) < 10:
|
||||
return (self.default_language, 0.0)
|
||||
|
||||
if not LANGDETECT_AVAILABLE:
|
||||
return (self.default_language, 0.0)
|
||||
|
||||
try:
|
||||
languages = detect_langs(text)
|
||||
if languages:
|
||||
@@ -84,21 +160,33 @@ class LanguageDetector:
|
||||
self.logger.error(f"Language detection error: {e}")
|
||||
return (self.default_language, 0.0)
|
||||
|
||||
def detect_multiple(self, text: str, top_n: int = 3, **options) -> List[Tuple[str, float]]:
|
||||
def detect_multiple(
|
||||
self,
|
||||
text: str,
|
||||
top_n: int = 3,
|
||||
**options
|
||||
) -> List[Tuple[str, float]]:
|
||||
"""
|
||||
Detect top N languages with confidence scores.
|
||||
|
||||
This method detects multiple candidate languages for text, returning
|
||||
the top N languages sorted by confidence.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
top_n: Number of top languages to return
|
||||
**options: Detection options
|
||||
|
||||
text: Input text to analyze
|
||||
top_n: Number of top languages to return (default: 3)
|
||||
**options: Detection options (unused)
|
||||
|
||||
Returns:
|
||||
list: List of (language_code, confidence_score) tuples
|
||||
list: List of (language_code, confidence_score) tuples, sorted by
|
||||
confidence (highest first)
|
||||
"""
|
||||
if not text or len(text.strip()) < 10:
|
||||
return [(self.default_language, 0.0)]
|
||||
|
||||
if not LANGDETECT_AVAILABLE:
|
||||
return [(self.default_language, 0.0)]
|
||||
|
||||
try:
|
||||
languages = detect_langs(text)
|
||||
if languages:
|
||||
@@ -112,57 +200,86 @@ class LanguageDetector:
|
||||
self.logger.error(f"Language detection error: {e}")
|
||||
return [(self.default_language, 0.0)]
|
||||
|
||||
def detect_batch(self, texts: List[str], **options) -> List[str]:
|
||||
def detect_batch(
|
||||
self,
|
||||
texts: List[str],
|
||||
**options
|
||||
) -> List[str]:
|
||||
"""
|
||||
Detect languages for multiple texts in batch.
|
||||
|
||||
This method processes multiple texts in batch, detecting the language
|
||||
for each text.
|
||||
|
||||
Args:
|
||||
texts: List of texts to analyze
|
||||
**options: Detection options
|
||||
|
||||
**options: Detection options (passed to detect method)
|
||||
|
||||
Returns:
|
||||
list: List of detected language codes
|
||||
list: List of detected language codes (one per input text)
|
||||
"""
|
||||
return [self.detect(text, **options) for text in texts]
|
||||
|
||||
def detect_batch_with_confidence(self, texts: List[str], **options) -> List[Tuple[str, float]]:
|
||||
def detect_batch_with_confidence(
|
||||
self,
|
||||
texts: List[str],
|
||||
**options
|
||||
) -> List[Tuple[str, float]]:
|
||||
"""
|
||||
Detect languages with confidence for multiple texts.
|
||||
|
||||
This method processes multiple texts in batch, detecting the language
|
||||
and confidence score for each text.
|
||||
|
||||
Args:
|
||||
texts: List of texts to analyze
|
||||
**options: Detection options
|
||||
|
||||
**options: Detection options (passed to detect_with_confidence method)
|
||||
|
||||
Returns:
|
||||
list: List of (language_code, confidence_score) tuples
|
||||
list: List of (language_code, confidence_score) tuples (one per input text)
|
||||
"""
|
||||
return [self.detect_with_confidence(text, **options) for text in texts]
|
||||
|
||||
def is_language(self, text: str, target_language: str, **options) -> bool:
|
||||
def is_language(
|
||||
self,
|
||||
text: str,
|
||||
target_language: str,
|
||||
min_confidence: Optional[float] = None,
|
||||
**options
|
||||
) -> bool:
|
||||
"""
|
||||
Check if text is in target language.
|
||||
|
||||
This method checks whether the detected language matches the target
|
||||
language and meets the minimum confidence threshold.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
target_language: Target language code
|
||||
**options: Detection options
|
||||
|
||||
text: Input text to check
|
||||
target_language: Target language code (e.g., "en", "fr")
|
||||
min_confidence: Minimum confidence threshold (optional, uses
|
||||
instance min_confidence if not provided)
|
||||
**options: Detection options (unused)
|
||||
|
||||
Returns:
|
||||
bool: True if text is in target language
|
||||
bool: True if text is in target language with sufficient confidence,
|
||||
False otherwise
|
||||
"""
|
||||
detected, confidence = self.detect_with_confidence(text, **options)
|
||||
min_confidence = options.get("min_confidence", self.min_confidence)
|
||||
return detected == target_language and confidence >= min_confidence
|
||||
threshold = min_confidence if min_confidence is not None else self.min_confidence
|
||||
return detected == target_language and confidence >= threshold
|
||||
|
||||
def get_language_name(self, language_code: str) -> str:
|
||||
"""
|
||||
Get language name from code.
|
||||
|
||||
This method converts a language code to its human-readable name.
|
||||
|
||||
Args:
|
||||
language_code: Language code (e.g., 'en', 'fr')
|
||||
|
||||
language_code: Language code (e.g., "en", "fr", "de")
|
||||
|
||||
Returns:
|
||||
str: Language name
|
||||
str: Language name (e.g., "English", "French", "German").
|
||||
Returns uppercase code if name not found.
|
||||
"""
|
||||
language_names = {
|
||||
'en': 'English',
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
"""
|
||||
Number and Quantity Normalization Module
|
||||
|
||||
Handles normalization of numbers, quantities, and numerical expressions.
|
||||
This module provides comprehensive number and quantity normalization capabilities
|
||||
for the Semantica framework, enabling standardization of numerical data across
|
||||
various formats and units.
|
||||
|
||||
Key Features:
|
||||
- Number format standardization
|
||||
- Unit conversion and normalization
|
||||
- Currency handling
|
||||
- Number format standardization (integers, floats, percentages)
|
||||
- Unit conversion and normalization (length, weight, volume)
|
||||
- Currency handling (symbols, codes, conversion)
|
||||
- Percentage processing
|
||||
- Scientific notation handling
|
||||
- Quantity parsing and normalization
|
||||
|
||||
Main Classes:
|
||||
- NumberNormalizer: Main number normalization class
|
||||
- NumberNormalizer: Main number normalization coordinator
|
||||
- UnitConverter: Unit conversion engine
|
||||
- CurrencyNormalizer: Currency processing
|
||||
- CurrencyNormalizer: Currency processing engine
|
||||
- ScientificNotationHandler: Scientific notation processor
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.normalize import NumberNormalizer
|
||||
>>> normalizer = NumberNormalizer()
|
||||
>>> number = normalizer.normalize_number("1,234.56")
|
||||
>>> quantity = normalizer.normalize_quantity("5 kg")
|
||||
>>> currency = normalizer.process_currency("$100")
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
import re
|
||||
@@ -26,23 +39,37 @@ from ..utils.logging import get_logger
|
||||
|
||||
class NumberNormalizer:
|
||||
"""
|
||||
Number and quantity normalization handler.
|
||||
Number and quantity normalization coordinator.
|
||||
|
||||
• Normalizes numbers to standard formats
|
||||
• Handles various number representations
|
||||
• Processes quantities and units
|
||||
• Manages currency and percentage values
|
||||
• Standardizes numerical expressions
|
||||
• Supports multiple number systems
|
||||
This class provides comprehensive number and quantity normalization
|
||||
capabilities, coordinating unit conversion, currency processing, and
|
||||
scientific notation handling.
|
||||
|
||||
Features:
|
||||
- Number format standardization
|
||||
- Quantity parsing and normalization
|
||||
- Unit conversion
|
||||
- Currency processing
|
||||
- Percentage handling
|
||||
- Scientific notation support
|
||||
|
||||
Example Usage:
|
||||
>>> normalizer = NumberNormalizer()
|
||||
>>> number = normalizer.normalize_number("1,234.56")
|
||||
>>> quantity = normalizer.normalize_quantity("5 kg")
|
||||
>>> currency = normalizer.process_currency("$100")
|
||||
"""
|
||||
|
||||
def __init__(self, config=None, **kwargs):
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
"""
|
||||
Initialize number normalizer.
|
||||
|
||||
Sets up the normalizer with unit converter, currency normalizer, and
|
||||
scientific notation handler components.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary
|
||||
**kwargs: Additional configuration options
|
||||
config: Configuration dictionary (optional)
|
||||
**kwargs: Additional configuration options (merged into config)
|
||||
"""
|
||||
self.logger = get_logger("number_normalizer")
|
||||
self.config = config or {}
|
||||
@@ -51,17 +78,31 @@ class NumberNormalizer:
|
||||
self.unit_converter = UnitConverter(**self.config)
|
||||
self.currency_normalizer = CurrencyNormalizer(**self.config)
|
||||
self.scientific_handler = ScientificNotationHandler(**self.config)
|
||||
|
||||
self.logger.debug("Number normalizer initialized")
|
||||
|
||||
def normalize_number(self, number_input: Union[str, int, float], **options) -> Union[int, float]:
|
||||
def normalize_number(
|
||||
self,
|
||||
number_input: Union[str, int, float],
|
||||
**options
|
||||
) -> Union[int, float]:
|
||||
"""
|
||||
Normalize number to standard format.
|
||||
|
||||
This method normalizes number input (string, int, or float) to a
|
||||
standard numeric format, handling percentages and scientific notation.
|
||||
|
||||
Args:
|
||||
number_input: Number input (string, int, or float)
|
||||
**options: Normalization options
|
||||
number_input: Number input - can be:
|
||||
- String (e.g., "1,234.56", "50%", "1.5e3")
|
||||
- int or float
|
||||
**options: Normalization options (unused)
|
||||
|
||||
Returns:
|
||||
Normalized number
|
||||
Union[int, float]: Normalized number (int if no decimal, float otherwise)
|
||||
|
||||
Raises:
|
||||
ValidationError: If number input type is unsupported or parsing fails
|
||||
"""
|
||||
if isinstance(number_input, (int, float)):
|
||||
return number_input
|
||||
@@ -92,16 +133,29 @@ class NumberNormalizer:
|
||||
except ValueError:
|
||||
raise ValidationError(f"Unable to parse number: {number_input}")
|
||||
|
||||
def normalize_quantity(self, quantity_input: str, **options) -> Dict[str, Any]:
|
||||
def normalize_quantity(
|
||||
self,
|
||||
quantity_input: str,
|
||||
**options
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Normalize quantity with units.
|
||||
|
||||
This method parses and normalizes quantity strings containing values
|
||||
and units (e.g., "5 kg", "10 meters").
|
||||
|
||||
Args:
|
||||
quantity_input: Quantity string (e.g., "5 kg", "10 meters")
|
||||
**options: Normalization options
|
||||
quantity_input: Quantity string (e.g., "5 kg", "10 meters", "3.5 liters")
|
||||
**options: Normalization options (unused)
|
||||
|
||||
Returns:
|
||||
Normalized quantity dictionary
|
||||
dict: Normalized quantity dictionary containing:
|
||||
- value: Numeric value (float)
|
||||
- unit: Normalized unit name (str)
|
||||
- original: Original quantity string (str)
|
||||
|
||||
Raises:
|
||||
ValidationError: If quantity format is invalid or parsing fails
|
||||
"""
|
||||
# Parse quantity and unit
|
||||
pattern = r'([\d.,\s]+)\s*([a-zA-Z]+)'
|
||||
@@ -127,51 +181,87 @@ class NumberNormalizer:
|
||||
else:
|
||||
raise ValidationError(f"Invalid quantity format: {quantity_input}")
|
||||
|
||||
def convert_units(self, value: float, from_unit: str, to_unit: str) -> float:
|
||||
def convert_units(
|
||||
self,
|
||||
value: float,
|
||||
from_unit: str,
|
||||
to_unit: str
|
||||
) -> float:
|
||||
"""
|
||||
Convert value between units.
|
||||
|
||||
This method converts a numeric value from one unit to another,
|
||||
supporting length, weight, and volume conversions.
|
||||
|
||||
Args:
|
||||
value: Value to convert
|
||||
from_unit: Source unit
|
||||
to_unit: Target unit
|
||||
value: Numeric value to convert
|
||||
from_unit: Source unit (e.g., "kg", "meter", "liter")
|
||||
to_unit: Target unit (e.g., "pound", "mile", "gallon")
|
||||
|
||||
Returns:
|
||||
Converted value
|
||||
float: Converted value in target unit
|
||||
|
||||
Raises:
|
||||
ValidationError: If units are incompatible or conversion fails
|
||||
"""
|
||||
return self.unit_converter.convert_units(value, from_unit, to_unit)
|
||||
|
||||
def process_currency(self, currency_input: str, **options) -> Dict[str, Any]:
|
||||
def process_currency(
|
||||
self,
|
||||
currency_input: str,
|
||||
default_currency: str = "USD",
|
||||
**options
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Process currency values.
|
||||
|
||||
This method parses and normalizes currency strings, extracting
|
||||
amount and currency code.
|
||||
|
||||
Args:
|
||||
currency_input: Currency string (e.g., "$100", "100 USD")
|
||||
**options: Processing options
|
||||
currency_input: Currency string (e.g., "$100", "100 USD", "€50")
|
||||
default_currency: Default currency code if not found (default: "USD")
|
||||
**options: Additional processing options (unused)
|
||||
|
||||
Returns:
|
||||
Normalized currency dictionary
|
||||
dict: Normalized currency dictionary containing:
|
||||
- amount: Numeric amount (float or None)
|
||||
- currency: Currency code (str, e.g., "USD", "EUR")
|
||||
- original: Original currency string (str)
|
||||
"""
|
||||
return self.currency_normalizer.normalize_currency(currency_input, **options)
|
||||
return self.currency_normalizer.normalize_currency(
|
||||
currency_input, default_currency=default_currency, **options
|
||||
)
|
||||
|
||||
|
||||
class UnitConverter:
|
||||
"""
|
||||
Unit conversion engine.
|
||||
|
||||
• Converts between different units
|
||||
• Handles various unit systems
|
||||
• Manages conversion factors
|
||||
• Processes compound units
|
||||
• Handles unit validation
|
||||
This class provides unit conversion capabilities, supporting length,
|
||||
weight, and volume conversions with validation.
|
||||
|
||||
Features:
|
||||
- Unit conversion (length, weight, volume)
|
||||
- Conversion factor management
|
||||
- Unit validation
|
||||
- Unit normalization
|
||||
- Support for multiple unit systems
|
||||
|
||||
Example Usage:
|
||||
>>> converter = UnitConverter()
|
||||
>>> converted = converter.convert_units(100, "kg", "pound")
|
||||
>>> normalized = converter.normalize_unit("km")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize unit converter.
|
||||
|
||||
Sets up the converter with conversion factors and unit categories.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("unit_converter")
|
||||
self.config = config
|
||||
@@ -206,18 +296,31 @@ class UnitConverter:
|
||||
"weight": ["kilogram", "gram", "pound", "ounce"],
|
||||
"volume": ["liter", "milliliter", "gallon"],
|
||||
}
|
||||
|
||||
self.logger.debug("Unit converter initialized")
|
||||
|
||||
def convert_units(self, value: float, from_unit: str, to_unit: str) -> float:
|
||||
def convert_units(
|
||||
self,
|
||||
value: float,
|
||||
from_unit: str,
|
||||
to_unit: str
|
||||
) -> float:
|
||||
"""
|
||||
Convert value between units.
|
||||
|
||||
This method converts a numeric value from one unit to another within
|
||||
the same category (length, weight, or volume).
|
||||
|
||||
Args:
|
||||
value: Value to convert
|
||||
from_unit: Source unit
|
||||
to_unit: Target unit
|
||||
value: Numeric value to convert
|
||||
from_unit: Source unit name (e.g., "kg", "meter", "liter")
|
||||
to_unit: Target unit name (e.g., "pound", "mile", "gallon")
|
||||
|
||||
Returns:
|
||||
Converted value
|
||||
float: Converted value in target unit
|
||||
|
||||
Raises:
|
||||
ValidationError: If units are incompatible or not in same category
|
||||
"""
|
||||
from_unit = from_unit.lower()
|
||||
to_unit = to_unit.lower()
|
||||
@@ -236,16 +339,23 @@ class UnitConverter:
|
||||
|
||||
return converted_value
|
||||
|
||||
def validate_units(self, from_unit: str, to_unit: str) -> bool:
|
||||
def validate_units(
|
||||
self,
|
||||
from_unit: str,
|
||||
to_unit: str
|
||||
) -> bool:
|
||||
"""
|
||||
Validate unit conversion compatibility.
|
||||
|
||||
This method validates that two units are compatible for conversion,
|
||||
checking that they exist and are in the same category.
|
||||
|
||||
Args:
|
||||
from_unit: Source unit
|
||||
to_unit: Target unit
|
||||
from_unit: Source unit name
|
||||
to_unit: Target unit name
|
||||
|
||||
Returns:
|
||||
True if units are compatible
|
||||
bool: True if units are compatible (same category), False otherwise
|
||||
"""
|
||||
from_unit = from_unit.lower()
|
||||
to_unit = to_unit.lower()
|
||||
@@ -266,16 +376,23 @@ class UnitConverter:
|
||||
|
||||
return from_category == to_category
|
||||
|
||||
def get_conversion_factor(self, from_unit: str, to_unit: str) -> float:
|
||||
def get_conversion_factor(
|
||||
self,
|
||||
from_unit: str,
|
||||
to_unit: str
|
||||
) -> float:
|
||||
"""
|
||||
Get conversion factor between units.
|
||||
|
||||
This method calculates the conversion factor to convert from one unit
|
||||
to another. If to_unit is "base", returns factor to base unit.
|
||||
|
||||
Args:
|
||||
from_unit: Source unit
|
||||
to_unit: Target unit
|
||||
from_unit: Source unit name
|
||||
to_unit: Target unit name or "base" for base unit
|
||||
|
||||
Returns:
|
||||
Conversion factor
|
||||
float: Conversion factor (multiply source value by this to get target)
|
||||
"""
|
||||
from_unit = from_unit.lower()
|
||||
|
||||
@@ -292,11 +409,15 @@ class UnitConverter:
|
||||
"""
|
||||
Normalize unit name to standard form.
|
||||
|
||||
This method normalizes unit abbreviations to full unit names
|
||||
(e.g., "km" -> "kilometer", "kg" -> "kilogram").
|
||||
|
||||
Args:
|
||||
unit: Unit name
|
||||
unit: Unit name or abbreviation (e.g., "km", "kg", "m")
|
||||
|
||||
Returns:
|
||||
Normalized unit name
|
||||
str: Normalized unit name (full name if abbreviation found,
|
||||
original unit otherwise)
|
||||
"""
|
||||
unit_lower = unit.lower()
|
||||
|
||||
@@ -321,19 +442,30 @@ class CurrencyNormalizer:
|
||||
"""
|
||||
Currency normalization engine.
|
||||
|
||||
• Normalizes currency values and codes
|
||||
• Handles currency conversion
|
||||
• Manages exchange rates
|
||||
• Processes currency symbols
|
||||
• Handles multiple currencies
|
||||
This class provides currency processing capabilities, including symbol
|
||||
and code recognition, amount extraction, and currency validation.
|
||||
|
||||
Features:
|
||||
- Currency symbol and code recognition
|
||||
- Amount extraction from currency strings
|
||||
- Currency code validation
|
||||
- Support for multiple currencies
|
||||
- Currency conversion (placeholder for exchange rate integration)
|
||||
|
||||
Example Usage:
|
||||
>>> normalizer = CurrencyNormalizer()
|
||||
>>> result = normalizer.normalize_currency("$100")
|
||||
>>> is_valid = normalizer.validate_currency_code("USD")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize currency normalizer.
|
||||
|
||||
Sets up the normalizer with currency symbols and codes dictionaries.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("currency_normalizer")
|
||||
self.config = config
|
||||
@@ -353,17 +485,31 @@ class CurrencyNormalizer:
|
||||
}
|
||||
|
||||
self.currency_codes = ["USD", "EUR", "GBP", "JPY", "CNY", "INR", "AUD", "CAD", "CHF", "SEK", "NOK", "DKK"]
|
||||
|
||||
self.logger.debug("Currency normalizer initialized")
|
||||
|
||||
def normalize_currency(self, currency_input: str, **options) -> Dict[str, Any]:
|
||||
def normalize_currency(
|
||||
self,
|
||||
currency_input: str,
|
||||
default_currency: str = "USD",
|
||||
**options
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Normalize currency value and code.
|
||||
|
||||
This method parses currency strings, extracting amount and currency
|
||||
code from symbols or text.
|
||||
|
||||
Args:
|
||||
currency_input: Currency string
|
||||
**options: Normalization options
|
||||
currency_input: Currency string (e.g., "$100", "100 USD", "€50")
|
||||
default_currency: Default currency code if not found (default: "USD")
|
||||
**options: Additional normalization options (unused)
|
||||
|
||||
Returns:
|
||||
Normalized currency dictionary
|
||||
dict: Normalized currency dictionary containing:
|
||||
- amount: Numeric amount (float or None if not found)
|
||||
- currency: Currency code (str, e.g., "USD", "EUR")
|
||||
- original: Original currency string (str)
|
||||
"""
|
||||
# Extract currency symbol or code
|
||||
currency_code = None
|
||||
@@ -404,9 +550,9 @@ class CurrencyNormalizer:
|
||||
except ValueError:
|
||||
amount = None
|
||||
|
||||
# Default to USD if no currency found
|
||||
# Default to specified currency if no currency found
|
||||
if not currency_code:
|
||||
currency_code = options.get("default_currency", "USD")
|
||||
currency_code = default_currency
|
||||
|
||||
return {
|
||||
"amount": amount,
|
||||
@@ -414,19 +560,31 @@ class CurrencyNormalizer:
|
||||
"original": currency_input
|
||||
}
|
||||
|
||||
def convert_currency(self, amount: float, from_currency: str, to_currency: str) -> float:
|
||||
def convert_currency(
|
||||
self,
|
||||
amount: float,
|
||||
from_currency: str,
|
||||
to_currency: str
|
||||
) -> float:
|
||||
"""
|
||||
Convert currency between different currencies.
|
||||
|
||||
This method converts an amount from one currency to another.
|
||||
Currently a placeholder; requires exchange rate API integration
|
||||
for production use.
|
||||
|
||||
Args:
|
||||
amount: Amount to convert
|
||||
from_currency: Source currency
|
||||
to_currency: Target currency
|
||||
from_currency: Source currency code (e.g., "USD")
|
||||
to_currency: Target currency code (e.g., "EUR")
|
||||
|
||||
Returns:
|
||||
Converted amount
|
||||
float: Converted amount (currently returns original amount)
|
||||
|
||||
Note:
|
||||
This is a placeholder implementation. In production, you would
|
||||
integrate with an exchange rate API to fetch current rates.
|
||||
"""
|
||||
# Note: This is a placeholder. In production, you'd fetch exchange rates
|
||||
self.logger.warning("Currency conversion requires exchange rate API")
|
||||
return amount
|
||||
|
||||
@@ -434,11 +592,14 @@ class CurrencyNormalizer:
|
||||
"""
|
||||
Validate currency code.
|
||||
|
||||
This method validates that a currency code is in the supported
|
||||
list of currency codes.
|
||||
|
||||
Args:
|
||||
currency_code: Currency code to validate
|
||||
currency_code: Currency code to validate (e.g., "USD", "EUR")
|
||||
|
||||
Returns:
|
||||
True if valid
|
||||
bool: True if currency code is valid, False otherwise
|
||||
"""
|
||||
return currency_code.upper() in self.currency_codes
|
||||
|
||||
@@ -447,63 +608,96 @@ class ScientificNotationHandler:
|
||||
"""
|
||||
Scientific notation processing engine.
|
||||
|
||||
• Handles scientific notation numbers
|
||||
• Processes exponential formats
|
||||
• Manages precision and significant digits
|
||||
• Converts between formats
|
||||
This class provides scientific notation handling capabilities, including
|
||||
parsing, conversion, and precision normalization.
|
||||
|
||||
Features:
|
||||
- Scientific notation parsing
|
||||
- Format conversion
|
||||
- Precision normalization
|
||||
- Significant digit handling
|
||||
|
||||
Example Usage:
|
||||
>>> handler = ScientificNotationHandler()
|
||||
>>> number = handler.parse_scientific_notation("1.5e3")
|
||||
>>> notation = handler.convert_to_scientific(1500, precision=2)
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize scientific notation handler.
|
||||
|
||||
Sets up the handler with configuration options.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("scientific_notation_handler")
|
||||
self.config = config
|
||||
|
||||
self.logger.debug("Scientific notation handler initialized")
|
||||
|
||||
def parse_scientific_notation(self, number_string: str) -> float:
|
||||
"""
|
||||
Parse scientific notation number.
|
||||
|
||||
This method parses a scientific notation string (e.g., "1.5e3", "2E-4")
|
||||
and returns the numeric value.
|
||||
|
||||
Args:
|
||||
number_string: Scientific notation string
|
||||
number_string: Scientific notation string (e.g., "1.5e3", "2E-4")
|
||||
|
||||
Returns:
|
||||
Parsed number as float
|
||||
float: Parsed numeric value
|
||||
|
||||
Raises:
|
||||
ValidationError: If string is not valid scientific notation
|
||||
"""
|
||||
try:
|
||||
return float(number_string)
|
||||
except ValueError:
|
||||
raise ValidationError(f"Invalid scientific notation: {number_string}")
|
||||
except ValueError as e:
|
||||
raise ValidationError(f"Invalid scientific notation: {number_string}") from e
|
||||
|
||||
def convert_to_scientific(self, number: float, precision: Optional[int] = None) -> str:
|
||||
def convert_to_scientific(
|
||||
self,
|
||||
number: float,
|
||||
precision: Optional[int] = None
|
||||
) -> str:
|
||||
"""
|
||||
Convert number to scientific notation.
|
||||
|
||||
This method converts a numeric value to scientific notation string
|
||||
format (e.g., "1.5e+03").
|
||||
|
||||
Args:
|
||||
number: Number to convert
|
||||
precision: Precision (number of decimal places)
|
||||
number: Numeric value to convert
|
||||
precision: Number of decimal places (optional, uses default if None)
|
||||
|
||||
Returns:
|
||||
Scientific notation string
|
||||
str: Scientific notation string (e.g., "1.5e+03", "2.0e-04")
|
||||
"""
|
||||
if precision is not None:
|
||||
return f"{number:.{precision}e}"
|
||||
else:
|
||||
return f"{number:e}"
|
||||
|
||||
def normalize_precision(self, number: float, significant_digits: int) -> float:
|
||||
def normalize_precision(
|
||||
self,
|
||||
number: float,
|
||||
significant_digits: int
|
||||
) -> float:
|
||||
"""
|
||||
Normalize number precision.
|
||||
|
||||
This method normalizes a number to a specified number of significant
|
||||
digits, preserving the order of magnitude.
|
||||
|
||||
Args:
|
||||
number: Number to normalize
|
||||
significant_digits: Number of significant digits
|
||||
number: Numeric value to normalize
|
||||
significant_digits: Number of significant digits to preserve
|
||||
|
||||
Returns:
|
||||
Normalized number
|
||||
float: Normalized number with specified significant digits
|
||||
"""
|
||||
if number == 0:
|
||||
return 0.0
|
||||
|
||||
@@ -1,32 +1,85 @@
|
||||
"""
|
||||
Text cleaning utilities for Semantica framework.
|
||||
Text Cleaning Module
|
||||
|
||||
This module provides text cleaning and preprocessing functions
|
||||
for HTML removal, whitespace normalization, and text sanitization.
|
||||
This module provides comprehensive text cleaning and preprocessing capabilities
|
||||
for the Semantica framework, enabling removal of HTML, normalization of
|
||||
whitespace and Unicode, and text sanitization.
|
||||
|
||||
Key Features:
|
||||
- HTML tag removal (with BeautifulSoup support)
|
||||
- Whitespace normalization
|
||||
- Unicode normalization (NFC, NFD, NFKC, NFKD)
|
||||
- Special character removal
|
||||
- Text sanitization (security-focused)
|
||||
- Batch processing
|
||||
|
||||
Main Classes:
|
||||
- TextCleaner: Text cleaning coordinator
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.normalize import TextCleaner
|
||||
>>> cleaner = TextCleaner()
|
||||
>>> cleaned = cleaner.clean(text, remove_html=True, normalize_whitespace=True)
|
||||
>>> sanitized = cleaner.sanitize(text)
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
import re
|
||||
import unicodedata
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
try:
|
||||
from bs4 import BeautifulSoup
|
||||
BEAUTIFULSOUP_AVAILABLE = True
|
||||
except ImportError:
|
||||
BEAUTIFULSOUP_AVAILABLE = False
|
||||
BeautifulSoup = None
|
||||
|
||||
from ..utils.exceptions import ProcessingError
|
||||
from ..utils.logging import get_logger
|
||||
|
||||
|
||||
class TextCleaner:
|
||||
"""Text cleaning utilities."""
|
||||
"""
|
||||
Text cleaning coordinator.
|
||||
|
||||
This class provides comprehensive text cleaning capabilities, including
|
||||
HTML removal, whitespace normalization, Unicode normalization, and
|
||||
text sanitization.
|
||||
|
||||
Features:
|
||||
- HTML tag removal (with BeautifulSoup support)
|
||||
- Whitespace normalization
|
||||
- Unicode normalization
|
||||
- Special character removal
|
||||
- Text sanitization
|
||||
- Batch processing
|
||||
|
||||
Example Usage:
|
||||
>>> cleaner = TextCleaner()
|
||||
>>> cleaned = cleaner.clean(text, remove_html=True)
|
||||
>>> sanitized = cleaner.sanitize(text)
|
||||
>>> batch_cleaned = cleaner.clean_batch(texts)
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize text cleaner.
|
||||
|
||||
Sets up the cleaner with configuration options.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("text_cleaner")
|
||||
self.config = config
|
||||
|
||||
if not BEAUTIFULSOUP_AVAILABLE:
|
||||
self.logger.warning("BeautifulSoup not available, HTML removal will use regex fallback")
|
||||
|
||||
self.logger.debug("Text cleaner initialized")
|
||||
|
||||
def clean(
|
||||
self,
|
||||
@@ -35,19 +88,31 @@ class TextCleaner:
|
||||
normalize_whitespace: bool = True,
|
||||
normalize_unicode: bool = True,
|
||||
remove_special_chars: bool = False,
|
||||
unicode_form: str = "NFC",
|
||||
allow_spaces: bool = True,
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Clean text with various options.
|
||||
|
||||
This method performs comprehensive text cleaning by applying multiple
|
||||
cleaning operations in sequence: HTML removal, Unicode normalization,
|
||||
whitespace normalization, and special character removal.
|
||||
|
||||
Args:
|
||||
text: Input text to clean
|
||||
remove_html: Whether to remove HTML tags
|
||||
normalize_whitespace: Whether to normalize whitespace
|
||||
normalize_unicode: Whether to normalize unicode
|
||||
remove_special_chars: Whether to remove special characters
|
||||
**options: Additional cleaning options
|
||||
|
||||
remove_html: Whether to remove HTML tags (default: True)
|
||||
normalize_whitespace: Whether to normalize whitespace (default: True)
|
||||
normalize_unicode: Whether to normalize Unicode (default: True)
|
||||
remove_special_chars: Whether to remove special characters (default: False)
|
||||
unicode_form: Unicode normalization form (default: "NFC"):
|
||||
- "NFC": Canonical composition
|
||||
- "NFD": Canonical decomposition
|
||||
- "NFKC": Compatibility composition
|
||||
- "NFKD": Compatibility decomposition
|
||||
allow_spaces: Whether to allow spaces when removing special chars (default: True)
|
||||
**options: Additional cleaning options (unused)
|
||||
|
||||
Returns:
|
||||
str: Cleaned text
|
||||
"""
|
||||
@@ -62,7 +127,7 @@ class TextCleaner:
|
||||
|
||||
# Normalize unicode
|
||||
if normalize_unicode:
|
||||
cleaned = self.normalize_unicode(cleaned, form=options.get("unicode_form", "NFC"))
|
||||
cleaned = self.normalize_unicode(cleaned, form=unicode_form)
|
||||
|
||||
# Normalize whitespace
|
||||
if normalize_whitespace:
|
||||
@@ -70,7 +135,7 @@ class TextCleaner:
|
||||
|
||||
# Remove special characters
|
||||
if remove_special_chars:
|
||||
cleaned = self.remove_special_chars(cleaned, allow_spaces=options.get("allow_spaces", True))
|
||||
cleaned = self.remove_special_chars(cleaned, allow_spaces=allow_spaces)
|
||||
|
||||
return cleaned.strip()
|
||||
|
||||
@@ -78,22 +143,28 @@ class TextCleaner:
|
||||
"""
|
||||
Remove HTML tags from text.
|
||||
|
||||
This method removes HTML tags from text, optionally preserving structure
|
||||
using BeautifulSoup for better parsing.
|
||||
|
||||
Args:
|
||||
text: Input text with HTML
|
||||
preserve_structure: Whether to preserve structure (use BeautifulSoup)
|
||||
|
||||
text: Input text containing HTML tags
|
||||
preserve_structure: Whether to preserve structure using BeautifulSoup
|
||||
(default: False, uses regex if False or BeautifulSoup
|
||||
unavailable)
|
||||
|
||||
Returns:
|
||||
str: Text without HTML tags
|
||||
str: Text without HTML tags and with HTML entities decoded
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
if preserve_structure:
|
||||
if preserve_structure and BEAUTIFULSOUP_AVAILABLE and BeautifulSoup:
|
||||
try:
|
||||
soup = BeautifulSoup(text, 'html.parser')
|
||||
return soup.get_text(separator='\n', strip=True)
|
||||
except Exception:
|
||||
# Fallback to regex if BeautifulSoup fails
|
||||
except Exception as e:
|
||||
self.logger.warning(f"BeautifulSoup parsing failed, using regex fallback: {e}")
|
||||
# Fallback to regex
|
||||
pass
|
||||
|
||||
# Remove HTML tags using regex
|
||||
@@ -116,11 +187,15 @@ class TextCleaner:
|
||||
"""
|
||||
Normalize whitespace in text.
|
||||
|
||||
This method normalizes whitespace by replacing tabs and newlines with
|
||||
spaces and collapsing multiple spaces into single spaces.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
|
||||
text: Input text with potentially irregular whitespace
|
||||
|
||||
Returns:
|
||||
str: Text with normalized whitespace
|
||||
str: Text with normalized whitespace (tabs/newlines -> spaces,
|
||||
multiple spaces -> single space)
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
@@ -135,14 +210,21 @@ class TextCleaner:
|
||||
|
||||
def normalize_unicode(self, text: str, form: str = "NFC") -> str:
|
||||
"""
|
||||
Normalize unicode characters.
|
||||
Normalize Unicode characters.
|
||||
|
||||
This method normalizes Unicode characters using the specified
|
||||
normalization form.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
form: Normalization form (NFC, NFD, NFKC, NFKD)
|
||||
|
||||
text: Input text to normalize
|
||||
form: Unicode normalization form (default: "NFC"):
|
||||
- "NFC": Canonical composition
|
||||
- "NFD": Canonical decomposition
|
||||
- "NFKC": Compatibility composition
|
||||
- "NFKD": Compatibility decomposition
|
||||
|
||||
Returns:
|
||||
str: Normalized text
|
||||
str: Unicode-normalized text (returns original text if normalization fails)
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
@@ -153,17 +235,28 @@ class TextCleaner:
|
||||
self.logger.warning(f"Failed to normalize unicode: {e}")
|
||||
return text
|
||||
|
||||
def remove_special_chars(self, text: str, allow_spaces: bool = True, allow_newlines: bool = False) -> str:
|
||||
def remove_special_chars(
|
||||
self,
|
||||
text: str,
|
||||
allow_spaces: bool = True,
|
||||
allow_newlines: bool = False
|
||||
) -> str:
|
||||
"""
|
||||
Remove special characters from text.
|
||||
|
||||
This method removes special characters from text, keeping only
|
||||
alphanumeric characters and optionally spaces and newlines.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
allow_spaces: Whether to allow spaces
|
||||
allow_newlines: Whether to allow newlines
|
||||
|
||||
text: Input text to process
|
||||
allow_spaces: Whether to allow spaces (default: True)
|
||||
allow_newlines: Whether to allow newlines (default: False)
|
||||
|
||||
Returns:
|
||||
str: Text without special characters
|
||||
str: Text with special characters removed, keeping only:
|
||||
- Alphanumeric characters
|
||||
- Spaces (if allow_spaces=True)
|
||||
- Newlines (if allow_newlines=True)
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
@@ -180,16 +273,25 @@ class TextCleaner:
|
||||
|
||||
return re.sub(pattern, '', text)
|
||||
|
||||
def sanitize(self, text: str, **options) -> str:
|
||||
def sanitize(
|
||||
self,
|
||||
text: str,
|
||||
remove_data_urls: bool = False,
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Sanitize text for security.
|
||||
|
||||
This method sanitizes text by removing potentially dangerous content
|
||||
like script tags, iframe tags, and javascript: URLs.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
**options: Sanitization options
|
||||
|
||||
text: Input text to sanitize
|
||||
remove_data_urls: Whether to remove data: URLs (default: False)
|
||||
**options: Additional sanitization options (unused)
|
||||
|
||||
Returns:
|
||||
str: Sanitized text
|
||||
str: Sanitized text with dangerous content removed
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
@@ -202,21 +304,30 @@ class TextCleaner:
|
||||
text = re.sub(r'javascript:', '', text, flags=re.IGNORECASE)
|
||||
|
||||
# Remove data: URLs if requested
|
||||
if options.get("remove_data_urls", False):
|
||||
if remove_data_urls:
|
||||
text = re.sub(r'data:[^;]*;base64,', '', text, flags=re.IGNORECASE)
|
||||
|
||||
return text
|
||||
|
||||
def trim(self, text: str, **options) -> str:
|
||||
def trim(
|
||||
self,
|
||||
text: str,
|
||||
remove_empty_lines: bool = False,
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Trim text.
|
||||
|
||||
This method trims leading and trailing whitespace from text, optionally
|
||||
removing empty lines.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
**options: Trimming options
|
||||
|
||||
text: Input text to trim
|
||||
remove_empty_lines: Whether to remove empty lines (default: False)
|
||||
**options: Additional trimming options (unused)
|
||||
|
||||
Returns:
|
||||
str: Trimmed text
|
||||
str: Trimmed text with leading/trailing whitespace removed
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
@@ -225,21 +336,28 @@ class TextCleaner:
|
||||
text = text.strip()
|
||||
|
||||
# Remove empty lines if requested
|
||||
if options.get("remove_empty_lines", False):
|
||||
if remove_empty_lines:
|
||||
lines = [line for line in text.split('\n') if line.strip()]
|
||||
text = '\n'.join(lines)
|
||||
|
||||
return text
|
||||
|
||||
def clean_batch(self, texts: List[str], **options) -> List[str]:
|
||||
def clean_batch(
|
||||
self,
|
||||
texts: List[str],
|
||||
**options
|
||||
) -> List[str]:
|
||||
"""
|
||||
Clean multiple texts in batch.
|
||||
|
||||
This method processes multiple texts in batch, applying the same
|
||||
cleaning operations to each text.
|
||||
|
||||
Args:
|
||||
texts: List of texts to clean
|
||||
**options: Cleaning options
|
||||
|
||||
**options: Cleaning options (passed to clean method)
|
||||
|
||||
Returns:
|
||||
list: List of cleaned texts
|
||||
list: List of cleaned texts (one per input text)
|
||||
"""
|
||||
return [self.clean(text, **options) for text in texts]
|
||||
|
||||
@@ -1,20 +1,32 @@
|
||||
"""
|
||||
Text Normalization Module
|
||||
|
||||
Handles text cleaning, normalization, and standardization.
|
||||
This module provides comprehensive text normalization capabilities for the
|
||||
Semantica framework, enabling standardization of text content across various
|
||||
formats and encodings.
|
||||
|
||||
Key Features:
|
||||
- Text cleaning and sanitization
|
||||
- Unicode normalization
|
||||
- Case normalization
|
||||
- Whitespace handling
|
||||
- Special character processing
|
||||
- Unicode normalization (NFC, NFD, NFKC, NFKD)
|
||||
- Case normalization (lower, upper, title, preserve)
|
||||
- Whitespace handling (normalization, line breaks, indentation)
|
||||
- Special character processing (punctuation, diacritics)
|
||||
- Format standardization
|
||||
|
||||
Main Classes:
|
||||
- TextNormalizer: Main text normalization class
|
||||
- UnicodeNormalizer: Unicode processing
|
||||
- WhitespaceNormalizer: Whitespace handling
|
||||
- SpecialCharacterProcessor: Special character handling
|
||||
- TextNormalizer: Main text normalization coordinator
|
||||
- UnicodeNormalizer: Unicode processing engine
|
||||
- WhitespaceNormalizer: Whitespace handling engine
|
||||
- SpecialCharacterProcessor: Special character processing engine
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.normalize import TextNormalizer
|
||||
>>> normalizer = TextNormalizer()
|
||||
>>> normalized = normalizer.normalize_text("Hello World", case="lower")
|
||||
>>> cleaned = normalizer.clean_text(text, remove_html=True)
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
"""
|
||||
|
||||
import re
|
||||
@@ -28,23 +40,38 @@ from .text_cleaner import TextCleaner
|
||||
|
||||
class TextNormalizer:
|
||||
"""
|
||||
Text normalization and cleaning handler.
|
||||
Text normalization and cleaning coordinator.
|
||||
|
||||
• Cleans and normalizes text content
|
||||
• Handles various text encodings
|
||||
• Processes special characters and symbols
|
||||
• Standardizes text formatting
|
||||
• Removes unwanted content and noise
|
||||
• Supports multiple languages and scripts
|
||||
This class provides comprehensive text normalization capabilities, coordinating
|
||||
Unicode normalization, whitespace handling, special character processing,
|
||||
and text cleaning.
|
||||
|
||||
Features:
|
||||
- Text cleaning and sanitization
|
||||
- Unicode normalization
|
||||
- Case normalization
|
||||
- Whitespace handling
|
||||
- Special character processing
|
||||
- Format standardization
|
||||
- Batch processing
|
||||
|
||||
Example Usage:
|
||||
>>> normalizer = TextNormalizer()
|
||||
>>> normalized = normalizer.normalize_text("Hello World", case="lower")
|
||||
>>> cleaned = normalizer.clean_text(text, remove_html=True)
|
||||
>>> batch = normalizer.process_batch(texts)
|
||||
"""
|
||||
|
||||
def __init__(self, config=None, **kwargs):
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
"""
|
||||
Initialize text normalizer.
|
||||
|
||||
Sets up the normalizer with text cleaner, Unicode normalizer, whitespace
|
||||
normalizer, and special character processor components.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary
|
||||
**kwargs: Additional configuration options
|
||||
config: Configuration dictionary (optional)
|
||||
**kwargs: Additional configuration options (merged into config)
|
||||
"""
|
||||
self.logger = get_logger("text_normalizer")
|
||||
self.config = config or {}
|
||||
@@ -54,17 +81,44 @@ class TextNormalizer:
|
||||
self.unicode_normalizer = UnicodeNormalizer(**self.config)
|
||||
self.whitespace_normalizer = WhitespaceNormalizer(**self.config)
|
||||
self.special_char_processor = SpecialCharacterProcessor(**self.config)
|
||||
|
||||
self.logger.debug("Text normalizer initialized")
|
||||
|
||||
def normalize_text(self, text: str, **options) -> str:
|
||||
def normalize_text(
|
||||
self,
|
||||
text: str,
|
||||
unicode_form: str = "NFC",
|
||||
case: str = "preserve",
|
||||
normalize_diacritics: bool = False,
|
||||
line_break_type: str = "unix",
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Normalize text content.
|
||||
|
||||
This method performs comprehensive text normalization by applying
|
||||
Unicode normalization, whitespace normalization, special character
|
||||
processing, and case normalization in sequence.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
**options: Normalization options
|
||||
text: Input text to normalize
|
||||
unicode_form: Unicode normalization form (default: "NFC"):
|
||||
- "NFC": Canonical composition
|
||||
- "NFD": Canonical decomposition
|
||||
- "NFKC": Compatibility composition
|
||||
- "NFKD": Compatibility decomposition
|
||||
case: Case normalization type (default: "preserve"):
|
||||
- "preserve": Keep original case
|
||||
- "lower": Convert to lowercase
|
||||
- "upper": Convert to uppercase
|
||||
- "title": Convert to title case
|
||||
normalize_diacritics: Whether to normalize diacritics (default: False)
|
||||
line_break_type: Line break type for whitespace normalization
|
||||
(default: "unix")
|
||||
**options: Additional normalization options (passed to sub-processors)
|
||||
|
||||
Returns:
|
||||
Normalized text
|
||||
str: Normalized text
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
@@ -72,22 +126,24 @@ class TextNormalizer:
|
||||
normalized = text
|
||||
|
||||
# Unicode normalization
|
||||
unicode_form = options.get("unicode_form", "NFC")
|
||||
normalized = self.unicode_normalizer.normalize_unicode(normalized, form=unicode_form)
|
||||
|
||||
# Whitespace normalization
|
||||
normalized = self.whitespace_normalizer.normalize_whitespace(normalized, **options)
|
||||
normalized = self.whitespace_normalizer.normalize_whitespace(
|
||||
normalized, line_break_type=line_break_type, **options
|
||||
)
|
||||
|
||||
# Special character processing
|
||||
normalized = self.special_char_processor.process_special_chars(normalized, **options)
|
||||
normalized = self.special_char_processor.process_special_chars(
|
||||
normalized, normalize_diacritics=normalize_diacritics, **options
|
||||
)
|
||||
|
||||
# Case normalization
|
||||
case_type = options.get("case", "preserve")
|
||||
if case_type == "lower":
|
||||
if case == "lower":
|
||||
normalized = normalized.lower()
|
||||
elif case_type == "upper":
|
||||
elif case == "upper":
|
||||
normalized = normalized.upper()
|
||||
elif case_type == "title":
|
||||
elif case == "title":
|
||||
normalized = normalized.title()
|
||||
|
||||
return normalized.strip()
|
||||
@@ -96,25 +152,37 @@ class TextNormalizer:
|
||||
"""
|
||||
Clean and sanitize text content.
|
||||
|
||||
This method delegates to the text cleaner for comprehensive text cleaning.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
**options: Cleaning options
|
||||
text: Input text to clean
|
||||
**options: Cleaning options (passed to TextCleaner.clean)
|
||||
|
||||
Returns:
|
||||
Cleaned text
|
||||
str: Cleaned text
|
||||
"""
|
||||
return self.text_cleaner.clean(text, **options)
|
||||
|
||||
def standardize_format(self, text: str, format_type: str = "standard") -> str:
|
||||
def standardize_format(
|
||||
self,
|
||||
text: str,
|
||||
format_type: str = "standard"
|
||||
) -> str:
|
||||
"""
|
||||
Standardize text format.
|
||||
|
||||
This method standardizes text format by applying format-specific
|
||||
transformations (compact, preserve, or standard).
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
format_type: Format type ('standard', 'compact', 'preserve')
|
||||
text: Input text to standardize
|
||||
format_type: Format type (default: "standard"):
|
||||
- "standard": Apply standard formatting
|
||||
- "compact": Remove extra whitespace
|
||||
- "preserve": Preserve original formatting
|
||||
|
||||
Returns:
|
||||
Formatted text
|
||||
str: Formatted text
|
||||
"""
|
||||
if format_type == "compact":
|
||||
# Remove extra whitespace
|
||||
@@ -125,16 +193,23 @@ class TextNormalizer:
|
||||
|
||||
return text.strip()
|
||||
|
||||
def process_batch(self, texts: List[str], **options) -> List[str]:
|
||||
def process_batch(
|
||||
self,
|
||||
texts: List[str],
|
||||
**options
|
||||
) -> List[str]:
|
||||
"""
|
||||
Process multiple texts in batch.
|
||||
|
||||
This method processes multiple texts in batch, applying the same
|
||||
normalization operations to each text.
|
||||
|
||||
Args:
|
||||
texts: List of texts
|
||||
**options: Processing options
|
||||
texts: List of texts to process
|
||||
**options: Processing options (passed to normalize_text method)
|
||||
|
||||
Returns:
|
||||
List of processed texts
|
||||
list: List of normalized texts (one per input text)
|
||||
"""
|
||||
return [self.normalize_text(text, **options) for text in texts]
|
||||
|
||||
@@ -143,33 +218,52 @@ class UnicodeNormalizer:
|
||||
"""
|
||||
Unicode normalization engine.
|
||||
|
||||
• Handles Unicode normalization
|
||||
• Processes different Unicode forms
|
||||
• Manages character encoding
|
||||
• Handles special Unicode characters
|
||||
• Supports various scripts and languages
|
||||
This class provides Unicode normalization capabilities, handling different
|
||||
Unicode forms and special character processing.
|
||||
|
||||
Features:
|
||||
- Unicode normalization (NFC, NFD, NFKC, NFKD)
|
||||
- Character encoding conversion
|
||||
- Special Unicode character processing
|
||||
- Support for various scripts and languages
|
||||
|
||||
Example Usage:
|
||||
>>> normalizer = UnicodeNormalizer()
|
||||
>>> normalized = normalizer.normalize_unicode(text, form="NFC")
|
||||
>>> processed = normalizer.process_special_chars(text)
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize Unicode normalizer.
|
||||
|
||||
Sets up the normalizer with configuration options.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("unicode_normalizer")
|
||||
self.config = config
|
||||
|
||||
self.logger.debug("Unicode normalizer initialized")
|
||||
|
||||
def normalize_unicode(self, text: str, form: str = "NFC") -> str:
|
||||
"""
|
||||
Normalize Unicode text.
|
||||
|
||||
This method normalizes Unicode characters using the specified
|
||||
normalization form.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
form: Unicode normalization form (NFC, NFD, NFKC, NFKD)
|
||||
text: Input text to normalize
|
||||
form: Unicode normalization form (default: "NFC"):
|
||||
- "NFC": Canonical composition
|
||||
- "NFD": Canonical decomposition
|
||||
- "NFKC": Compatibility composition
|
||||
- "NFKD": Compatibility decomposition
|
||||
|
||||
Returns:
|
||||
Normalized text
|
||||
str: Unicode-normalized text (returns original text if normalization fails)
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
@@ -180,17 +274,26 @@ class UnicodeNormalizer:
|
||||
self.logger.warning(f"Unicode normalization failed: {e}")
|
||||
return text
|
||||
|
||||
def handle_encoding(self, text: str, source_encoding: str, target_encoding: str = "utf-8") -> str:
|
||||
def handle_encoding(
|
||||
self,
|
||||
text: str,
|
||||
source_encoding: str,
|
||||
target_encoding: str = "utf-8"
|
||||
) -> str:
|
||||
"""
|
||||
Handle text encoding conversion.
|
||||
|
||||
This method converts text from one encoding to another, handling
|
||||
both string and bytes input.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
source_encoding: Source encoding
|
||||
target_encoding: Target encoding
|
||||
text: Input text (string or bytes)
|
||||
source_encoding: Source encoding name (e.g., "latin-1", "cp1252")
|
||||
target_encoding: Target encoding name (default: "utf-8")
|
||||
|
||||
Returns:
|
||||
Converted text
|
||||
str: Converted text in target encoding (falls back to UTF-8 with
|
||||
error replacement if conversion fails)
|
||||
"""
|
||||
if isinstance(text, bytes):
|
||||
try:
|
||||
@@ -204,11 +307,14 @@ class UnicodeNormalizer:
|
||||
"""
|
||||
Process special Unicode characters.
|
||||
|
||||
This method replaces common special Unicode characters (smart quotes,
|
||||
dashes, ellipsis) with their ASCII equivalents.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
text: Input text containing special Unicode characters
|
||||
|
||||
Returns:
|
||||
Processed text
|
||||
str: Text with special Unicode characters replaced with ASCII equivalents
|
||||
"""
|
||||
# Replace common special characters
|
||||
replacements = {
|
||||
@@ -231,32 +337,56 @@ class WhitespaceNormalizer:
|
||||
"""
|
||||
Whitespace normalization engine.
|
||||
|
||||
• Normalizes whitespace characters
|
||||
• Handles different whitespace types
|
||||
• Manages line breaks and spacing
|
||||
• Processes indentation and formatting
|
||||
This class provides whitespace normalization capabilities, handling
|
||||
different whitespace types, line breaks, and indentation.
|
||||
|
||||
Features:
|
||||
- Whitespace character normalization
|
||||
- Line break handling (Unix, Windows, Mac)
|
||||
- Indentation processing
|
||||
- Multiple whitespace collapse
|
||||
|
||||
Example Usage:
|
||||
>>> normalizer = WhitespaceNormalizer()
|
||||
>>> normalized = normalizer.normalize_whitespace(text, line_break_type="unix")
|
||||
>>> processed = normalizer.process_indentation(text, indent_type="spaces")
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize whitespace normalizer.
|
||||
|
||||
Sets up the normalizer with configuration options.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("whitespace_normalizer")
|
||||
self.config = config
|
||||
|
||||
self.logger.debug("Whitespace normalizer initialized")
|
||||
|
||||
def normalize_whitespace(self, text: str, **options) -> str:
|
||||
def normalize_whitespace(
|
||||
self,
|
||||
text: str,
|
||||
line_break_type: str = "unix",
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Normalize whitespace in text.
|
||||
|
||||
This method normalizes whitespace by replacing tabs with spaces,
|
||||
normalizing line breaks, and collapsing multiple spaces.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
**options: Normalization options
|
||||
text: Input text with potentially irregular whitespace
|
||||
line_break_type: Line break type (default: "unix"):
|
||||
- "unix": Unix-style line breaks (\n)
|
||||
- "windows": Windows-style line breaks (\r\n)
|
||||
**options: Additional normalization options (unused)
|
||||
|
||||
Returns:
|
||||
Normalized text
|
||||
str: Text with normalized whitespace
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
@@ -265,7 +395,6 @@ class WhitespaceNormalizer:
|
||||
text = text.replace('\t', ' ')
|
||||
|
||||
# Normalize line breaks
|
||||
line_break_type = options.get("line_break_type", "unix")
|
||||
text = self.handle_line_breaks(text, line_break_type)
|
||||
|
||||
# Remove excessive whitespace
|
||||
@@ -274,16 +403,26 @@ class WhitespaceNormalizer:
|
||||
|
||||
return text.strip()
|
||||
|
||||
def handle_line_breaks(self, text: str, line_break_type: str = "unix") -> str:
|
||||
def handle_line_breaks(
|
||||
self,
|
||||
text: str,
|
||||
line_break_type: str = "unix"
|
||||
) -> str:
|
||||
"""
|
||||
Normalize line breaks.
|
||||
|
||||
This method normalizes line breaks to the specified type (Unix, Windows,
|
||||
or Mac).
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
line_break_type: Line break type ('unix', 'windows', 'mac')
|
||||
text: Input text with potentially mixed line breaks
|
||||
line_break_type: Line break type (default: "unix"):
|
||||
- "unix": Unix-style (\n)
|
||||
- "windows": Windows-style (\r\n)
|
||||
- "mac": Mac-style (\r)
|
||||
|
||||
Returns:
|
||||
Normalized text
|
||||
str: Text with normalized line breaks
|
||||
"""
|
||||
if line_break_type == "unix":
|
||||
text = text.replace('\r\n', '\n')
|
||||
@@ -295,16 +434,25 @@ class WhitespaceNormalizer:
|
||||
|
||||
return text
|
||||
|
||||
def process_indentation(self, text: str, indent_type: str = "spaces") -> str:
|
||||
def process_indentation(
|
||||
self,
|
||||
text: str,
|
||||
indent_type: str = "spaces"
|
||||
) -> str:
|
||||
"""
|
||||
Normalize text indentation.
|
||||
|
||||
This method normalizes text indentation by converting between tabs
|
||||
and spaces.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
indent_type: Indentation type ('spaces', 'tabs')
|
||||
text: Input text with potentially mixed indentation
|
||||
indent_type: Indentation type (default: "spaces"):
|
||||
- "spaces": Convert tabs to 4 spaces
|
||||
- "tabs": Convert 4 spaces to tabs
|
||||
|
||||
Returns:
|
||||
Normalized text
|
||||
str: Text with normalized indentation
|
||||
"""
|
||||
if indent_type == "spaces":
|
||||
text = text.replace('\t', ' ') # Convert tabs to 4 spaces
|
||||
@@ -318,39 +466,60 @@ class SpecialCharacterProcessor:
|
||||
"""
|
||||
Special character processing engine.
|
||||
|
||||
• Processes special characters and symbols
|
||||
• Handles punctuation and diacritics
|
||||
• Manages mathematical symbols
|
||||
• Processes currency and unit symbols
|
||||
This class provides special character processing capabilities, including
|
||||
punctuation normalization and diacritic handling.
|
||||
|
||||
Features:
|
||||
- Punctuation normalization (quotes, dashes, ellipsis)
|
||||
- Diacritic processing (normalization or removal)
|
||||
- Special character replacement
|
||||
|
||||
Example Usage:
|
||||
>>> processor = SpecialCharacterProcessor()
|
||||
>>> processed = processor.process_special_chars(text, normalize_diacritics=True)
|
||||
>>> normalized = processor.normalize_punctuation(text)
|
||||
"""
|
||||
|
||||
def __init__(self, **config):
|
||||
"""
|
||||
Initialize special character processor.
|
||||
|
||||
Sets up the processor with configuration options.
|
||||
|
||||
Args:
|
||||
**config: Configuration options
|
||||
**config: Configuration options (currently unused)
|
||||
"""
|
||||
self.logger = get_logger("special_char_processor")
|
||||
self.config = config
|
||||
|
||||
self.logger.debug("Special character processor initialized")
|
||||
|
||||
def process_special_chars(self, text: str, **options) -> str:
|
||||
def process_special_chars(
|
||||
self,
|
||||
text: str,
|
||||
normalize_diacritics: bool = False,
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Process special characters in text.
|
||||
|
||||
This method processes special characters by normalizing punctuation
|
||||
and optionally processing diacritics.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
**options: Processing options
|
||||
text: Input text to process
|
||||
normalize_diacritics: Whether to normalize diacritics (default: False)
|
||||
**options: Additional processing options (passed to process_diacritics)
|
||||
|
||||
Returns:
|
||||
Processed text
|
||||
str: Text with special characters processed
|
||||
"""
|
||||
# Normalize punctuation
|
||||
text = self.normalize_punctuation(text)
|
||||
|
||||
# Process diacritics if requested
|
||||
if options.get("normalize_diacritics", False):
|
||||
text = self.process_diacritics(text)
|
||||
if normalize_diacritics:
|
||||
text = self.process_diacritics(text, **options)
|
||||
|
||||
return text
|
||||
|
||||
@@ -358,11 +527,14 @@ class SpecialCharacterProcessor:
|
||||
"""
|
||||
Normalize punctuation marks.
|
||||
|
||||
This method normalizes various Unicode punctuation marks to their
|
||||
ASCII equivalents (quotes, dashes, ellipsis).
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
text: Input text with potentially mixed punctuation
|
||||
|
||||
Returns:
|
||||
Normalized text
|
||||
str: Text with normalized punctuation marks
|
||||
"""
|
||||
# Normalize quotes
|
||||
text = re.sub(r'["""]', '"', text)
|
||||
@@ -376,18 +548,29 @@ class SpecialCharacterProcessor:
|
||||
|
||||
return text
|
||||
|
||||
def process_diacritics(self, text: str, **options) -> str:
|
||||
def process_diacritics(
|
||||
self,
|
||||
text: str,
|
||||
remove_diacritics: bool = False,
|
||||
**options
|
||||
) -> str:
|
||||
"""
|
||||
Process diacritical marks.
|
||||
|
||||
This method processes diacritical marks by either normalizing them
|
||||
(NFC) or removing them entirely.
|
||||
|
||||
Args:
|
||||
text: Input text
|
||||
**options: Processing options
|
||||
text: Input text with diacritical marks
|
||||
remove_diacritics: Whether to remove diacritics (default: False):
|
||||
- True: Remove all diacritical marks
|
||||
- False: Normalize diacritics using NFC
|
||||
**options: Additional processing options (unused)
|
||||
|
||||
Returns:
|
||||
Processed text
|
||||
str: Text with diacritics processed (normalized or removed)
|
||||
"""
|
||||
if options.get("remove_diacritics", False):
|
||||
if remove_diacritics:
|
||||
# Remove diacritics
|
||||
nfd = unicodedata.normalize('NFD', text)
|
||||
return ''.join(c for c in nfd if unicodedata.category(c) != 'Mn')
|
||||
|
||||
Reference in New Issue
Block a user