Source code for flexicon.code.Lexicon.msa_collection

#
#   msa_collection.py
#
#   Class: MSACollection
#          Smart collection for morphosyntactic analyses with type-aware filtering
#          and unified access across multiple concrete types.
#
#   Platform: Python.NET
#             FieldWorks Version 9+
#
#   Copyright 2025
#

"""
Smart collection class for morphosyntactic analyses.

This module provides MSACollection, a smart collection that manages
morphosyntactic analyses while showing type diversity and supporting unified
operations across the four concrete types:
- MoStemMsa
- MoDerivAffMsa
- MoInflAffMsa
- MoUnclassifiedAffixMsa

Problem:
    entry.MorphoSyntaxAnalysesOC returns objects with multiple concrete
    implementations. Users need to:

    - See which types are in the collection
    - Filter by type if they want to
    - Work with all types together without manual casting
    - Filter by common properties that work across all types

Solution:
    MSACollection provides:

    - __str__() showing type breakdown
    - by_type() filtering to specific concrete types
    - filter() for common criteria (pos_main, has_pos, where_predicate)
    - Convenience methods (stem_msas(), deriv_aff_msas(), infl_aff_msas(),
      unclassified_aff_msas())
    - Chainable filtering: msas.stem_msas().filter(pos_main=some_pos)

Example::

    from flexlibs2.code.Lexicon.msa_collection import MSACollection

    # entry.MorphoSyntaxAnalysesOC already wrapped by LexEntryOperations
    msas = entry_ops.GetMorphoSyntaxAnalyses(entry)
    print(msas)  # Shows type breakdown
    # MSACollection (8 total)
    #   MoStemMsa: 4 (50%)
    #   MoDerivAffMsa: 2 (25%)
    #   MoInflAffMsa: 2 (25%)

    # Filter by type
    stem_only = msas.stem_msas()
    print(len(stem_only))  # 4

    # Filter by POS
    target_pos = ...
    matching = msas.filter(pos_main=target_pos)

    # Chain filters
    stem_with_pos = msas.stem_msas().filter(pos_main=target_pos)

    # Iterate
    for msa in msas:
        print(msa.class_type)
"""

from ..Shared.smart_collection import SmartCollection
from .morphosyntax_analysis import MorphosyntaxAnalysis


