Source code for flexicon.code.Lexicon.ExampleOperations

#
#   ExampleOperations.py
#
#   Class: ExampleOperations
#          Example sentence operations for FieldWorks Language Explorer
#          projects via SIL Language and Culture Model (LCM) API.
#
#   Platform: Python.NET
#             FieldWorks Version 9+
#
#   Copyright 2025
#

import logging

logger = logging.getLogger(__name__)

# Import BaseOperations parent class
from ..BaseOperations import BaseOperations, OperationsMethod, wrap_enumerable

# Import FLEx LCM types
from SIL.LCModel import (
    ILexExampleSentence,
    ILexExampleSentenceFactory,
    ILexSense,
    ICmTranslation,
    ICmTranslationFactory,
)
from SIL.LCModel.Core.KernelInterfaces import ITsString
from SIL.LCModel.Core.Text import TsStringUtils

# Import flexlibs exceptions
from ..FLExProject import (
    FP_ParameterError,
)

# Import string utilities
from ..Shared.string_utils import normalize_text


[docs] class ExampleOperations(BaseOperations): """ This class provides operations for managing example sentences in a FieldWorks project. Example sentences illustrate the usage of lexical senses in context. Each example can have vernacular text, translations in multiple analysis languages, references to source texts, and associated media files. Usage:: from flexlibs2 import FLExProject, ExampleOperations project = FLExProject() project.OpenProject("my project", writeEnabled=True) exampleOps = ExampleOperations(project) # Get first entry and sense entry = project.LexiconAllEntries().__next__() sense = entry.SensesOS[0] if entry.SensesOS.Count > 0 else None # Get all examples for a sense for example in exampleOps.GetAll(sense): text = exampleOps.GetExample(example) trans = exampleOps.GetTranslation(example) print(f"Example: {text}") print(f"Translation: {trans}") # Create a new example example = exampleOps.Create(sense, "The cat sat on the mat.") exampleOps.SetTranslation(example, "Le chat s'est assis sur le tapis.") exampleOps.SetReference(example, "Example Corpus 1.23") project.CloseProject() """ def __init__(self, project): """ Initialize ExampleOperations with a FLExProject instance. Args: project: The FLExProject instance to operate on. """ super().__init__(project) def _GetSequence(self, parent): """ Specify which sequence to reorder for examples. For Example, we reorder sense.ExamplesOS """ return parent.ExamplesOS @wrap_enumerable @OperationsMethod def GetAll(self, sense_or_hvo=None): """ Get example sentences for a sense or all examples in the project. Args: sense_or_hvo: Optional ILexSense object or HVO. If provided, returns examples for that sense only. If None, returns ALL examples in the entire project. Yields: ILexExampleSentence: Each example sentence. Example: >>> exampleOps = ExampleOperations(project) >>> >>> # Get examples for a specific sense >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> for example in exampleOps.GetAll(sense): ... text = exampleOps.GetExample(example) ... print(f"Example: {text}") Example: The dog ran quickly. Example: Dogs run faster than cats. >>> >>> # Get ALL examples in entire project >>> for example in exampleOps.GetAll(): ... text = exampleOps.GetExample(example) ... print(f"Example: {text}") Notes: - When called with no argument, iterates all examples in project - When called with sense, returns only that sense's examples - Returns examples in the order they appear in FLEx - Returns empty generator if no examples exist - Examples are ordered collections (can be reordered) See Also: Create, Delete, Reorder """ if sense_or_hvo is None: # Iterate ALL examples in entire project for entry in self.project.lexDB.Entries: for sense in entry.AllSenses: # Includes subsenses for example in sense.ExamplesOS: yield example else: # Iterate examples for specific sense sense = self.__GetSenseObject(sense_or_hvo) for example in sense.ExamplesOS: yield example @OperationsMethod def Create(self, sense_or_hvo, example_text, wsHandle=None): """ Create a new example sentence for a lexical sense. Args: sense_or_hvo: The ILexSense object or HVO. example_text (str): The example sentence text. wsHandle: Optional writing system handle. Defaults to vernacular WS. Returns: ILexExampleSentence: The newly created example object. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If sense_or_hvo or example_text is None. FP_ParameterError: If example_text is empty. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> example = exampleOps.Create(sense, "The cat slept.") >>> print(exampleOps.GetExample(example)) The cat slept. >>> # Create with specific writing system >>> example = exampleOps.Create(sense, "Le chat dort.", ... project.WSHandle('fr')) Notes: - The example is added to the end of the sense's examples list - You can add translations after creation using SetTranslation() - References and media can also be added separately - Use vernacular writing system for the example text See Also: Delete, SetExample, SetTranslation """ self._EnsureWriteEnabled() self._ValidateParam(sense_or_hvo, "sense_or_hvo") self._ValidateParam(example_text, "example_text") self._ValidateStringNotEmpty(example_text, "example_text") sense = self.__GetSenseObject(sense_or_hvo) wsHandle = self.__WSHandleVern(wsHandle) # Create the new example using the factory factory = self.project.project.ServiceLocator.GetService(ILexExampleSentenceFactory) with self._TransactionCM("Create example"): # factory.Create() is the first mutation; kept inside the transaction # so the object is owned atomically with its Add() and property writes. example = factory.Create() # Add to sense's examples collection (must be done before setting properties) sense.ExamplesOS.Add(example) # Set example text mkstr = TsStringUtils.MakeString(example_text, wsHandle) example.Example.set_String(wsHandle, mkstr) return example @OperationsMethod def Delete(self, example_or_hvo): """ Delete an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO to delete. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if len(examples) > 0: ... exampleOps.Delete(examples[-1]) Warning: - Deletion is permanent and cannot be undone - All translations and associated data are also deleted - Media file links are removed (but files themselves remain) Notes: - Removes the example from the owning sense's collection - Automatically removes all translations See Also: Create, GetAll """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) # ExamplesOS lives on ILexSense (the concrete owner), not on the # base ICmObject that pythonnet returns from .Owner. The prior # hasattr(owner, "ExamplesOS") was always False on the base type # and Delete silently no-op'd. Route through _GetTypedOwner to # recover the concrete ILexSense first. Same shape as #160 / #98. # (issue #162) sense = self._GetTypedOwner(example) if sense is not None and hasattr(sense, "ExamplesOS"): sense.ExamplesOS.Remove(example) @OperationsMethod def Duplicate(self, item_or_hvo, insert_after=True, deep=False): """ Duplicate an example sentence, creating a new copy with a new GUID. Args: item_or_hvo: The ILexExampleSentence object or HVO to duplicate. insert_after (bool): If True (default), insert after the source example. If False, insert at end of parent's examples list. deep (bool): If True, also duplicate owned objects (translations). If False (default), only copy simple properties. Returns: ILexExampleSentence: The newly created duplicate example with a new GUID. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If item_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... # Shallow duplicate (no translations) ... dup = exampleOps.Duplicate(examples[0]) ... print(f"Original: {exampleOps.GetGuid(examples[0])}") ... print(f"Duplicate: {exampleOps.GetGuid(dup)}") Original: 12345678-1234-1234-1234-123456789abc Duplicate: 87654321-4321-4321-4321-cba987654321 ... ... # Deep duplicate (includes translations) ... deep_dup = exampleOps.Duplicate(examples[0], deep=True) ... trans = exampleOps.GetTranslation(deep_dup) ... print(f"Translation: {trans}") Notes: - Factory.Create() automatically generates a new GUID - insert_after=True preserves the original example's position/priority - Simple properties copied: Example (MultiString), Reference - Owned objects (deep=True): TranslationsOC - LiftResidue is not copied (import-specific data) - MediaFilesOS are not copied (would require file handling) See Also: Create, Delete, GetGuid """ self._EnsureWriteEnabled() self._ValidateParam(item_or_hvo, "item_or_hvo") # Get source example and parent. Cast owner to its concrete # ILexSense (the only LCM owner of ILexExample) so ExamplesOS is # reachable. Raw source.Owner is typed ICmObject; hasattr(..., # "ExamplesOS") returns False there, silently no-opping Duplicate. source = self.__GetExampleObject(item_or_hvo) parent = self._GetTypedOwner(source) if parent is None: raise FP_ParameterError("Example has no owning sense") # Create new example using factory (auto-generates new GUID) factory = self.project.project.ServiceLocator.GetService(ILexExampleSentenceFactory) with self._TransactionCM("Duplicate example"): duplicate = factory.Create() # Determine insertion position if insert_after: # Insert after source example source_index = parent.ExamplesOS.IndexOf(source) parent.ExamplesOS.Insert(source_index + 1, duplicate) else: # Insert at end parent.ExamplesOS.Add(duplicate) # Copy simple MultiString properties (AFTER adding to parent) duplicate.Example.CopyAlternatives(source.Example) # Skip copying Reference - it's complex and often empty # Reference is an ITsString pointing to the source text location # For duplicates, this reference doesn't make sense to copy # Handle owned objects if deep=True if deep: # Duplicate translations for translation in source.TranslationsOC: if translation.TypeRA is None: logger.warning( "Duplicate: skipping translation copy -- source TypeRA is null" ) continue trans_factory = self.project.project.ServiceLocator.GetService(ICmTranslationFactory) new_trans = trans_factory.Create(duplicate, translation.TypeRA) duplicate.TranslationsOC.Add(new_trans) # Copy translation properties new_trans.Translation.CopyAlternatives(translation.Translation) return duplicate # ========== SYNC INTEGRATION METHODS ========== @OperationsMethod def GetSyncableProperties(self, item): """ Get all syncable properties of an example sentence for comparison. Args: item: The ILexExampleSentence object. Returns: dict: Dictionary mapping property names to their values: - MultiString properties as dicts {ws: text} - Atomic properties as simple values - Does NOT include Owning Sequence (OS) properties (translations) """ props = {} # MultiString properties # Example - the example sentence in various writing systems example_dict = {} if hasattr(item, "Example"): for ws_def in self.project.WritingSystems.GetAll(): text = normalize_text( ITsString(item.Example.get_String(ws_def.Handle)).Text ) if text: example_dict[ws_def.Id] = text props["Example"] = example_dict # Reference - ILexExampleSentence.Reference is ITsString (single-string), # NOT IMultiString. Treating it as IMultiString with a WS-handle loop would # raise AttributeError at runtime because ITsString has no get_String(handle) # method. Use _ReadTsString instead. (P0 bug fix -- Category 8 same-name # field: ILexEtymology.Reference is IMultiString, ILexExampleSentence.Reference # is ITsString.) if hasattr(item, "Reference"): props["Reference"] = self._ReadTsString(item.Reference) else: props["Reference"] = "" # TranslationsOC - owned collection of ICmTranslation objects. # Each ICmTranslation has: # Translation: IMultiString (text per writing system) # TypeRA: ICmPossibility (e.g. "Free translation") -- serialized as GUID string # Serialize as a list of dicts (order-preserving, matching TranslationsOC order). translations_list = [] if hasattr(item, "TranslationsOC"): for trans in item.TranslationsOC: trans_text_dict = {} if hasattr(trans, "Translation"): for ws_def in self.project.WritingSystems.GetAll(): text = normalize_text( ITsString(trans.Translation.get_String(ws_def.Handle)).Text ) if text: trans_text_dict[ws_def.Id] = text type_guid = None if hasattr(trans, "TypeRA") and trans.TypeRA is not None: type_guid = str(trans.TypeRA.Guid) translations_list.append({ "Translation": trans_text_dict, "TypeRA": type_guid, }) props["TranslationsOC"] = translations_list return props @OperationsMethod def ApplySyncableProperties(self, item, props, ws_map=None, fill_gaps=False): """ Apply a syncable-properties dict onto an ILexExampleSentence item. Extends the base implementation to handle: - Reference: ITsString (not IMultiString) -- set via default analysis WS. - TranslationsOC: owned collection of ICmTranslation objects. Strategy is clear-and-rebuild (v1 choice: simpler than index-based reconciliation; acceptable because the diff layer identifies examples by GUID and replaces them as a unit). A comment marks where v2 could do smarter merging. Args: item: Target ILexExampleSentence (must already exist in target project). props: dict produced by GetSyncableProperties on a source example. ws_map: Optional source->target writing-system Id mapping. fill_gaps (bool): When True, only write fields whose current target value is empty/absent; passed through to BaseOperations. Reference/TranslationsOC are always applied regardless (domain has not ruled on them). """ import logging as _logging _log = _logging.getLogger(__name__) self._EnsureWriteEnabled() _special_fields = ("Reference", "TranslationsOC") remaining_props = {} special_props = {} for k, v in props.items(): if k in _special_fields: special_props[k] = v else: remaining_props[k] = v with self._TransactionCM("Apply example sync properties"): # Apply plain / multistring fields via base class. super().ApplySyncableProperties(item, remaining_props, ws_map=ws_map, fill_gaps=fill_gaps) # --- Reference (ITsString, single-string) --- if "Reference" in special_props: ref_text = special_props["Reference"] or "" if ref_text: ws_handle = self.project.project.DefaultAnalWs item.Reference = TsStringUtils.MakeString(ref_text, ws_handle) # --- TranslationsOC (owned collection -- clear-and-rebuild) --- # V1 choice: clear all existing ICmTranslation objects and recreate from # the serialized list. This is safe for cross-project sync where the target # example was just created. A future v2 could reconcile by TypeRA GUID to # preserve locally-authored translations not present in the source. if "TranslationsOC" in special_props: translations_data = special_props["TranslationsOC"] if not isinstance(translations_data, list): _log.warning( "[WARN] ApplySyncableProperties: TranslationsOC expected list, " "got %s -- skipping", type(translations_data).__name__ ) else: # Resolve target writing systems once. target_ws_by_id = { ws.Id: ws.Handle for ws in self.project.WritingSystems.GetAll() } # Build a GUID->object map for the translation type possibility list. # Translation types live in LangProject.TranslationTagsOA. type_guid_map = {} try: for tt in self.project.lp.TranslationTagsOA.PossibilitiesOS: type_guid_map[str(tt.Guid)] = tt except Exception as exc: _log.warning( "[WARN] ApplySyncableProperties: could not enumerate " "TranslationTagsOA: %s", exc ) # Clear existing translations on the target example. item.TranslationsOC.Clear() for trans_dict in translations_data: if not isinstance(trans_dict, dict): _log.warning( "[WARN] ApplySyncableProperties: TranslationsOC entry " "is not a dict -- skipped" ) continue # Resolve TypeRA by GUID before Create() -- factory requires a non-null possibility. type_guid_str = trans_dict.get("TypeRA") type_obj = None if type_guid_str: type_obj = type_guid_map.get(type_guid_str) if type_obj is None: _log.warning( "[WARN] ApplySyncableProperties: TranslationsOC " "TypeRA GUID %s not found in target project's " "TranslationTagsOA -- translation skipped", type_guid_str ) if type_obj is None: _log.warning( "[WARN] ApplySyncableProperties: TranslationsOC entry " "has no resolvable TypeRA -- translation skipped" ) continue # Create a new ICmTranslation owned by this example. new_trans = self.project.project.ServiceLocator.GetService( ICmTranslationFactory ).Create(item, type_obj) item.TranslationsOC.Add(new_trans) # Set Translation IMultiString per writing system. trans_text_dict = trans_dict.get("Translation", {}) for src_ws_id, text in trans_text_dict.items(): if not text: continue tgt_ws_id = ( ws_map.get(src_ws_id, src_ws_id) if ws_map else src_ws_id ) tgt_handle = target_ws_by_id.get(tgt_ws_id) if tgt_handle is None: continue new_trans.Translation.set_String( tgt_handle, TsStringUtils.MakeString(text, tgt_handle) ) @OperationsMethod def CompareTo(self, item1, item2, ops1=None, ops2=None): """ Compare two example sentences and return their differences. Args: item1: The first ILexExampleSentence object. item2: The second ILexExampleSentence object. ops1: Optional ExampleOperations instance for item1. ops2: Optional ExampleOperations instance for item2. Returns: tuple: (is_different, differences_dict) """ ops1 = ops1 or self ops2 = ops2 or self props1 = ops1.GetSyncableProperties(item1) props2 = ops2.GetSyncableProperties(item2) differences = {} all_keys = set(props1.keys()) | set(props2.keys()) for key in all_keys: val1 = props1.get(key) val2 = props2.get(key) if val1 != val2: differences[key] = (val1, val2) is_different = len(differences) > 0 return is_different, differences @OperationsMethod def Reorder(self, sense_or_hvo, example_list): """ Reorder the examples for a sense. Args: sense_or_hvo: The ILexSense object or HVO. example_list: List of ILexExampleSentence objects or HVOs in desired order. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If sense_or_hvo or example_list is None. FP_ParameterError: If example_list doesn't contain all examples or contains examples not belonging to this sense. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> # Reverse the order >>> exampleOps.Reorder(sense, reversed(examples)) Notes: - All examples must be provided in the new order - Examples not in the list will be removed - All examples must belong to the specified sense - This is useful for organizing examples by frequency or importance See Also: GetAll, Create """ self._EnsureWriteEnabled() self._ValidateParam(sense_or_hvo, "sense_or_hvo") self._ValidateParam(example_list, "example_list") sense = self.__GetSenseObject(sense_or_hvo) # Convert to list if it's an iterator example_list = list(example_list) # Resolve all to objects examples = [self.__GetExampleObject(ex) for ex in example_list] # Verify all examples belong to this sense current_examples = set(sense.ExamplesOS) new_examples = set(examples) if current_examples != new_examples: raise FP_ParameterError("Example list must contain exactly the same examples as the sense") with self._TransactionCM("Reorder examples"): # Clear and re-add in new order sense.ExamplesOS.Clear() for example in examples: sense.ExamplesOS.Add(example) @OperationsMethod def GetExample(self, example_or_hvo, wsHandle=None): """ Get the example text of an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. wsHandle: Optional writing system handle. Defaults to vernacular WS. Returns: str: The example text, or empty string if not set. Raises: FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... text = exampleOps.GetExample(examples[0]) ... print(text) The dog ran quickly. >>> # Get in specific writing system >>> text_fr = exampleOps.GetExample(examples[0], ... project.WSHandle('fr')) Notes: - Returns empty string if text not set in specified writing system - Use vernacular writing system for example sentences - Different writing systems can contain different text See Also: SetExample, Create """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) wsHandle = self.__WSHandleVern(wsHandle) text = ITsString(example.Example.get_String(wsHandle)).Text # _NormalizeMultiString turns "***" into "" but leaves None alone. # GetExample's docstring promises a str (empty when unset), so coerce # the None-on-unset case to "" as well -- callers do not expect to # have to .startswith()-guard the return value. return self._NormalizeMultiString(text) or "" @OperationsMethod def SetExample(self, example_or_hvo, text, wsHandle=None): """ Set the example text of an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. text (str): The new example text. wsHandle: Optional writing system handle. Defaults to vernacular WS. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If example_or_hvo or text is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... exampleOps.SetExample(examples[0], "The cat ran quickly.") ... print(exampleOps.GetExample(examples[0])) The cat ran quickly. Notes: - Empty string is allowed (clears the text) - Use different writing systems for different representations - Does not affect translations (update those separately) See Also: GetExample, Create, SetTranslation """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") self._ValidateParam(text, "text") example = self.__GetExampleObject(example_or_hvo) wsHandle = self.__WSHandleVern(wsHandle) mkstr = TsStringUtils.MakeString(text, wsHandle) example.Example.set_String(wsHandle, mkstr) @OperationsMethod def GetTranslations(self, example_or_hvo): """ Get all translation objects for an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. Returns: list: List of ICmTranslation objects (empty list if none). Raises: FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... translations = exampleOps.GetTranslations(examples[0]) ... for trans in translations: ... wsHandle = project.project.DefaultAnalWs ... text = ITsString(trans.Translation.get_String(wsHandle)).Text ... print(f"Translation: {text}") Translation: The dog ran quickly. Translation: El perro corrió rápidamente. Notes: - Returns ICmTranslation objects, not strings - To get text, use GetTranslation() for a specific WS - Translations are in analysis writing systems - Multiple translations can exist for different languages See Also: GetTranslation, SetTranslation, AddTranslation """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) return list(example.TranslationsOC) @OperationsMethod def GetTranslation(self, example_or_hvo, wsHandle=None): """ Get the translation text for a specific writing system. Args: example_or_hvo: The ILexExampleSentence object or HVO. wsHandle: Optional writing system handle. Defaults to analysis WS. Returns: str: The translation text, or empty string if not set. Raises: FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... trans = exampleOps.GetTranslation(examples[0]) ... print(trans) The dog ran quickly. >>> # Get Spanish translation >>> trans_es = exampleOps.GetTranslation(examples[0], ... project.WSHandle('es')) >>> print(trans_es) El perro corrió rápidamente. Notes: - Returns empty string if no translation in specified WS - Use analysis writing system for translations - Searches through all translation objects for matching WS - Returns the first match found See Also: SetTranslation, GetTranslations, GetExample """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) wsHandle = self.__WSHandle(wsHandle) # Search through translations for this writing system for translation in example.TranslationsOC: text = ITsString(translation.Translation.get_String(wsHandle)).Text if text: return text return "" @OperationsMethod def SetTranslation(self, example_or_hvo, text, wsHandle=None): """ Set the translation text for a specific writing system. Creates a translation object if one doesn't exist for the writing system. Args: example_or_hvo: The ILexExampleSentence object or HVO. text (str): The translation text. wsHandle: Optional writing system handle. Defaults to analysis WS. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If example_or_hvo or text is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> example = exampleOps.Create(sense, "El gato durmió.") >>> exampleOps.SetTranslation(example, "The cat slept.") >>> print(exampleOps.GetTranslation(example)) The cat slept. >>> # Set French translation >>> exampleOps.SetTranslation(example, "Le chat a dormi.", ... project.WSHandle('fr')) Notes: - Creates new translation object if needed - Updates existing translation if already present - Use analysis writing system for translations - Empty string is allowed (clears the translation) See Also: GetTranslation, AddTranslation, RemoveTranslation """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") self._ValidateParam(text, "text") example = self.__GetExampleObject(example_or_hvo) wsHandle = self.__WSHandle(wsHandle) # Find existing translation for this WS or create new one translation = None for trans in example.TranslationsOC: existing_text = ITsString(trans.Translation.get_String(wsHandle)).Text if existing_text: # This translation has content in our WS translation = trans break with self._TransactionCM("Set translation"): if translation is None: # Create new translation factory = self.project.project.ServiceLocator.GetService(ICmTranslationFactory) translation = factory.Create() example.TranslationsOC.Add(translation) # Set the translation text mkstr = TsStringUtils.MakeString(text, wsHandle) translation.Translation.set_String(wsHandle, mkstr) @OperationsMethod def AddTranslation(self, example_or_hvo, text, wsHandle=None): """ Add a new translation to an example sentence. This is an alias for SetTranslation() - both create or update translations. Args: example_or_hvo: The ILexExampleSentence object or HVO. text (str): The translation text. wsHandle: Optional writing system handle. Defaults to analysis WS. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If example_or_hvo or text is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> example = exampleOps.Create(sense, "Le chien a couru.") >>> exampleOps.AddTranslation(example, "The dog ran.") >>> exampleOps.AddTranslation(example, "El perro corrió.", ... project.WSHandle('es')) Notes: - Identical to SetTranslation() in functionality - Creates new translation if needed, updates if exists - Use for semantic clarity when adding first translation See Also: SetTranslation, GetTranslation, RemoveTranslation """ self._EnsureWriteEnabled() self.SetTranslation(example_or_hvo, text, wsHandle) @OperationsMethod def RemoveTranslation(self, example_or_hvo, wsHandle=None): """ Remove the translation for a specific writing system. Args: example_or_hvo: The ILexExampleSentence object or HVO. wsHandle: Optional writing system handle. Defaults to analysis WS. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... # Remove English translation ... exampleOps.RemoveTranslation(examples[0]) ... # Remove Spanish translation ... exampleOps.RemoveTranslation(examples[0], ... project.WSHandle('es')) Notes: - Sets translation text to empty string for the WS - Does not remove the translation object itself - If translation has content in other WSs, those are preserved - No error if translation doesn't exist See Also: SetTranslation, AddTranslation, GetTranslation """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) wsHandle = self.__WSHandle(wsHandle) # Find and clear translation for this WS for translation in example.TranslationsOC: text = ITsString(translation.Translation.get_String(wsHandle)).Text if text: translation.Translation.set_String(wsHandle, None) break @OperationsMethod def GetReference(self, example_or_hvo): """ Get the reference (source citation) for an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. Returns: str: The reference text, or empty string if not set. Raises: FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... ref = exampleOps.GetReference(examples[0]) ... print(f"Reference: {ref}") Reference: Genesis 1:1 Notes: - Reference can be a corpus citation, biblical reference, etc. - Format is free-form text - Commonly used formats: "Book Chapter:Verse", "Corpus ID" - Returns empty string if no reference set See Also: SetReference """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) if hasattr(example, "Reference") and example.Reference: ref = ITsString(example.Reference).Text return self._NormalizeMultiString(ref) return "" @OperationsMethod def SetReference(self, example_or_hvo, reference_text): """ Set the reference (source citation) for an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. reference_text (str): The reference text (e.g., "Genesis 1:1"). Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If example_or_hvo or reference_text is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> example = exampleOps.Create(sense, "In the beginning...") >>> exampleOps.SetReference(example, "Genesis 1:1") >>> print(exampleOps.GetReference(example)) Genesis 1:1 >>> # Corpus reference >>> exampleOps.SetReference(example, "Corpus A, Text 3, Line 12") Notes: - Reference format is free-form - Common formats: biblical citations, corpus IDs, page numbers - Empty string is allowed (clears the reference) - Use consistent format within a project See Also: GetReference """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") self._ValidateParam(reference_text, "reference_text") example = self.__GetExampleObject(example_or_hvo) wsHandle = self.project.project.DefaultAnalWs mkstr = TsStringUtils.MakeString(reference_text, wsHandle) example.Reference = mkstr @OperationsMethod def GetMediaFiles(self, example_or_hvo): """ Get all media files associated with an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. Returns: list: List of media file objects (empty list if none). Raises: FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... media = exampleOps.GetMediaFiles(examples[0]) ... print(f"Media count: {len(media)}") Media count: 2 Notes: - Returns media file objects from MediaFilesOS collection - Media files can be audio, video, or image files - File objects contain path and metadata information - Empty list if no media files attached See Also: GetMediaCount """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) if hasattr(example, "MediaFilesOS"): return list(example.MediaFilesOS) return [] @OperationsMethod def GetMediaCount(self, example_or_hvo): """ Get the count of media files associated with an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. Returns: int: The number of media files. Raises: FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... count = exampleOps.GetMediaCount(examples[0]) ... print(f"This example has {count} media files") This example has 2 media files Notes: - More efficient than len(GetMediaFiles()) for just counting - Returns 0 if no media files attached - Includes all media types (audio, video, images) See Also: GetMediaFiles """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) if hasattr(example, "MediaFilesOS"): return example.MediaFilesOS.Count return 0 @OperationsMethod def AddMediaFile(self, example_or_hvo, file_path, label=None): """ Add a media file (typically audio) to an example sentence. Copies the file to the project's LinkedFiles directory and creates a media reference linked to the example. Args: example_or_hvo: The ILexExampleSentence object or HVO. file_path (str): Path to the external media file to import. label (str, optional): Descriptive label for the media file. Returns: ICmFile: The created media file object with proper path. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If example_or_hvo or file_path is None. FP_ParameterError: If file_path is empty or file doesn't exist. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> example = exampleOps.Create(sense, "The dog barked loudly.") >>> # Add audio recording >>> media = exampleOps.AddMediaFile( ... example, ... "/path/to/audio.wav", ... label="Native speaker pronunciation" ... ) >>> print(f"Added media file") Added media file >>> # Verify the media was added >>> count = exampleOps.GetMediaCount(example) >>> print(f"Example now has {count} media file(s)") Example now has 1 media file(s) Notes: - File is copied to LinkedFiles/AudioVisual directory - Unique filename generated if collision occurs (audio_1.wav, etc.) - Creates ICmFile object with proper LinkedFiles path - Supported formats: WAV, MP3, OGG, WMA (audio), MP4, AVI (video) - Multiple media files can be added to one example - Label is stored in the default analysis writing system See Also: RemoveMediaFile, MoveMediaFile, GetMediaFiles, GetMediaCount """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") self._ValidateParam(file_path, "file_path") self._ValidateStringNotEmpty(file_path, "file_path") example = self.__GetExampleObject(example_or_hvo) with self._TransactionCM("Add media file"): # Use MediaOperations to properly copy file and create ICmFile # Copy file to project and get ICmFile reference media_file = self.project.Media.CopyToProject(file_path, internal_subdir="AudioVisual", label=label) # Add to example's media collection if hasattr(example, "MediaFilesOS"): example.MediaFilesOS.Add(media_file) else: raise FP_ParameterError("Example does not support media files") return media_file @OperationsMethod def RemoveMediaFile(self, example_or_hvo, media_or_hvo): """ Remove a media file from an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. media_or_hvo: The ICmFile object or HVO to remove. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If example_or_hvo or media_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... media_files = exampleOps.GetMediaFiles(examples[0]) ... if media_files: ... exampleOps.RemoveMediaFile(examples[0], media_files[0]) ... print("Media file removed") Media file removed Notes: - Removes the media file reference from the example - Does NOT delete the actual file from disk - The media file object is deleted from the database - Cannot be undone - If you need to delete the physical file, use project.Media methods Warning: - This operation cannot be undone - The media file reference is permanently removed - Other examples may still reference the same physical file See Also: AddMediaFile, MoveMediaFile, GetMediaFiles """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") self._ValidateParam(media_or_hvo, "media_or_hvo") example = self.__GetExampleObject(example_or_hvo) # Resolve media object if isinstance(media_or_hvo, int): media = self.project.Object(media_or_hvo) else: media = media_or_hvo # Remove from collection if hasattr(example, "MediaFilesOS"): example.MediaFilesOS.Remove(media) @OperationsMethod def MoveMediaFile(self, media, from_example_or_hvo, to_example_or_hvo): """ Move a media file from one example sentence to another example sentence. This is useful when reorganizing examples or moving audio recordings to the correct example sentence. Args: media: ICmFile object to move. from_example_or_hvo: Source ILexExampleSentence object or HVO. to_example_or_hvo: Destination ILexExampleSentence object or HVO. Returns: bool: True if media was moved, False if source and destination are same. Raises: FP_ReadOnlyError: If the project is not opened with write enabled. FP_NullParameterError: If any parameter is None. FP_ParameterError: If media not in source example's collection. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> # Get two examples from the sense >>> examples = list(exampleOps.GetAll(sense)) >>> if len(examples) >= 2: ... example1 = examples[0] ... example2 = examples[1] ... ... # Get media from first example ... media_files = exampleOps.GetMediaFiles(example1) ... if media_files: ... # Move the first audio file ... moved = exampleOps.MoveMediaFile(media_files[0], example1, example2) ... if moved: ... print("Media file moved successfully") ... ... # Verify the move ... count1 = exampleOps.GetMediaCount(example1) ... count2 = exampleOps.GetMediaCount(example2) ... print(f"Example 1 media: {count1}") ... print(f"Example 2 media: {count2}") Media file moved successfully Example 1 media: 0 Example 2 media: 1 >>> # Can also move between different senses >>> other_sense = entry.SensesOS[1] if entry.SensesOS.Count > 1 else None >>> if other_sense: ... other_examples = list(exampleOps.GetAll(other_sense)) ... if other_examples: ... other_example = other_examples[0] ... media_files = exampleOps.GetMediaFiles(example2) ... if media_files: ... exampleOps.MoveMediaFile(media_files[0], example2, other_example) Notes: - Media is removed from source MediaFilesOS and added to destination - File reference and description are preserved - The physical media file is NOT moved/copied - Cannot move to the same example (returns False) - Media object's GUID remains the same - No changes are made to the physical file location Warning: - Moving media between different sense's examples is allowed - Ensure the media is semantically appropriate for the target example - The media will be associated with the new example - Consider adding/updating labels to clarify context after moving See Also: AddMediaFile, RemoveMediaFile, GetMediaFiles, GetMediaCount """ self._EnsureWriteEnabled() self._ValidateParam(media, "media") self._ValidateParam(from_example_or_hvo, "from_example_or_hvo") self._ValidateParam(to_example_or_hvo, "to_example_or_hvo") from_example = self.__GetExampleObject(from_example_or_hvo) to_example = self.__GetExampleObject(to_example_or_hvo) # Can't move to same example if from_example == to_example: logger.warning("Source and destination are the same example") return False # Verify media is in source collection if not hasattr(from_example, "MediaFilesOS"): raise FP_ParameterError("Source example does not support media files") if media not in from_example.MediaFilesOS: raise FP_ParameterError("Media file not found in source example's media collection") # Verify destination supports media if not hasattr(to_example, "MediaFilesOS"): raise FP_ParameterError("Destination example does not support media files") with self._TransactionCM("Move media file"): # Move the media (remove from source, add to destination) from_example.MediaFilesOS.Remove(media) to_example.MediaFilesOS.Add(media) logger.info(f"Moved media from example {from_example.Guid} to example {to_example.Guid}") return True @OperationsMethod def GetOwningSense(self, example_or_hvo): """ Get the lexical sense that owns this example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. Returns: ILexSense: The owning sense object. Raises: FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... owner = exampleOps.GetOwningSense(examples[0]) ... gloss = project.LexiconGetSenseGloss(owner) ... print(f"Example belongs to sense: {gloss}") Example belongs to sense: run (verb) Notes: - Returns the ILexSense that contains this example - Useful for navigation and context - Examples always have exactly one owning sense See Also: GetAll, Create """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) return ILexSense(example.Owner) @OperationsMethod def GetGuid(self, example_or_hvo): """ Get the GUID of an example sentence. Args: example_or_hvo: The ILexExampleSentence object or HVO. Returns: System.Guid: The GUID of the example. Raises: FP_NullParameterError: If example_or_hvo is None. Example: >>> exampleOps = ExampleOperations(project) >>> entry = project.LexiconAllEntries().__next__() >>> sense = entry.SensesOS[0] >>> examples = list(exampleOps.GetAll(sense)) >>> if examples: ... guid = exampleOps.GetGuid(examples[0]) ... print(f"Example GUID: {guid}") Example GUID: 12345678-1234-1234-1234-123456789abc Notes: - GUIDs are globally unique identifiers - Persistent across project versions - Use for external references and tracking - Same GUID across different copies of the project See Also: GetOwningSense """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) return example.Guid # --- Additional Properties --- @OperationsMethod def GetLiteralTranslation(self, example_or_hvo, wsHandle=None): """ Get the literal translation of an example sentence. Args: example_or_hvo: Either an ILexExampleSentence object or its HVO wsHandle: Optional writing system handle. Defaults to analysis WS. Returns: str: The literal translation text Example: >>> sense = list(project.Senses.GetAll())[0] >>> examples = project.Examples.GetAll(sense) >>> if examples: ... lit_trans = project.Examples.GetLiteralTranslation(examples[0]) ... print(lit_trans) """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) wsHandle = self.__WSHandleAnalysis(wsHandle) return self._NormalizeMultiString(ITsString(example.LiteralTranslation.get_String(wsHandle)).Text) @OperationsMethod def SetLiteralTranslation(self, example_or_hvo, text, wsHandle=None): """ Set the literal translation of an example sentence. Args: example_or_hvo: Either an ILexExampleSentence object or its HVO text (str): The literal translation text wsHandle: Optional writing system handle. Defaults to analysis WS. Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If example_or_hvo or text is None Example: >>> sense = list(project.Senses.GetAll())[0] >>> examples = project.Examples.GetAll(sense) >>> if examples: ... project.Examples.SetLiteralTranslation(examples[0], "word-for-word translation") """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") self._ValidateParam(text, "text") example = self.__GetExampleObject(example_or_hvo) wsHandle = self.__WSHandleAnalysis(wsHandle) mkstr = TsStringUtils.MakeString(text, wsHandle) example.LiteralTranslation.set_String(wsHandle, mkstr) @OperationsMethod def GetDoNotPublishIn(self, example_or_hvo): """ Get the publications this example should not be published in. Args: example_or_hvo: Either an ILexExampleSentence object or its HVO Returns: list: List of publication names Example: >>> sense = list(project.Senses.GetAll())[0] >>> examples = project.Examples.GetAll(sense) >>> if examples: ... pubs = project.Examples.GetDoNotPublishIn(examples[0]) ... print(pubs) """ self._ValidateParam(example_or_hvo, "example_or_hvo") example = self.__GetExampleObject(example_or_hvo) result = [] for pub in example.DoNotPublishInRC: name = normalize_text(pub.Name.BestAnalysisAlternative.Text) if pub.Name else str(pub.Guid) result.append(name) return result @OperationsMethod def AddDoNotPublishIn(self, example_or_hvo, publication): """ Add a publication to exclude this example from. Args: example_or_hvo: Either an ILexExampleSentence object or its HVO publication: Publication name (str) or ICmPossibility object Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If example_or_hvo or publication is None FP_ParameterError: If publication name not found """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") self._ValidateParam(publication, "publication") example = self.__GetExampleObject(example_or_hvo) # Find publication object if string provided if isinstance(publication, str): pub_obj = self.project.Publications.Find(publication) if not pub_obj: raise FP_ParameterError(f"Publication '{publication}' not found") publication = pub_obj if publication not in example.DoNotPublishInRC: example.DoNotPublishInRC.Add(publication) @OperationsMethod def RemoveDoNotPublishIn(self, example_or_hvo, publication): """ Remove a publication from the exclude list for this example. Args: example_or_hvo: Either an ILexExampleSentence object or its HVO publication: Publication name (str) or ICmPossibility object Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If example_or_hvo or publication is None FP_ParameterError: If publication name not found """ self._EnsureWriteEnabled() self._ValidateParam(example_or_hvo, "example_or_hvo") self._ValidateParam(publication, "publication") example = self.__GetExampleObject(example_or_hvo) # Find publication object if string provided if isinstance(publication, str): pub_obj = self.project.Publications.Find(publication) if not pub_obj: raise FP_ParameterError(f"Publication '{publication}' not found") publication = pub_obj if publication in example.DoNotPublishInRC: example.DoNotPublishInRC.Remove(publication) # --- Private Helper Methods --- def __GetSenseObject(self, sense_or_hvo): """ Resolve HVO or object to ILexSense. Args: sense_or_hvo: Either an ILexSense object or an HVO (int). Returns: ILexSense: The resolved sense object. """ if isinstance(sense_or_hvo, int): return self.project.Object(sense_or_hvo) return sense_or_hvo def __GetExampleObject(self, example_or_hvo): """ Resolve HVO or object to ILexExampleSentence. Args: example_or_hvo: Either an ILexExampleSentence object or an HVO (int). Returns: ILexExampleSentence: The resolved example object. """ if isinstance(example_or_hvo, int): return self.project.Object(example_or_hvo) return example_or_hvo def __WSHandle(self, wsHandle): """ Get writing system handle, defaulting to analysis WS for translations. Args: wsHandle: Optional writing system handle. Returns: int: The writing system handle. """ if wsHandle is None: return self.project.project.DefaultAnalWs return self.project._FLExProject__WSHandle(wsHandle, self.project.project.DefaultAnalWs) def __WSHandleVern(self, wsHandle): """ Get writing system handle, defaulting to vernacular WS for example text. Args: wsHandle: Optional writing system handle. Returns: int: The writing system handle. """ if wsHandle is None: return self.project.project.DefaultVernWs return self.project._FLExProject__WSHandle(wsHandle, self.project.project.DefaultVernWs)