Source code for flexicon.code.Discourse.ConstChartMovedTextOperations

#
#   ConstChartMovedTextOperations.py
#
#   Class: ConstChartMovedTextOperations
#          Moved text marker operations for constituent charts in FieldWorks
#          Language Explorer projects via SIL Language and Culture Model (LCM) API.
#
#   Platform: Python.NET
#             FieldWorks Version 9+
#
#   Copyright 2025
#

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

# Import FLEx LCM types
from SIL.LCModel import (
    IConstChartMovedTextMarker,
    IConstChartMovedTextMarkerFactory,
    IConstChartRow,
    IConstChartWordGroup,
    IDsConstChart,
)

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


[docs] class ConstChartMovedTextOperations(BaseOperations): """ This class provides operations for managing moved text markers in constituent charts for discourse analysis in FieldWorks projects. Moved text markers indicate that a word group represents text that has been moved from its canonical position (preposed or postposed) in the discourse structure. This class should be accessed via FLExProject.ConstChartMovedText property. Usage:: from flexicon import FLExProject project = FLExProject() project.OpenProject("my project", writeEnabled=True) # Get a word group chart = project.ConstCharts.Find("Genesis 1 Analysis") row = project.ConstChartRows.Find(chart, 0) wg = project.ConstChartWordGroups.Find(row, 0) # Mark as preposed text marker = project.ConstChartMovedText.Create(wg, preposed=True) # Check if preposed if project.ConstChartMovedText.IsPreposed(marker): print("Text is preposed") project.CloseProject() """ def __init__(self, project): """ Initialize ConstChartMovedTextOperations with a FLExProject instance. Args: project: The FLExProject instance to operate on. """ super().__init__(project) # --- Core CRUD Operations --- @OperationsMethod def Create(self, word_group_or_hvo, preposed=True): """ Create a moved text marker for a word group. Marks a word group as containing moved text (either preposed or postposed from its canonical position in the discourse). Args: word_group_or_hvo: Either an IConstChartWordGroup object or its HVO preposed (bool): True if text is preposed (moved earlier), False if postposed (moved later). Default is True. Returns: IConstChartMovedTextMarker: The newly created marker object Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If word_group_or_hvo is None Example: >>> # Mark word group as preposed >>> wg = project.ConstChartWordGroups.Find(row, 0) >>> marker = project.ConstChartMovedText.Create(wg, preposed=True) >>> print(project.ConstChartMovedText.IsPreposed(marker)) True >>> # Mark as postposed >>> marker = project.ConstChartMovedText.Create(wg, preposed=False) >>> print(project.ConstChartMovedText.IsPreposed(marker)) False Notes: - Preposed text appears earlier than its canonical position - Postposed text appears later than its canonical position - Marker is inserted into row.CellsOS (the row that owns the word group); WordGroupRA and ColumnRA are set before Preposed to avoid NullReferenceException (R5, #290 model). - Word group can reference at most one moved text marker. See Also: Delete, Find, IsPreposed, SetPreposed """ self._EnsureWriteEnabled() self._ValidateParam(word_group_or_hvo, "word_group_or_hvo") word_group = self.__ResolveWordGroup(word_group_or_hvo) row = self.__RowFromWordGroup(word_group) if row is None: raise FP_ParameterError("word_group has no owning row (Owner is None)") with self._TransactionCM("Create moved text marker"): # Create the new moved text marker using the factory. factory = self.project.project.ServiceLocator.GetService(IConstChartMovedTextMarkerFactory) new_marker = factory.Create() # Step 1: insert into row.CellsOS BEFORE setting any property # setters (avoids NullReferenceException -- R5, issue #290 model). row.CellsOS.Add(new_marker) # Step 2: set WordGroupRA and ColumnRA before Preposed. new_marker.WordGroupRA = word_group if hasattr(word_group, "ColumnRA") and word_group.ColumnRA is not None: new_marker.ColumnRA = word_group.ColumnRA # Step 3: set Preposed after the above. # VERIFY in T3: full path with live segments needed to confirm # NRE is resolved (analogical basis: #290 resolution). new_marker.Preposed = bool(preposed) return new_marker @OperationsMethod def Delete(self, marker_or_hvo): """ Delete a moved text marker. Args: marker_or_hvo: Either an IConstChartMovedTextMarker object or its HVO Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If marker_or_hvo is None Example: >>> marker = project.ConstChartMovedText.Find(wg) >>> if marker: ... project.ConstChartMovedText.Delete(marker) Warning: - This is a destructive operation - Cannot be undone - Word group will no longer be marked as moved See Also: Create, Find """ self._EnsureWriteEnabled() self._ValidateParam(marker_or_hvo, "marker_or_hvo") # Resolve to marker object marker = self.__ResolveObject(marker_or_hvo) with self._TransactionCM("Delete moved text marker"): # Delete the marker (LCM handles removal from repository) marker.Delete() @OperationsMethod def Find(self, word_group_or_hvo): """ Find the moved text marker for a word group. Args: word_group_or_hvo: Either an IConstChartWordGroup object or its HVO Returns: IConstChartMovedTextMarker or None: The marker if exists, None otherwise Raises: FP_NullParameterError: If word_group_or_hvo is None Example: >>> wg = project.ConstChartWordGroups.Find(row, 0) >>> marker = project.ConstChartMovedText.Find(wg) >>> if marker: ... print("Word group has moved text marker") ... if project.ConstChartMovedText.IsPreposed(marker): ... print("Text is preposed") Notes: - Returns None if no marker in the row references this word group. - Navigates from the word group's owning row through row.CellsOS and matches on WordGroupRA (R5/#290 model -- the marker is a peer cell in the row, not an owned child of the word group). See Also: Create, GetAll """ self._ValidateParam(word_group_or_hvo, "word_group_or_hvo") word_group = self.__ResolveWordGroup(word_group_or_hvo) row = self.__RowFromWordGroup(word_group) if row is None: return None for cell in row.CellsOS: if getattr(cell, "ClassName", None) != "ConstChartMovedTextMarker": if not isinstance(cell, IConstChartMovedTextMarker): continue try: marker = IConstChartMovedTextMarker(cell) except Exception: marker = cell wgra = getattr(marker, "WordGroupRA", None) if wgra is None: continue if wgra.Hvo == word_group.Hvo or str(wgra.Guid) == str(word_group.Guid): return marker return None @wrap_enumerable @OperationsMethod def GetAll(self, chart_or_hvo): """ Get all moved text markers in a constituent chart. Args: chart_or_hvo: Either an IDsConstChart object or its HVO Returns: list[IConstChartMovedTextMarker]: List of moved text markers (empty list if none) Raises: FP_NullParameterError: If chart_or_hvo is None Example: >>> chart = project.ConstCharts.Find("Genesis 1 Analysis") >>> markers = project.ConstChartMovedText.GetAll(chart) >>> preposed_count = sum(1 for m in markers ... if project.ConstChartMovedText.IsPreposed(m)) >>> print(f"Found {preposed_count} preposed markers") Notes: - Returns empty list if chart has no moved text markers. - Searches row.CellsOS directly for IConstChartMovedTextMarker instances (R5/#290 model -- markers are peer cells, not word group children). - Includes both preposed and postposed markers. See Also: Find, Create """ self._ValidateParam(chart_or_hvo, "chart_or_hvo") chart = self.__ResolveChart(chart_or_hvo) markers = [] # Iterate CellsOS directly; markers are peer cells in the row. for row in chart.RowsOS: for cell in row.CellsOS: if getattr(cell, "ClassName", None) != "ConstChartMovedTextMarker": if not isinstance(cell, IConstChartMovedTextMarker): continue try: markers.append(IConstChartMovedTextMarker(cell)) except Exception: markers.append(cell) return markers # --- Marker Properties --- @OperationsMethod def IsPreposed(self, marker_or_hvo): """ Check if a moved text marker indicates preposed text. Args: marker_or_hvo: Either an IConstChartMovedTextMarker object or its HVO Returns: bool: True if text is preposed, False if postposed Raises: FP_NullParameterError: If marker_or_hvo is None Example: >>> marker = project.ConstChartMovedText.Find(wg) >>> if marker: ... if project.ConstChartMovedText.IsPreposed(marker): ... print("Text moved earlier") ... else: ... print("Text moved later") Notes: - Preposed = text appears earlier than canonical position - Postposed = text appears later than canonical position - Returns boolean value of Preposed property See Also: SetPreposed, Create """ self._ValidateParam(marker_or_hvo, "marker_or_hvo") marker = self.__ResolveObject(marker_or_hvo) return marker.Preposed @OperationsMethod def SetPreposed(self, marker_or_hvo, value): """ Set whether moved text is preposed or postposed. Args: marker_or_hvo: Either an IConstChartMovedTextMarker object or its HVO value (bool): True for preposed, False for postposed Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If marker_or_hvo or value is None Example: >>> marker = project.ConstChartMovedText.Find(wg) >>> if marker: ... # Change from preposed to postposed ... project.ConstChartMovedText.SetPreposed(marker, False) Notes: - True = preposed (text moved earlier) - False = postposed (text moved later) - Changes are immediately persisted See Also: IsPreposed, Create """ self._EnsureWriteEnabled() self._ValidateParam(marker_or_hvo, "marker_or_hvo") self._ValidateParam(value, "value") marker = self.__ResolveObject(marker_or_hvo) with self._TransactionCM("Set moved-text preposed flag"): marker.Preposed = bool(value) @OperationsMethod def GetWordGroup(self, marker_or_hvo): """ Get the word group associated with a moved text marker. Args: marker_or_hvo: Either an IConstChartMovedTextMarker object or its HVO Returns: IConstChartWordGroup or None: The word group, or None if not found Raises: FP_NullParameterError: If marker_or_hvo is None Example: >>> marker = project.ConstChartMovedText.Find(wg) >>> if marker: ... word_group = project.ConstChartMovedText.GetWordGroup(marker) ... if word_group: ... print(f"Marker belongs to word group {word_group.Hvo}") Notes: - Returns None if WordGroupRA is not set. - Navigation is via WordGroupRA (R5/#290 model): the marker's owner is the row (row.CellsOS), not the word group. The word group is reached through the WordGroupRA reference attribute. See Also: Create, Find """ self._ValidateParam(marker_or_hvo, "marker_or_hvo") marker = self.__ResolveObject(marker_or_hvo) # Navigate via WordGroupRA: marker -> word group reference. return marker.WordGroupRA if hasattr(marker, "WordGroupRA") else None # --- Private Helper Methods --- def __RowFromWordGroup(self, word_group): """Cast word_group.Owner to IConstChartRow (Owner is ICmObject at runtime).""" row = word_group.Owner if row is None: return None if isinstance(row, IConstChartRow): return row try: return IConstChartRow(row) except Exception: return None def __ResolveObject(self, marker_or_hvo): """ Resolve HVO or object to IConstChartMovedTextMarker. Args: marker_or_hvo: Either an IConstChartMovedTextMarker object or an HVO (int) Returns: IConstChartMovedTextMarker: The resolved marker object Raises: FP_ParameterError: If HVO doesn't refer to a moved text marker """ if isinstance(marker_or_hvo, int): obj = self.project.Object(marker_or_hvo) if getattr(obj, "ClassName", None) == "ConstChartMovedTextMarker": try: return IConstChartMovedTextMarker(obj) except Exception: pass if isinstance(obj, IConstChartMovedTextMarker): return obj raise FP_ParameterError("HVO does not refer to a moved text marker") if getattr(marker_or_hvo, "ClassName", None) == "ConstChartMovedTextMarker": try: return IConstChartMovedTextMarker(marker_or_hvo) except Exception: pass return marker_or_hvo def __ResolveWordGroup(self, word_group_or_hvo): """ Resolve HVO or object to IConstChartWordGroup. Args: word_group_or_hvo: Either an IConstChartWordGroup object or an HVO (int) Returns: IConstChartWordGroup: The resolved word group object Raises: FP_ParameterError: If HVO doesn't refer to a word group """ if isinstance(word_group_or_hvo, int): obj = self.project.Object(word_group_or_hvo) if getattr(obj, "ClassName", None) == "ConstChartWordGroup": try: return IConstChartWordGroup(obj) except Exception: pass if isinstance(obj, IConstChartWordGroup): return obj raise FP_ParameterError("HVO does not refer to a word group") if getattr(word_group_or_hvo, "ClassName", None) == "ConstChartWordGroup": try: return IConstChartWordGroup(word_group_or_hvo) except Exception: pass return word_group_or_hvo def __ResolveChart(self, chart_or_hvo): """ Resolve HVO or object to IDsConstChart. Args: chart_or_hvo: Either an IDsConstChart object or an HVO (int) Returns: IDsConstChart: The resolved chart object Raises: FP_ParameterError: If HVO doesn't refer to a constituent chart """ if isinstance(chart_or_hvo, int): obj = self.project.Object(chart_or_hvo) if getattr(obj, "ClassName", None) == "DsConstChart": try: return IDsConstChart(obj) except Exception: pass if isinstance(obj, IDsConstChart): return obj raise FP_ParameterError("HVO does not refer to a constituent chart") if getattr(chart_or_hvo, "ClassName", None) == "DsConstChart": try: return IDsConstChart(chart_or_hvo) except Exception: pass return chart_or_hvo # --- Reordering Support --- def _GetSequence(self, parent): """ Not applicable for moved text markers. Moved text markers are cells in row.CellsOS (R5/#290 model). They do not expose a reorderable sub-sequence of their own. Raises: NotImplementedError: Moved text markers don't use a sub-sequence. """ raise NotImplementedError( "Moved text markers live in row.CellsOS as peer cells. " "Reordering methods are not applicable to this type." )