Source code for flexicon.code.Scripture.ScrSectionOperations

#
#   ScrSectionOperations.py
#
#   Class: ScrSectionOperations
#          Scripture section operations for 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 (
    IScrBook,
    IScrSection,
    IScrSectionFactory,
    IScrTxtPara,
    IStText,
    IStTextFactory,
)
from SIL.LCModel.Core.KernelInterfaces import ITsString
from SIL.LCModel.Core.Text import TsStringUtils

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


[docs] class ScrSectionOperations(BaseOperations): """ This class provides operations for managing Scripture sections in a FieldWorks project. Scripture sections are subdivisions of books, each with a heading and content paragraphs. Sections organize Scripture text into logical units. This class should be accessed via FLExProject.ScrSections property. Usage:: from flexlibs2 import FLExProject project = FLExProject() project.OpenProject("my project", writeEnabled=True) # Get a book genesis = project.ScrBooks.Find(1) # Create a section section = project.ScrSections.Create(genesis, "Creation", "In the beginning...") # Get all sections in a book sections = project.ScrSections.GetAll(genesis) # Get section heading and content heading = project.ScrSections.GetHeading(section) paras = project.ScrSections.GetContent(section) project.CloseProject() """ def __init__(self, project): """ Initialize ScrSectionOperations with a FLExProject instance. Args: project: The FLExProject instance to operate on. """ super().__init__(project) # --- Core CRUD Operations --- @OperationsMethod def Create(self, book_or_hvo, heading="", content=""): """ Create a new Scripture section with heading and/or content. Args: book_or_hvo: Either an IScrBook object or its HVO heading (str, optional): Section heading text content (str, optional): Initial paragraph content text Returns: IScrSection: The newly created section object Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If book_or_hvo is None FP_ParameterError: If book doesn't exist Example: >>> genesis = project.ScrBooks.Find(1) >>> section = project.ScrSections.Create( ... genesis, ... "The Creation", ... "In the beginning God created the heavens and the earth." ... ) >>> # Create section with heading only >>> section2 = project.ScrSections.Create(genesis, "The Fall") >>> # Create empty section >>> section3 = project.ScrSections.Create(genesis) Notes: - Section is appended to the book's sections - Section GUID is auto-generated - Heading and content can be empty strings - Content creates a single paragraph if provided See Also: Delete, Find, GetHeading, SetHeading """ self._EnsureWriteEnabled() self._ValidateParam(book_or_hvo, "book_or_hvo") # Resolve to book object book = self.__ResolveBook(book_or_hvo) # Create the new section using the factory factory = self.project.project.ServiceLocator.GetService(IScrSectionFactory) new_section = factory.CreateScrSection( book, book.SectionsOS.Count, # Append to end heading or "", content or "", True, # Is intro section = False (main content) ) return new_section @OperationsMethod def Delete(self, section_or_hvo): """ Delete a Scripture section from the FLEx project. Args: section_or_hvo: Either an IScrSection object or its HVO Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If section_or_hvo is None FP_ParameterError: If section doesn't exist Example: >>> section = project.ScrSections.Find(genesis, 0) >>> if section: ... project.ScrSections.Delete(section) Warning: - This is a destructive operation - All paragraphs and content will be deleted - Cannot be undone Notes: - Deletion cascades to all owned objects (paragraphs, etc.) See Also: Create """ self._EnsureWriteEnabled() self._ValidateParam(section_or_hvo, "section_or_hvo") # Resolve to section object section = self.__ResolveObject(section_or_hvo) # Delete the section (LCM handles removal from repository) section.Delete() @OperationsMethod def Find(self, book_or_hvo, index): """ Find a Scripture section by index within a book. Args: book_or_hvo: Either an IScrBook object or its HVO index (int): The zero-based index of the section in the book Returns: IScrSection or None: The section object if found, None otherwise Raises: FP_NullParameterError: If book_or_hvo or index is None Example: >>> genesis = project.ScrBooks.Find(1) >>> # Get first section >>> section = project.ScrSections.Find(genesis, 0) >>> if section: ... heading = project.ScrSections.GetHeading(section) ... print(f"Section: {heading}") >>> # Get third section >>> section3 = project.ScrSections.Find(genesis, 2) Notes: - Returns None if index out of range - Index is zero-based (0 = first section) - Use GetAll() to get all sections See Also: GetAll, Create """ self._ValidateParam(book_or_hvo, "book_or_hvo") self._ValidateParam(index, "index") # Resolve to book object book = self.__ResolveBook(book_or_hvo) # Check if index is valid if index < 0 or index >= book.SectionsOS.Count: return None return book.SectionsOS[index] @wrap_enumerable @OperationsMethod def GetAll(self, book_or_hvo): """ Get all Scripture sections in a book. Args: book_or_hvo: Either an IScrBook object or its HVO Returns: list: List of IScrSection objects (empty list if none) Raises: FP_NullParameterError: If book_or_hvo is None Example: >>> genesis = project.ScrBooks.Find(1) >>> sections = project.ScrSections.GetAll(genesis) >>> for i, section in enumerate(sections): ... heading = project.ScrSections.GetHeading(section) ... print(f"{i+1}. {heading}") 1. The Creation 2. The Fall 3. Cain and Abel Notes: - Returns empty list if book has no sections - Sections are in database order - Same as project.ScrBooks.GetSections(book) See Also: Find, Create """ self._ValidateParam(book_or_hvo, "book_or_hvo") # Resolve to book object book = self.__ResolveBook(book_or_hvo) return list(book.SectionsOS) # --- Section Properties --- @OperationsMethod def GetHeading(self, section_or_hvo, wsHandle=None): """ Get the heading of a Scripture section. Args: section_or_hvo: Either an IScrSection object or its HVO wsHandle: Optional writing system handle. Defaults to vernacular WS. Returns: str: The section heading (empty string if not set) Raises: FP_NullParameterError: If section_or_hvo is None Example: >>> section = project.ScrSections.Find(genesis, 0) >>> heading = project.ScrSections.GetHeading(section) >>> print(heading) The Creation >>> # Get in specific writing system >>> heading_fr = project.ScrSections.GetHeading(section, ... project.WSHandle('fr')) Notes: - Returns empty string if heading not set - Heading can be in multiple writing systems See Also: SetHeading, Create """ self._ValidateParam(section_or_hvo, "section_or_hvo") section = self.__ResolveObject(section_or_hvo) wsHandle = self.__WSHandle(wsHandle) if not section.HeadingOA: return "" # Get the heading text from the first paragraph if section.HeadingOA.ParagraphsOS.Count == 0: return "" para = section.HeadingOA.ParagraphsOS[0] # Note: Contents is ITsString, not IMultiUnicode text = para.Contents.Text if para.Contents else "" return text or "" @OperationsMethod def SetHeading(self, section_or_hvo, text, wsHandle=None): """ Set the heading of a Scripture section. Args: section_or_hvo: Either an IScrSection object or its HVO text (str): The new heading text wsHandle: Optional writing system handle. Defaults to vernacular WS. Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If section_or_hvo or text is None Example: >>> section = project.ScrSections.Find(genesis, 0) >>> project.ScrSections.SetHeading(section, "The Creation Story") >>> # Set in specific writing system >>> project.ScrSections.SetHeading(section, "La Création", ... project.WSHandle('fr')) Notes: - Creates heading StText if it doesn't exist - Creates heading paragraph if it doesn't exist - Empty text is allowed See Also: GetHeading, Create """ self._EnsureWriteEnabled() self._ValidateParam(section_or_hvo, "section_or_hvo") self._ValidateParam(text, "text") section = self.__ResolveObject(section_or_hvo) wsHandle = self.__WSHandle(wsHandle) with self._TransactionCM("Set section heading"): # Ensure HeadingOA exists if not section.HeadingOA: text_factory = self.project.project.ServiceLocator.GetService(IStTextFactory) section.HeadingOA = text_factory.Create() # Ensure heading has at least one paragraph if section.HeadingOA.ParagraphsOS.Count == 0: from SIL.LCModel import IStTxtParaFactory para_factory = self.project.project.ServiceLocator.GetService(IStTxtParaFactory) para = para_factory.Create() section.HeadingOA.ParagraphsOS.Add(para) # Set the heading text (Contents is ITsString, assign directly) para = section.HeadingOA.ParagraphsOS[0] mkstr = TsStringUtils.MakeString(text, wsHandle) para.Contents = mkstr @OperationsMethod def GetContent(self, section_or_hvo): """ Get all content paragraphs in a Scripture section. Args: section_or_hvo: Either an IScrSection object or its HVO Returns: list: List of IScrTxtPara objects (empty list if none) Raises: FP_NullParameterError: If section_or_hvo is None Example: >>> section = project.ScrSections.Find(genesis, 0) >>> paras = project.ScrSections.GetContent(section) >>> for para in paras: ... text = project.ScrTxtParas.GetText(para) ... print(f"Paragraph: {text}") Notes: - Returns empty list if section has no content - Paragraphs are in database order - Use ScrTxtParaOperations for paragraph-level operations See Also: ScrTxtParaOperations.GetAll """ self._ValidateParam(section_or_hvo, "section_or_hvo") section = self.__ResolveObject(section_or_hvo) if not section.ContentOA: return [] return list(section.ContentOA.ParagraphsOS) @OperationsMethod def MoveTo(self, section_or_hvo, target_book_or_hvo, index): """ Move a section to a different position or book. Args: section_or_hvo: Either an IScrSection object or its HVO target_book_or_hvo: Either an IScrBook object or its HVO (destination) index (int): The zero-based index position in the target book Raises: FP_ReadOnlyError: If project is not opened with write enabled FP_NullParameterError: If any parameter is None FP_ParameterError: If index is out of range Example: >>> section = project.ScrSections.Find(genesis, 2) >>> # Move to first position in same book >>> project.ScrSections.MoveTo(section, genesis, 0) >>> # Move to different book >>> exodus = project.ScrBooks.Find(2) >>> project.ScrSections.MoveTo(section, exodus, 0) Notes: - Can move within same book or to different book - Index is zero-based - Other sections shift position automatically See Also: Create, Delete """ self._EnsureWriteEnabled() self._ValidateParam(section_or_hvo, "section_or_hvo") self._ValidateParam(target_book_or_hvo, "target_book_or_hvo") self._ValidateParam(index, "index") section = self.__ResolveObject(section_or_hvo) target_book = self.__ResolveBook(target_book_or_hvo) if index < 0 or index > target_book.SectionsOS.Count: raise FP_ParameterError(f"Index {index} out of range (0-{target_book.SectionsOS.Count})") # Get current owner current_book = section.Owner # If moving within same book, adjust for removal if current_book == target_book: current_index = target_book.SectionsOS.IndexOf(section) if current_index < index: index -= 1 with self._TransactionCM("Move section"): # Remove from current location if current_book: current_book.SectionsOS.Remove(section) # Insert at new location target_book.SectionsOS.Insert(index, section) # --- Private Helper Methods --- def __ResolveObject(self, section_or_hvo): """ Resolve HVO or object to IScrSection. Args: section_or_hvo: Either an IScrSection object or an HVO (int) Returns: IScrSection: The resolved section object Raises: FP_ParameterError: If HVO doesn't refer to a Scripture section """ if isinstance(section_or_hvo, int): obj = self.project.Object(section_or_hvo) if not isinstance(obj, IScrSection): raise FP_ParameterError("HVO does not refer to a Scripture section") return obj return section_or_hvo def __ResolveBook(self, book_or_hvo): """ Resolve HVO or object to IScrBook. Args: book_or_hvo: Either an IScrBook object or an HVO (int) Returns: IScrBook: The resolved book object Raises: FP_ParameterError: If HVO doesn't refer to a Scripture book """ if isinstance(book_or_hvo, int): obj = self.project.Object(book_or_hvo) if not isinstance(obj, IScrBook): raise FP_ParameterError("HVO does not refer to a Scripture book") return obj return book_or_hvo def __WSHandle(self, wsHandle): """ Get writing system handle, defaulting to vernacular WS. 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)