#
# 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