Source code for flexicon.code.Lexicon.allomorph

#
#   allomorph.py
#
#   Class: Allomorph
#          Wrapper for allomorph objects providing unified interface
#          access across multiple concrete types.
#
#   Platform: Python.NET
#             FieldWorks Version 9+
#
#   Copyright 2025
#

"""
Wrapper class for allomorph objects with unified interface.

This module provides Allomorph, a wrapper class that transparently
handles the two concrete types of allomorphs:
- MoStemAllomorph: Allomorphs of stems (StemName property)
- MoAffixAllomorph: Allomorphs of affixes (AffixType property)

The wrapper exposes a unified interface for accessing common properties
and provides convenience methods for checking type-specific capabilities
without exposing the underlying ClassName or casting complexity.

Problem:
    Allomorphs have different properties depending on their concrete type:
    - MoStemAllomorph has StemName property
    - MoAffixAllomorph has AffixType property

    Both have Form (ITsString), PhEnvironmentRC, Gloss, etc.

    Users working with mixed collections need to check ClassName and cast to
    access type-specific properties, which is error-prone and verbose.

Solution:
    Allomorph wrapper provides:
    - Simple properties for common features (form, environment, gloss)
    - Capability check properties (is_stem_allomorph, is_affix_allomorph)
    - Property access that works across all types
    - Optional: Methods for advanced users who know C# types

Example::

    from flexlibs2.code.Lexicon.allomorph import Allomorph

    # Wrap an allomorph from GetAll()
    allomorph = allomorphOps.GetAll(entry)[0]  # Typed as IMoForm
    wrapped = Allomorph(allomorph)

    # Access common properties
    print(wrapped.form)  # Works for all allomorph types
    print(wrapped.environment)

    # Check capabilities
    if wrapped.is_stem_allomorph:
        print("This is a stem allomorph")

    # Optional: Advanced users can access concrete types
    if wrapped.as_stem_allomorph():
        concrete = wrapped.as_stem_allomorph()
        # Use concrete interface for advanced operations
"""

from ..Shared.wrapper_base import LCMObjectWrapper
from ..lcm_casting import cast_to_concrete


