Source code for flexicon.code.Lists.possibility_item_base

#
#   possibility_item_base.py
#
#   Class: PossibilityItemOperations
#          Base class for operations on specialized possibility list items
#          (Publications, Confidence Levels, Translation Types, Agents, Overlays)
#
#   Platform: Python.NET
#             FieldWorks Version 9+
#
#   Copyright 2025
#

# Import FLEx LCM types
from SIL.LCModel import ICmPossibilityFactory
from SIL.LCModel.Core.KernelInterfaces import ITsString
from SIL.LCModel.Core.Text import TsStringUtils

# Import flexlibs exceptions
from ..FLExProject import FP_ParameterError, FP_NullParameterError
from ..BaseOperations import BaseOperations, OperationsMethod, wrap_enumerable
from ..Shared.string_utils import normalize_match_key


[docs] class PossibilityItemOperations(BaseOperations): """ Base class for operations on specialized possibility list items. Provides unified CRUD operations for possibility-based items like Publications, Confidence Levels, Translation Types, Agents, and Overlays. Each subclass specializes the base class by: 1. Accessing the appropriate list object (e.g., ConfidenceLevelsOA) 2. Specifying the item class name (e.g., 'Confidence', 'Publication') 3. Providing domain-specific methods (e.g., GetPageWidth, IsVisible) Shared Operations (inherited by all subclasses): - GetAll() - Get all items (basic implementation, may be overridden) - Create() - Create new item with validation - Delete() - Delete item from list - Duplicate() - Clone item with new GUID - Find() - Find item by name - Exists() - Check if item exists by name - GetName() / SetName() - Get/set item name - GetDescription() / SetDescription() - Get/set item description - GetSyncableProperties() - Return dict of syncable properties - CompareTo() - Compare two items - GetGuid() - Get GUID Subclasses must implement: - __init__() - Call super().__init__(project) - _GetSequence() - Return parent.SubPossibilitiesOS (for reordering) - _get_item_class_name() - Return item class name string - _get_list_object() - Return the list container object Example subclass structure:: class ConfidenceOperations(PossibilityItemOperations): def _get_item_class_name(self): return "Confidence" def _get_list_object(self): return self.project.lp.ConfidenceLevelsOA # Domain-specific methods def GetAnalysesWithConfidence(self, level): ... """ def _GetSequence(self, parent): """Specify which sequence to reorder for item sub-possibilities. Returns: The SubPossibilitiesOS sequence for reordering operations. """ return parent.SubPossibilitiesOS def _get_item_class_name(self): """Get the item class name for error messages and type checking. Must be overridden by subclass. Returns: str: Item class name (e.g., 'Confidence', 'Publication') """ raise NotImplementedError( f"{self.__class__.__name__} must implement _get_item_class_name()" ) def _get_list_object(self): """Get the list container object for this item type. Must be overridden by subclass to return the appropriate list object (e.g., self.project.lp.ConfidenceLevelsOA). Returns: The ICmPossibilityList object containing items """ raise NotImplementedError( f"{self.__class__.__name__} must implement _get_list_object()" ) # ========== SHARED HELPER METHODS ========== def __WSHandle(self, wsHandle): """Resolve writing system handle to default if None. Args: wsHandle: Writing system handle or None. Returns: int: Resolved writing system handle. """ if wsHandle is None: return self.project.project.DefaultAnalWs return wsHandle def __ResolveObject(self, obj_or_hvo): """Resolve either an object or HVO to the object. Args: obj_or_hvo: Either an ICmPossibility object or its HVO (int). Returns: The resolved ICmPossibility object. Raises: FP_ParameterError: If HVO doesn't refer to valid object. """ if isinstance(obj_or_hvo, int): obj = self.project.Object(obj_or_hvo) if obj is None: raise FP_ParameterError( f"HVO {obj_or_hvo} does not refer to a valid {self._get_item_class_name()}" ) return obj return obj_or_hvo # ========== CORE CRUD OPERATIONS ========== @wrap_enumerable @OperationsMethod def GetAll(self): """Get all items in the list. Returns: list: List of ICmPossibility objects representing items. Notes: - Returns flat list of all items - Returns empty list if no items exist - Subclasses may override for specialized behavior """ list_obj = self._get_list_object() if not list_obj: return [] return list(list_obj.PossibilitiesOS) @OperationsMethod def Create(self, name, wsHandle=None): """Create a new item. Args: name (str): The name of the new item. wsHandle: Optional writing system handle. Defaults to analysis WS. Returns: ICmPossibility: The newly created item object. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If name is None. FP_ParameterError: If name is empty or list doesn't exist. """ self._EnsureWriteEnabled() self._ValidateParam(name, "name") if not name or not name.strip(): raise FP_ParameterError(f"{self._get_item_class_name()} name cannot be empty") # Get the list object list_obj = self._get_list_object() if not list_obj: raise FP_ParameterError( f"{self._get_item_class_name()} list not found in project" ) wsHandle = self.__WSHandle(wsHandle) # Create the new item using the factory factory = self.project.project.ServiceLocator.GetService(ICmPossibilityFactory) with self._TransactionCM(f"Create {self._get_item_class_name()} {name!r}"): new_item = factory.Create() # Add to list (must be done before setting properties) list_obj.PossibilitiesOS.Add(new_item) # Set name mkstr = TsStringUtils.MakeString(name, wsHandle) new_item.Name.set_String(wsHandle, mkstr) return new_item @OperationsMethod def Delete(self, item_or_hvo): """Delete an item from the list. Args: item_or_hvo: Either an ICmPossibility object or its HVO. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If item_or_hvo is None. """ self._EnsureWriteEnabled() self._ValidateParam(item_or_hvo, "item_or_hvo") item = self.__ResolveObject(item_or_hvo) # Remove from list list_obj = self._get_list_object() if list_obj and item in list_obj.PossibilitiesOS: list_obj.PossibilitiesOS.Remove(item) @OperationsMethod def Duplicate(self, item_or_hvo, insert_after=True): """Duplicate an item, creating a new copy with a new GUID. Args: item_or_hvo: Either an ICmPossibility object or its HVO to duplicate. insert_after (bool): If True (default), insert after the source item. If False, insert at end of list. Returns: ICmPossibility: The newly created duplicate item with a new GUID. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If item_or_hvo is None. """ self._EnsureWriteEnabled() self._ValidateParam(item_or_hvo, "item_or_hvo") # Get source item source = self.__ResolveObject(item_or_hvo) # Get the list object list_obj = self._get_list_object() if not list_obj: raise FP_ParameterError( f"{self._get_item_class_name()} list not found in project" ) # Create new item using factory (auto-generates new GUID) factory = self.project.project.ServiceLocator.GetService(ICmPossibilityFactory) with self._TransactionCM(f"Duplicate {self._get_item_class_name()}"): duplicate = factory.Create() # ADD TO PARENT FIRST before copying properties (CRITICAL) if insert_after: # Insert after source item source_index = list_obj.PossibilitiesOS.IndexOf(source) list_obj.PossibilitiesOS.Insert(source_index + 1, duplicate) else: # Insert at end list_obj.PossibilitiesOS.Add(duplicate) # Copy MultiString properties using CopyAlternatives duplicate.Name.CopyAlternatives(source.Name) duplicate.Description.CopyAlternatives(source.Description) return duplicate @OperationsMethod def Find(self, name): """Find an item by name (case-insensitive). Args: name (str): The name to search for. Returns: ICmPossibility: The matching item, or None if not found. """ self._ValidateParam(name, "name") if not name or not name.strip(): return None target = normalize_match_key(name.strip(), casefold=True) wsHandle = self.project.project.DefaultAnalWs for item in self.GetAll(): item_name = ITsString(item.Name.get_String(wsHandle)).Text if normalize_match_key(item_name, casefold=True) == target: return item return None @OperationsMethod def Exists(self, name): """Check if an item exists by name. Args: name (str): The name to check for. Returns: bool: True if an item with that name exists. """ self._ValidateParam(name, "name") return self.Find(name) is not None @OperationsMethod def GetName(self, item_or_hvo, wsHandle=None): """Get the name of an item in specified writing system. Args: item_or_hvo: Either an ICmPossibility object or its HVO. wsHandle: Optional writing system handle. Defaults to analysis WS. Returns: str: The item name, or empty string if not set. """ self._ValidateParam(item_or_hvo, "item_or_hvo") item = self.__ResolveObject(item_or_hvo) wsHandle = self.__WSHandle(wsHandle) name_str = item.Name.get_String(wsHandle) if name_str: return ITsString(name_str).Text or "" return "" @OperationsMethod def SetName(self, item_or_hvo, name, wsHandle=None): """Set the name of an item in specified writing system. Args: item_or_hvo: Either an ICmPossibility object or its HVO. name (str): The new name. wsHandle: Optional writing system handle. Defaults to analysis WS. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If item_or_hvo or name is None. """ self._EnsureWriteEnabled() self._ValidateParam(item_or_hvo, "item_or_hvo") self._ValidateParam(name, "name") item = self.__ResolveObject(item_or_hvo) wsHandle = self.__WSHandle(wsHandle) mkstr = TsStringUtils.MakeString(name or "", wsHandle) item.Name.set_String(wsHandle, mkstr) @OperationsMethod def GetDescription(self, item_or_hvo, wsHandle=None): """Get the description of an item in specified writing system. Args: item_or_hvo: Either an ICmPossibility object or its HVO. wsHandle: Optional writing system handle. Defaults to analysis WS. Returns: str: The item description, or empty string if not set. """ self._ValidateParam(item_or_hvo, "item_or_hvo") item = self.__ResolveObject(item_or_hvo) wsHandle = self.__WSHandle(wsHandle) desc_str = item.Description.get_String(wsHandle) if desc_str: return ITsString(desc_str).Text or "" return "" @OperationsMethod def SetDescription(self, item_or_hvo, description, wsHandle=None): """Set the description of an item in specified writing system. Args: item_or_hvo: Either an ICmPossibility object or its HVO. description (str): The new description. wsHandle: Optional writing system handle. Defaults to analysis WS. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If item_or_hvo is None. """ self._EnsureWriteEnabled() self._ValidateParam(item_or_hvo, "item_or_hvo") item = self.__ResolveObject(item_or_hvo) wsHandle = self.__WSHandle(wsHandle) mkstr = TsStringUtils.MakeString(description or "", wsHandle) item.Description.set_String(wsHandle, mkstr) @OperationsMethod def GetGuid(self, item_or_hvo): """Get the GUID of an item. Args: item_or_hvo: Either an ICmPossibility object or its HVO. Returns: str: The item GUID. """ self._ValidateParam(item_or_hvo, "item_or_hvo") item = self.__ResolveObject(item_or_hvo) return str(item.Guid) @OperationsMethod def CompareTo(self, item1_or_hvo, item2_or_hvo): """Compare two items by name. Args: item1_or_hvo: First item or HVO. item2_or_hvo: Second item or HVO. Returns: int: -1 if item1 < item2, 0 if equal, 1 if item1 > item2 (by name) """ self._ValidateParam(item1_or_hvo, "item1_or_hvo") self._ValidateParam(item2_or_hvo, "item2_or_hvo") item1 = self.__ResolveObject(item1_or_hvo) item2 = self.__ResolveObject(item2_or_hvo) wsHandle = self.project.project.DefaultAnalWs name1 = ITsString(item1.Name.get_String(wsHandle)).Text or "" name2 = ITsString(item2.Name.get_String(wsHandle)).Text or "" if name1 < name2: return -1 elif name1 > name2: return 1 return 0 # ========== SYNC INTEGRATION METHODS ========== @OperationsMethod def GetSyncableProperties(self, item_or_hvo): """Get syncable properties for cross-project synchronization. Returns basic syncable properties common to all possibility items. Subclasses may override to add domain-specific properties. Args: item_or_hvo: Either an ICmPossibility object or its HVO. Returns: dict: Dictionary of syncable properties - Name (dict of WS -> text) - Description (dict of WS -> text) - Guid """ self._ValidateParam(item_or_hvo, "item_or_hvo") item = self.__ResolveObject(item_or_hvo) props = {} props["Guid"] = str(item.Guid) # Collect all writing system alternatives for Name name_alts = {} for ws in self.project.WritingSystems.GetAll(): wsHandle = ws.Handle name_str = item.Name.get_String(wsHandle) if name_str: name_alts[str(wsHandle)] = ITsString(name_str).Text or "" if name_alts: props["Name"] = name_alts # Collect all writing system alternatives for Description desc_alts = {} for ws in self.project.WritingSystems.GetAll(): wsHandle = ws.Handle desc_str = item.Description.get_String(wsHandle) if desc_str: desc_alts[str(wsHandle)] = ITsString(desc_str).Text or "" if desc_alts: props["Description"] = desc_alts return props