[docs] class MSACollection(SmartCollection): """ Smart collection for morphosyntactic analyses with type-aware filtering. Manages collections of morphosyntactic analysis objects (MorphosyntaxAnalysis wrapper objects) with type-aware display and filtering capabilities. Supports filtering by common properties (POS, has_pos) and by concrete type. Attributes: _items: List of MorphosyntaxAnalysis wrapper objects Example:: msas = entry_ops.GetMorphoSyntaxAnalyses(entry) print(msas) # Shows type breakdown stem = msas.stem_msas() # Filter to MoStemMsa with_pos = msas.filter(pos_main=target_pos) # POS filter both = msas.stem_msas().filter(pos_main=target_pos) # Chain """ def __init__(self, items=None): """ Initialize an MSACollection. Args: items: Iterable of MorphosyntaxAnalysis objects, or None for empty. Example:: collection = MSACollection() collection = MSACollection([msa1, msa2, msa3]) """ super().__init__(items)
[docs] def filter(self, pos_main=None, has_pos=None, where=None): """ Filter the collection by common MSA properties. Supports filtering by properties that work across all MSA types (pos_main, has_pos). For complex filtering, use where(). Args: pos_main (IPartOfSpeech, optional): Filter to MSAs with this Part of Speech (uses GUID comparison). For derivational affixes, this filters by ToPartOfSpeechRA. has_pos (bool, optional): If True, only include MSAs with a pos_main set. If False, only include MSAs without pos_main. where (callable, optional): Custom predicate function. If provided, other criteria are ignored. Returns: MSACollection: New collection with filtered items. Example:: # Filter by POS target_pos = ... matching = msas.filter(pos_main=target_pos) # Filter by presence of POS with_pos = msas.filter(has_pos=True) without_pos = msas.filter(has_pos=False) # Custom filtering deriv_with_pos = msas.where( lambda m: m.is_deriv_aff_msa and m.has_from_pos ) # Chain filters stem_with_pos = msas.stem_msas().filter(pos_main=target_pos) Notes: - pos_main comparison uses GUID string comparison - has_pos and pos_main filters are AND-ed together - where() takes precedence over other criteria - Returns new collection (doesn't modify original) - Use where() for complex custom filtering """ # If custom predicate provided, use it if where is not None: filtered = [item for item in self._items if where(item)] return MSACollection(filtered) # Apply criteria filters filtered = self._items if has_pos is not None: if has_pos: # Include only MSAs with pos_main set filtered = [msa for msa in filtered if msa.pos_main is not None] else: # Include only MSAs without pos_main filtered = [msa for msa in filtered if msa.pos_main is None] if pos_main is not None: # Get the GUID of the target POS for comparison target_guid = None if hasattr(pos_main, "Guid"): target_guid = str(pos_main.Guid) else: target_guid = str(pos_main) filtered = [msa for msa in filtered if msa.pos_main is not None and str(msa.pos_main.Guid) == target_guid] return MSACollection(filtered)
[docs] def where(self, predicate): """ Filter using a custom predicate function. For filtering by complex criteria or properties not supported by filter(). Args: predicate (callable): Function that takes a MorphosyntaxAnalysis and returns True to include it in the result. Returns: MSACollection: New collection with items matching the predicate. Example:: # Complex predicate: derivational affixes with from/to POS deriv_with_both = msas.where( lambda m: m.is_deriv_aff_msa and m.pos_from is not None and m.pos_to is not None ) # Combining conditions specific = msas.where( lambda m: m.is_stem_msa and m.pos_main is not None ) # Type checking combined with other criteria non_stem = msas.where(lambda m: not m.is_stem_msa) Notes: - Predicate receives each MorphosyntaxAnalysis object - Return True to include in result, False to exclude - For simple filters (pos_main, has_pos), use filter() instead """ filtered = [msa for msa in self._items if predicate(msa)] return MSACollection(filtered)
[docs] def stem_msas(self): """ Get only the stem (MoStemMsa) MSAs from the collection. Convenience method for filtering to MoStemMsa objects only. Returns: MSACollection: New collection with only MoStemMsa objects. Example:: stem = msas.stem_msas() print(f"Found {len(stem)} stem MSAs") # Chain with other filters stem_with_pos = msas.stem_msas().filter(has_pos=True) Notes: - Equivalent to by_type('MoStemMsa') - Stem MSAs have PartOfSpeechRA for the main POS - Use is_stem_msa on individual MSAs to check type """ return self.by_type("MoStemMsa")
[docs] def deriv_aff_msas(self): """ Get only the derivational affix (MoDerivAffMsa) MSAs from the collection. Convenience method for filtering to MoDerivAffMsa objects only. Returns: MSACollection: New collection with only MoDerivAffMsa objects. Example:: deriv = msas.deriv_aff_msas() print(f"Found {len(deriv)} derivational affix MSAs") # Chain with other filters deriv_with_from = msas.deriv_aff_msas().filter(has_pos=True) Notes: - Equivalent to by_type('MoDerivAffMsa') - Derivational affixes have FromPartOfSpeechRA and ToPartOfSpeechRA - Use is_deriv_aff_msa on individual MSAs to check type """ return self.by_type("MoDerivAffMsa")
[docs] def infl_aff_msas(self): """ Get only the inflectional affix (MoInflAffMsa) MSAs from the collection. Convenience method for filtering to MoInflAffMsa objects only. Returns: MSACollection: New collection with only MoInflAffMsa objects. Example:: infl = msas.infl_aff_msas() print(f"Found {len(infl)} inflectional affix MSAs") # Chain with other filters infl_with_pos = msas.infl_aff_msas().filter(has_pos=True) Notes: - Equivalent to by_type('MoInflAffMsa') - Inflectional affixes have PartOfSpeechRA and slot references - Use is_infl_aff_msa on individual MSAs to check type """ return self.by_type("MoInflAffMsa")
[docs] def unclassified_aff_msas(self): """ Get only the unclassified affix (MoUnclassifiedAffixMsa) MSAs from the collection. Convenience method for filtering to MoUnclassifiedAffixMsa objects only. Returns: MSACollection: New collection with only MoUnclassifiedAffixMsa objects. Example:: unclass = msas.unclassified_aff_msas() print(f"Found {len(unclass)} unclassified affix MSAs") # Chain with other filters unclass_with_pos = msas.unclassified_aff_msas().filter(has_pos=True) Notes: - Equivalent to by_type('MoUnclassifiedAffixMsa') - Unclassified affixes have PartOfSpeechRA - Use is_unclassified_aff_msa on individual MSAs to check type """ return self.by_type("MoUnclassifiedAffixMsa")
def __repr__(self): """Technical representation.""" return f"MSACollection({len(self._items)} items)"