[docs] class Allomorph(LCMObjectWrapper): """ Wrapper for allomorph objects providing unified interface access. Handles the two concrete types of allomorphs (MoStemAllomorph, MoAffixAllomorph) transparently, providing common properties and capability checks without exposing ClassName or casting. Attributes: _obj: The base interface object (IMoForm) _concrete: The concrete type object (IMoStemAllomorph or IMoAffixAllomorph) Example:: allomorph = allomorphOps.GetAll(entry)[0] wrapped = Allomorph(allomorph) print(wrapped.form) print(wrapped.environment) if wrapped.is_stem_allomorph: print("Stem allomorph") """ def __init__(self, lcm_allomorph): """ Initialize Allomorph wrapper with an allomorph object. Args: lcm_allomorph: An IMoForm object (or derived type). Typically from AllomorphOperations.GetAll(). Example:: allomorph = allomorphOps.GetAll(entry)[0] wrapped = Allomorph(allomorph) """ super().__init__(lcm_allomorph) # ========== Common Properties (work across all allomorph types) ========== @property def form(self) -> str: """ Get the form (text) of this allomorph. Returns: str: The allomorph form, normalized and stripped of FLEx null markers. Example:: print(f"Allomorph form: {wrapped.form}") # Output: "walk" or "-ing" Notes: - Uses vernacular writing system by default - Empty string returned if form not set - FLEx null marker (``***``) is normalized to empty string """ try: from SIL.LCModel.Core.KernelInterfaces import ITsString from ..Shared.string_utils import normalize_text form_multistring = self._obj.Form if form_multistring: # Get from default vernacular writing system default_ws = self._obj.Cache.DefaultVernWs form_text = ITsString(form_multistring.get_String(default_ws)).Text return normalize_text(form_text) return "" except Exception: return "" @property def gloss(self) -> str: """ Get the gloss of this allomorph. Returns: str: The allomorph gloss, or empty string if not set. Example:: if wrapped.gloss: print(f"Gloss: {wrapped.gloss}") Notes: - Gloss is typically empty for allomorphs (glosses go on senses) - This is included for completeness as allomorphs have Gloss field """ try: from SIL.LCModel.Core.KernelInterfaces import ITsString from ..Shared.string_utils import normalize_text if hasattr(self._concrete, "Gloss") and self._concrete.Gloss: # Get from default analysis writing system default_ws = self._obj.Cache.DefaultAnalWs gloss_text = ITsString(self._concrete.Gloss.get_String(default_ws)).Text return normalize_text(gloss_text) return "" except Exception: return "" @property def environment(self) -> "list[object]": """ Get the phonological environments for this allomorph. Returns: list: List of IPhEnvironment objects defining where this allomorph occurs. Empty list if no environments specified (appears in all contexts). Example:: for env in wrapped.environment: print(f"Environment: {env.Name}") if not wrapped.environment: print("This allomorph appears in all contexts") Notes: - Multiple environments use OR logic - Empty list means allomorph appears in any context - Environments guide the parser in selecting the appropriate allomorph """ try: if hasattr(self._concrete, "PhoneEnvRC"): return list(self._concrete.PhoneEnvRC) return [] except Exception: return [] # ========== Type Detection Properties ========== @property def is_stem_allomorph(self): """ Check if this is a stem allomorph. Returns: bool: True if this is a MoStemAllomorph, False otherwise. Example:: if wrapped.is_stem_allomorph: print("This is a stem allomorph") else: print("This is an affix allomorph") Notes: - MoStemAllomorph allomorphs are variants of stems/roots - MoAffixAllomorph allomorphs are variants of affixes """ try: return self.class_type == "MoStemAllomorph" except Exception: return False @property def is_affix_allomorph(self): """ Check if this is an affix allomorph. Returns: bool: True if this is a MoAffixAllomorph, False otherwise. Example:: if wrapped.is_affix_allomorph: print("This is an affix allomorph") else: print("This is a stem allomorph") Notes: - MoAffixAllomorph allomorphs are variants of prefixes, suffixes, etc. - MoStemAllomorph allomorphs are variants of stems/roots """ try: return self.class_type == "MoAffixAllomorph" except Exception: return False # ========== Type-Specific Property Access ========== @property def stem_name(self): """ Get the stem name (only for stem allomorphs). Returns: str: The stem name if this is a MoStemAllomorph, empty string otherwise. Example:: if wrapped.is_stem_allomorph: print(f"Stem name: {wrapped.stem_name}") Notes: - Only available on MoStemAllomorph - Returns empty string for MoAffixAllomorph - StemName is a multistring property """ if not self.is_stem_allomorph: return "" try: from SIL.LCModel.Core.KernelInterfaces import ITsString from ..Shared.string_utils import normalize_text if hasattr(self._concrete, "StemName") and self._concrete.StemName: # Get from default analysis writing system default_ws = self._obj.Cache.DefaultAnalWs stem_name_text = ITsString(self._concrete.StemName.get_String(default_ws)).Text return normalize_text(stem_name_text) return "" except Exception: return "" @property def affix_type(self): """ Get the affix type (only for affix allomorphs). Returns: IMoMorphType or None: The affix type if this is a MoAffixAllomorph, None otherwise. Example:: if wrapped.is_affix_allomorph and wrapped.affix_type: print(f"Affix type: {wrapped.affix_type.Name}") Notes: - Only available on MoAffixAllomorph - Returns None for MoStemAllomorph - AffixType indicates prefix, suffix, infix, etc. """ if not self.is_affix_allomorph: return None try: if hasattr(self._concrete, "AffixType"): return self._concrete.AffixType return None except Exception: return None # ========== Advanced: Direct C# class access (optional for power users) ==========
[docs] def as_stem_allomorph(self): """ Cast to IMoStemAllomorph if this is a stem allomorph. For advanced users who need direct access to the C# concrete interface. Returns None if this is not a MoStemAllomorph. Returns: IMoStemAllomorph or None: The concrete interface if this is a MoStemAllomorph, None otherwise. Example:: if allomorph_obj.as_stem_allomorph(): concrete = allomorph_obj.as_stem_allomorph() # Can now access IMoStemAllomorph-specific methods/properties stem_name = concrete.StemName # Advanced operations... Notes: - For users who know C# interfaces and want advanced control - Most users should use properties like is_stem_allomorph and stem_name instead - Only useful if you need to call methods or access properties that aren't exposed through the wrapper """ if self.class_type == "MoStemAllomorph": return self._concrete return None
[docs] def as_affix_allomorph(self): """ Cast to IMoAffixAllomorph if this is an affix allomorph. For advanced users who need direct access to the C# concrete interface. Returns None if this is not a MoAffixAllomorph. Returns: IMoAffixAllomorph or None: The concrete interface if this is a MoAffixAllomorph, None otherwise. Example:: if allomorph_obj.as_affix_allomorph(): concrete = allomorph_obj.as_affix_allomorph() # Can now access IMoAffixAllomorph-specific methods/properties affix_type = concrete.AffixType # Advanced operations... Notes: - For users who know C# interfaces and want advanced control - Most users should use properties like is_affix_allomorph and affix_type instead """ if self.class_type == "MoAffixAllomorph": return self._concrete return None
@property def concrete(self): """ Get the raw concrete interface object. For advanced users who need to access the underlying C# interface directly without going through wrapper properties. Returns: The concrete interface object (IMoStemAllomorph or IMoAffixAllomorph depending on the allomorph's actual type). Example:: # Direct access to concrete interface concrete = allomorph_obj.concrete stem_name = concrete.StemName # MoStemAllomorph property Notes: - For power users only - Bypasses the wrapper's abstraction - Normal users should prefer wrapper properties like is_stem_allomorph, stem_name, etc. """ return self._concrete def __repr__(self): """String representation showing allomorph form and type.""" form = self.form or "Unnamed" return f"Allomorph({form}, {self.class_type})" def __str__(self): """Human-readable description.""" form = self.form or "Unnamed" type_name = "Stem" if self.is_stem_allomorph else "Affix" return f"{type_name} Allomorph '{form}'"