Source code for flexicon.code.Discourse.ConstChartCellTagOperations

#
#   ConstChartCellTagOperations.py
#
#   Class: ConstChartCellTagOperations
#          Per-cell tag (IConstChartTag) operations for constituent
#          charts in FieldWorks Language Explorer projects via SIL
#          Language and Culture Model (LCM) API.
#
#   Platform: Python.NET
#             FieldWorks Version 9+
#
#   Copyright 2026
#

from ..BaseOperations import BaseOperations, OperationsMethod, wrap_enumerable

from SIL.LCModel import (
    IConstChartTag,
    IConstChartTagFactory,
    IConstChartRow,
    ICmPossibility,
)

from ..FLExProject import (
    FP_ParameterError,
)


[docs] class ConstChartCellTagOperations(BaseOperations): """ Operations for managing per-cell chart tags (``IConstChartTag``) -- the annotation surface in a constituent chart. Each chart row owns a sequence of cell-parts; a ``IConstChartTag`` is one cell-part subtype. Each tag is positioned in a column (``ColumnRA``) and references a marker from the project-wide vocabulary (``TagRA``). The vocabulary itself is managed by ``ConstChartMarkerOperations``. Typical workflow:: row = list(chart.RowsOS)[0] col = list(template.ColumnsOS)[1] topic = project.ConstChartMarkers.Find("Topic") tag = project.ConstChartCellTags.Create(row, col, topic) assert project.ConstChartCellTags.GetMarker(tag) is topic Access via ``FLExProject.ConstChartCellTags``. """ def __init__(self, project): super().__init__(project) # --- Core CRUD Operations ------------------------------------------ @OperationsMethod def Create(self, row_or_hvo, column, marker, index=None): """ Add a new IConstChartTag cell-part to ``row.CellsOS``. Args: row_or_hvo: The ``IConstChartRow`` (or HVO) that will own the new cell-part. column: The ``ICmPossibility`` representing the column this tag occupies. Stored as ``ColumnRA``. marker: The ``ICmPossibility`` from the project's ChartMarkers vocabulary. Stored as ``TagRA``. index (int, optional): Position to insert at within ``row.CellsOS``. When None (default) the tag is appended at the end. Returns: IConstChartTag: The newly created cell-part. Raises: FP_ReadOnlyError: If the project is not writeEnabled. FP_NullParameterError: If any of row_or_hvo / column / marker is None. FP_ParameterError: If index is out of range. """ self._EnsureWriteEnabled() self._ValidateParam(row_or_hvo, "row_or_hvo") self._ValidateParam(column, "column") self._ValidateParam(marker, "marker") row = self.__ResolveRow(row_or_hvo) cell_count = row.CellsOS.Count if index is None: index = cell_count if index < 0 or index > cell_count: raise FP_ParameterError( f"index {index} out of range for CellsOS of size {cell_count}" ) factory = self.project.project.ServiceLocator.GetService( IConstChartTagFactory ) with self._TransactionCM("Create chart cell tag"): return factory.Create(row, index, column, marker) @OperationsMethod def Delete(self, tag_or_hvo): """ Delete a cell-tag from its owning row. Args: tag_or_hvo: ``IConstChartTag`` or its HVO. """ self._EnsureWriteEnabled() self._ValidateParam(tag_or_hvo, "tag_or_hvo") tag = self.__ResolveTag(tag_or_hvo) with self._TransactionCM("Delete chart cell tag"): tag.Delete() @OperationsMethod def Find(self, row_or_hvo, column): """ Find the first cell-tag in ``row`` positioned in ``column``. Args: row_or_hvo: ``IConstChartRow`` or HVO. column: ``ICmPossibility`` representing the column. Returns: IConstChartTag or None: First matching tag, or None when the row has no tag in that column. """ self._ValidateParam(row_or_hvo, "row_or_hvo") self._ValidateParam(column, "column") row = self.__ResolveRow(row_or_hvo) column_hvo = column.Hvo # CellsOS is declared over IConstituentChartCellPart, so its # elements fail isinstance(cell, IConstChartTag) even when they are # tags -- the old filter matched nothing. Discriminate on ClassName # and cast the match (issue #270). from ..lcm_casting import cast_to_concrete for cell in row.CellsOS: if cell.ClassName != "ConstChartTag": continue tag = cast_to_concrete(cell) if tag.ColumnRA is not None and tag.ColumnRA.Hvo == column_hvo: return tag return None @wrap_enumerable @OperationsMethod def GetAll(self, row_or_hvo): """ Iterate every IConstChartTag in ``row.CellsOS`` (other cell-part subtypes are filtered out). Args: row_or_hvo: ``IConstChartRow`` or HVO. Returns: list[IConstChartTag]: Tags in CellsOS order. """ self._ValidateParam(row_or_hvo, "row_or_hvo") row = self.__ResolveRow(row_or_hvo) # isinstance() over CellsOS matched nothing because the elements # arrive as the declared base IConstituentChartCellPart, so this # returned an empty list for every row. Filter on ClassName, then # cast so IConstChartTag surface is reachable (issue #270). return self._GetTypedElements( c for c in row.CellsOS if c.ClassName == "ConstChartTag" ) # --- Tag Properties ------------------------------------------------ @OperationsMethod def GetMarker(self, tag_or_hvo): """Return ``tag.TagRA`` -- the referenced marker (or None).""" self._ValidateParam(tag_or_hvo, "tag_or_hvo") tag = self.__ResolveTag(tag_or_hvo) return tag.TagRA @OperationsMethod def SetMarker(self, tag_or_hvo, marker): """Set ``tag.TagRA`` to a marker from the project vocabulary.""" self._EnsureWriteEnabled() self._ValidateParam(tag_or_hvo, "tag_or_hvo") self._ValidateParam(marker, "marker") tag = self.__ResolveTag(tag_or_hvo) with self._TransactionCM("Set chart cell tag marker"): tag.TagRA = marker @OperationsMethod def GetColumn(self, tag_or_hvo): """Return ``tag.ColumnRA`` -- the column the tag occupies.""" self._ValidateParam(tag_or_hvo, "tag_or_hvo") tag = self.__ResolveTag(tag_or_hvo) return tag.ColumnRA # --- Private Helpers ----------------------------------------------- def __ResolveRow(self, row_or_hvo): # Casts by ClassName BEFORE returning (issue #275, generalising # #269's fix): self.project.Object() returns a bare ICmObject, so # isinstance(obj, IConstChartRow) is False even for a genuine row. # Strict widening over the bare isinstance check. 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 an IConstChartRow" ) if getattr(row_or_hvo, "ClassName", None) == "ConstChartRow": try: return IConstChartRow(row_or_hvo) except Exception: pass return row_or_hvo def __ResolveTag(self, tag_or_hvo): # Same mechanism as __ResolveRow above (issue #275). if isinstance(tag_or_hvo, int): obj = self.project.Object(tag_or_hvo) if getattr(obj, "ClassName", None) == "ConstChartTag": try: return IConstChartTag(obj) except Exception: pass if isinstance(obj, IConstChartTag): return obj raise FP_ParameterError( "HVO does not refer to an IConstChartTag" ) if getattr(tag_or_hvo, "ClassName", None) == "ConstChartTag": try: return IConstChartTag(tag_or_hvo) except Exception: pass return tag_or_hvo