Source code for flexicon.code.Discourse.ConstChartClauseMarkerOperations

#
#   ConstChartClauseMarkerOperations.py
#
#   Class: ConstChartClauseMarkerOperations
#          Clause 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 (
    IConstChartClauseMarker,
    IConstChartClauseMarkerFactory,
    IConstChartRow,
    IConstChartWordGroup,
)

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


[docs] class ConstChartClauseMarkerOperations(BaseOperations): """ This class provides operations for managing clause markers in constituent charts for discourse analysis in FieldWorks projects. Clause markers identify clausal relationships and dependencies within the discourse structure. They link word groups and mark clause boundaries. This class should be accessed via FLExProject.ConstChartClauseMarkers property. Usage:: from flexicon import FLExProject project = FLExProject() project.OpenProject("my project", writeEnabled=True) # Get a row and word group chart = project.ConstCharts.Find("Genesis 1 Analysis") row = project.ConstChartRows.Find(chart, 0) wg = project.ConstChartWordGroups.Find(row, 0) # Create a clause marker marker = project.ConstChartClauseMarkers.Create(row, wg) # Get all markers in a row for marker in project.ConstChartClauseMarkers.GetAll(row): # Process clause marker pass project.CloseProject() """ def __init__(self, project): """ Initialize ConstChartClauseMarkerOperations with a FLExProject instance. Args: project: The FLExProject instance to operate on. """ super().__init__(project) # --- Core CRUD Operations --- @OperationsMethod def Create(self, row_or_hvo, word_group): """ Create a new clause marker in a chart row. Clause markers mark clausal boundaries and dependencies in the discourse analysis. They associate with word groups to identify clause structure. Args: row_or_hvo: Either an IConstChartRow object or its HVO word_group: IConstChartWordGroup object this marker refers to Returns: IConstChartClauseMarker: The newly created clause marker object Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If row_or_hvo or word_group is None FP_ParameterError: If word_group is invalid type Example: >>> row = project.ConstChartRows.Find(chart, 0) >>> wg = project.ConstChartWordGroups.Find(row, 0) >>> marker = project.ConstChartClauseMarkers.Create(row, wg) Notes: - Marker is appended to the row's CellsOS (issue #324) - Each marker identifies a clause boundary or relationship - Factory.Create() automatically adds marker to repository - Markers can have dependent clauses attached See Also: Delete, Find, GetWordGroup, AddDependentClause, InsertDependentClause, RemoveDependentClause """ self._EnsureWriteEnabled() self._ValidateParam(row_or_hvo, "row_or_hvo") self._ValidateParam(word_group, "word_group") row = self.__ResolveRow(row_or_hvo) # Validate word group if not isinstance(word_group, IConstChartWordGroup): raise FP_ParameterError("word_group must be an IConstChartWordGroup object") with self._TransactionCM("Create clause marker"): # Create the new clause marker using the factory factory = self.project.project.ServiceLocator.GetService(IConstChartClauseMarkerFactory) new_marker = factory.Create() # Clause markers are peer cell-parts in row.CellsOS (issue #324). # IConstChartRow has CellsOS only -- ClauseMarkersOS does not exist. row.CellsOS.Add(new_marker) # ColumnRA must be set when available (same ordering model as # ConstChartMovedTextOperations.Create, issue #290 / R5). if hasattr(new_marker, "ColumnRA") and hasattr(word_group, "ColumnRA"): if word_group.ColumnRA is not None: new_marker.ColumnRA = word_group.ColumnRA # WordGroupRA is not on IConstChartClauseMarker (see #232 sweep); # association is via chart structure / DependentClausesRS, not here. return new_marker @OperationsMethod def Delete(self, marker_or_hvo): """ Delete a clause marker from its row. Args: marker_or_hvo: Either an IConstChartClauseMarker 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.ConstChartClauseMarkers.Find(row, 0) >>> if marker: ... project.ConstChartClauseMarkers.Delete(marker) Warning: - This is a destructive operation - Dependent clause references will be lost - Cannot be undone Notes: - Deletion cascades to all owned relationships - Remaining markers maintain their order 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 clause marker"): # Delete the marker (LCM handles removal from repository) marker.Delete() @OperationsMethod def Find(self, row_or_hvo, index): """ Find a clause marker in a row by its index position. Args: row_or_hvo: Either an IConstChartRow object or its HVO index (int): Zero-based index of the marker to find Returns: IConstChartClauseMarker or None: The marker if found, None otherwise Raises: FP_NullParameterError: If row_or_hvo is None Example: >>> row = project.ConstChartRows.Find(chart, 0) >>> marker = project.ConstChartClauseMarkers.Find(row, 0) >>> if marker: ... wg = project.ConstChartClauseMarkers.GetWordGroup(marker) ... print(f"Marker references word group {wg.Hvo}") Notes: - Index is zero-based (0 = first marker) - Returns None if index out of range - More efficient than iterating GetAll() See Also: GetAll, Create """ self._ValidateParam(row_or_hvo, "row_or_hvo") row = self.__ResolveRow(row_or_hvo) markers = self.__ClauseMarkersInRow(row) if index < 0 or index >= len(markers): return None return markers[index] @wrap_enumerable @OperationsMethod def GetAll(self, row_or_hvo): """ Get all clause markers in a chart row. Args: row_or_hvo: Either an IConstChartRow object or its HVO Returns: list[IConstChartClauseMarker]: List of clause markers (empty list if none) Raises: FP_NullParameterError: If row_or_hvo is None Example: >>> row = project.ConstChartRows.Find(chart, 0) >>> markers = project.ConstChartClauseMarkers.GetAll(row) >>> for marker in markers: ... wg = project.ConstChartClauseMarkers.GetWordGroup(marker) ... print(f"Marker for word group {wg.Hvo}") Notes: - Returns empty list if row has no clause markers - Markers are in row order - Each marker identifies a clause boundary See Also: Find, Create """ self._ValidateParam(row_or_hvo, "row_or_hvo") row = self.__ResolveRow(row_or_hvo) return self.__ClauseMarkersInRow(row) # --- Marker Properties --- @OperationsMethod def GetWordGroup(self, marker_or_hvo): """ Get the word group associated with a clause marker. Args: marker_or_hvo: Either an IConstChartClauseMarker object or its HVO Returns: IConstChartWordGroup or None: The word group, or None if not set Raises: FP_NullParameterError: If marker_or_hvo is None Example: >>> marker = project.ConstChartClauseMarkers.Find(row, 0) >>> wg = project.ConstChartClauseMarkers.GetWordGroup(marker) >>> if wg: ... print(f"Marker references word group {wg.Hvo}") Notes: - ``IConstChartClauseMarker`` has no ``WordGroupRA`` (that member belongs to ``IConstChartMovedTextMarker`` only; issue #357). - Navigation uses ``ColumnRA``: the word group in the marker's owning row whose ``ColumnRA`` matches the marker's. - Returns None when ``ColumnRA`` is unset or no matching word group exists in the row. See Also: Create, GetDependentClauses """ self._ValidateParam(marker_or_hvo, "marker_or_hvo") marker = self.__ResolveObject(marker_or_hvo) return self.__WordGroupForClauseMarker(marker) @wrap_enumerable @OperationsMethod def GetDependentClauses(self, marker_or_hvo): """ Get the dependent clauses associated with a clause marker. Dependent clauses show the hierarchical structure of clausal relationships in the discourse. Args: marker_or_hvo: Either an IConstChartClauseMarker object or its HVO Returns: list: List of dependent clause markers (empty list if none) Raises: FP_NullParameterError: If marker_or_hvo is None Example: >>> marker = project.ConstChartClauseMarkers.Find(row, 0) >>> dependents = project.ConstChartClauseMarkers.GetDependentClauses(marker) >>> print(f"Marker has {len(dependents)} dependent clauses") Notes: - Returns empty list if no dependent clauses - Dependent clauses form a hierarchy - Used to mark embedded or subordinate clauses See Also: AddDependentClause, InsertDependentClause, RemoveDependentClause, GetWordGroup """ self._ValidateParam(marker_or_hvo, "marker_or_hvo") marker = self.__ResolveObject(marker_or_hvo) if hasattr(marker, "DependentClausesRS"): return list(marker.DependentClausesRS) return [] @OperationsMethod def AddDependentClause(self, marker_or_hvo, clause_marker): """ Add a dependent clause to a clause marker. Creates a hierarchical relationship showing that one clause is dependent on another in the discourse structure. Args: marker_or_hvo: Either an IConstChartClauseMarker object or its HVO clause_marker: IConstChartClauseMarker object to add as dependent Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If marker_or_hvo or clause_marker is None FP_ParameterError: If clause_marker is invalid type Example: >>> # Create main clause marker >>> main_marker = project.ConstChartClauseMarkers.Create(row, main_wg) >>> >>> # Create dependent clause marker >>> dep_marker = project.ConstChartClauseMarkers.Create(row, dep_wg) >>> >>> # Add as dependent >>> project.ConstChartClauseMarkers.AddDependentClause( ... main_marker, dep_marker) Notes: - Creates hierarchical clause structure - Dependent clause is subordinate to main clause - Multiple dependents can be added to one marker - Circular dependencies should be avoided See Also: GetDependentClauses, InsertDependentClause, RemoveDependentClause, Create """ self._EnsureWriteEnabled() self._ValidateParam(marker_or_hvo, "marker_or_hvo") self._ValidateParam(clause_marker, "clause_marker") if not isinstance(clause_marker, IConstChartClauseMarker): raise FP_ParameterError("clause_marker must be an IConstChartClauseMarker object") marker = self.__ResolveObject(marker_or_hvo) # Add to dependent clauses collection. The capability check and the # membership test stay OUTSIDE the transaction so an already-dependent # clause is a true no-op and does not open an empty undo task. if hasattr(marker, "DependentClausesRS"): if clause_marker not in marker.DependentClausesRS: with self._TransactionCM("Add dependent clause"): marker.DependentClausesRS.Add(clause_marker) @OperationsMethod def InsertDependentClause(self, marker_or_hvo, index, clause_marker): """ Insert a dependent clause marker at a given index in DependentClausesRS. Mirrors ``SegmentOperations.InsertAnalysis`` for reference-sequence ordering (issue #230 / #215 pattern). Args: marker_or_hvo: Either an IConstChartClauseMarker object or its HVO. index: 0-based index at which to insert (may equal ``Count`` to append). clause_marker: IConstChartClauseMarker to insert. Raises: FP_ReadOnlyError: If project is not opened with write enabled. FP_NullParameterError: If marker_or_hvo or clause_marker is None. FP_ParameterError: If index is out of range or clause_marker is invalid. Example: >>> deps = project.ConstChartClauseMarkers.GetDependentClauses(main) >>> project.ConstChartClauseMarkers.InsertDependentClause( ... main, 0, dep_marker) See Also: AddDependentClause, RemoveDependentClause, GetDependentClauses """ self._EnsureWriteEnabled() self._ValidateParam(marker_or_hvo, "marker_or_hvo") self._ValidateParam(clause_marker, "clause_marker") if not isinstance(clause_marker, IConstChartClauseMarker): raise FP_ParameterError( "clause_marker must be an IConstChartClauseMarker object" ) marker = self.__ResolveObject(marker_or_hvo) if not hasattr(marker, "DependentClausesRS"): raise FP_ParameterError( "Clause marker does not expose DependentClausesRS" ) count = marker.DependentClausesRS.Count if not isinstance(index, int) or index < 0 or index > count: raise FP_ParameterError( f"index must be between 0 and {count} (inclusive); got {index!r}" ) with self._TransactionCM("Insert dependent clause"): marker.DependentClausesRS.Insert(index, clause_marker) @OperationsMethod def RemoveDependentClause(self, marker_or_hvo, index): """ Remove the dependent clause marker at a given index from DependentClausesRS. Args: marker_or_hvo: Either an IConstChartClauseMarker object or its HVO. index: 0-based index within DependentClausesRS to remove. Raises: FP_ReadOnlyError: If project is not opened with write enabled. FP_NullParameterError: If marker_or_hvo is None. FP_ParameterError: If index is out of range. Example: >>> project.ConstChartClauseMarkers.RemoveDependentClause(main, 0) See Also: InsertDependentClause, AddDependentClause, GetDependentClauses """ self._EnsureWriteEnabled() self._ValidateParam(marker_or_hvo, "marker_or_hvo") marker = self.__ResolveObject(marker_or_hvo) if not hasattr(marker, "DependentClausesRS"): raise FP_ParameterError( "Clause marker does not expose DependentClausesRS" ) count = marker.DependentClausesRS.Count if not isinstance(index, int) or index < 0 or index >= count: raise FP_ParameterError( f"index must be between 0 and {count - 1} (inclusive); got {index!r}" ) with self._TransactionCM("Remove dependent clause"): marker.DependentClausesRS.RemoveAt(index) # --- Private Helper Methods --- def __ClauseMarkersInRow(self, row): """Return clause markers in ``row.CellsOS`` order (issue #324).""" return self._GetTypedElements( c for c in row.CellsOS if c.ClassName == "ConstChartClauseMarker" ) def __WordGroupForClauseMarker(self, marker): """ Resolve the word group for a clause marker via shared ``ColumnRA``. ``IConstChartClauseMarker`` does not expose ``WordGroupRA``; ``Create`` copies ``word_group.ColumnRA`` onto the new marker (issue #324 / #357). """ if not hasattr(marker, "ColumnRA"): return None column = marker.ColumnRA if column is None: return None row = marker.Owner if row is None or not hasattr(row, "CellsOS"): return None column_hvo = column.Hvo if hasattr(column, "Hvo") else None for cell in row.CellsOS: if getattr(cell, "ClassName", None) != "ConstChartWordGroup": continue cell_column = getattr(cell, "ColumnRA", None) if cell_column is None: continue if column_hvo is not None and hasattr(cell_column, "Hvo"): if cell_column.Hvo != column_hvo: continue elif cell_column != column: continue try: return IConstChartWordGroup(cell) except Exception: return cell return None def __ResolveObject(self, marker_or_hvo): """ Resolve HVO or object to IConstChartClauseMarker. Args: marker_or_hvo: Either an IConstChartClauseMarker object or an HVO (int) Returns: IConstChartClauseMarker: The resolved marker object Raises: FP_ParameterError: If HVO doesn't refer to a clause marker """ if isinstance(marker_or_hvo, int): obj = self.project.Object(marker_or_hvo) if getattr(obj, "ClassName", None) == "ConstChartClauseMarker": try: return IConstChartClauseMarker(obj) except Exception: pass if isinstance(obj, IConstChartClauseMarker): return obj raise FP_ParameterError("HVO does not refer to a clause marker") if getattr(marker_or_hvo, "ClassName", None) == "ConstChartClauseMarker": try: return IConstChartClauseMarker(marker_or_hvo) except Exception: pass return marker_or_hvo def __ResolveRow(self, row_or_hvo): """ Resolve HVO or object to IConstChartRow. Args: row_or_hvo: Either an IConstChartRow object or an HVO (int) Returns: IConstChartRow: The resolved row object Raises: FP_ParameterError: If HVO doesn't refer to a chart row """ if isinstance(row_or_hvo, int): obj = self.project.Object(row_or_hvo) if getattr(obj, "ClassName", None) == "ConstChartRow": try: return IConstChartRow(obj) except Exception: pass if isinstance(obj, IConstChartRow): return obj raise FP_ParameterError("HVO does not refer to a chart row") if getattr(row_or_hvo, "ClassName", None) == "ConstChartRow": try: return IConstChartRow(row_or_hvo) except Exception: pass return row_or_hvo # --- Reordering Support --- def _GetSequence(self, parent): """ Get the owning sequence for clause markers. Args: parent: The parent IConstChartRow object Returns: ILcmOwningSequence: The row's CellsOS sequence Notes: - Required for BaseOperations reordering methods - Clause markers are cell-parts in CellsOS alongside word groups, tags, and moved-text markers (issue #324) """ return parent.CellsOS