Source code for ooodev.write.table.write_table

from __future__ import annotations
from typing import Any, cast, overload, Sequence, TYPE_CHECKING, Tuple, TypeVar, Generic, Generator

try:
    # python 3.12+
    from typing import override  # noqa # type: ignore
except ImportError:
    from typing_extensions import override  # noqa # type: ignore

from com.sun.star.lang import IndexOutOfBoundsException
from ooo.dyn.table.cell_content_type import CellContentType

from ooodev.mock import mock_g
from ooodev.adapter.beans.property_change_implement import PropertyChangeImplement
from ooodev.adapter.beans.vetoable_change_implement import VetoableChangeImplement
from ooodev.adapter.text.text_table_comp import TextTableComp
from ooodev.format.inner.style_partial import StylePartial
from ooodev.loader import lo as mLo
from ooodev.loader.inst.lo_inst import LoInst
from ooodev.utils.data_type.range_obj import RangeObj
from ooodev.utils.data_type.rng.range_converter import RangeConverter
from ooodev.utils.partial.lo_inst_props_partial import LoInstPropsPartial
from ooodev.utils.partial.qi_partial import QiPartial
from ooodev.write.partial.write_doc_prop_partial import WriteDocPropPartial
from ooodev.write.table.partial.write_table_prop_partial import WriteTablePropPartial
from ooodev.write.table.table_column_separators import TableColumnSeparators
from ooodev.write.table.write_table_cell import WriteTableCell
from ooodev.write.table.write_table_cell_range import WriteTableCellRange
from ooodev.write.table.write_text_table_cursor import WriteTextTableCursor
from ooodev.events.partial.events_partial import EventsPartial
from ooodev.write.table.partial.write_table_properties_partial import WriteTablePropertiesPartial

if TYPE_CHECKING:
    from com.sun.star.table import CellAddress
    from com.sun.star.table import CellRangeAddress
    from com.sun.star.table import XCell
    from com.sun.star.text import XTextTable
    from ooodev.utils.data_type.cell_obj import CellObj
    from ooodev.proto.component_proto import ComponentT
    from ooodev.utils.data_type.range_values import RangeValues
    from ooodev.utils.data_type.cell_values import CellValues
    from ooodev.write.table.write_table_rows import WriteTableRows
    from ooodev.write.table.write_table_columns import WriteTableColumns
    from ooodev.write.style.direct.table.table_styler import TableStyler

T = TypeVar("T", bound="ComponentT")


[docs]class WriteTable( Generic[T], WriteTablePropPartial, EventsPartial, LoInstPropsPartial, WriteDocPropPartial, TextTableComp, WriteTablePropertiesPartial, QiPartial, StylePartial, PropertyChangeImplement, VetoableChangeImplement, ): """Represents writer text content.""" # this class can be used to wrap the table created by # ooodev.office.write.Write.add_table() method. # https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1text_1_1TextTable.html
[docs] def __init__(self, owner: T, component: XTextTable, lo_inst: LoInst | None = None) -> None: """ Constructor Args: owner (T): Owner of this component. component (XTextTable): UNO object that supports ``com.sun.star.text.TextContent`` service. lo_inst (LoInst, optional): Lo instance. Defaults to ``None``. """ if lo_inst is None: lo_inst = mLo.Lo.current_lo self._owner = owner WriteTablePropPartial.__init__(self, obj=self) LoInstPropsPartial.__init__(self, lo_inst=lo_inst) EventsPartial.__init__(self) if not isinstance(owner, WriteDocPropPartial): raise TypeError("WriteDocPropPartial is not inherited by owner.") WriteDocPropPartial.__init__(self, obj=owner.write_doc) # type: ignore TextTableComp.__init__(self, component) # type: ignore WriteTablePropertiesPartial.__init__(self, component=component) # type: ignore QiPartial.__init__(self, component=component, lo_inst=self.lo_inst) # type: ignore StylePartial.__init__(self, component=component) # pylint: disable=no-member generic_args = self._ComponentBase__get_generic_args() # type: ignore PropertyChangeImplement.__init__(self, component=component, trigger_args=generic_args) # type: ignore VetoableChangeImplement.__init__(self, component=component, trigger_args=generic_args) # type: ignore self._cols = None self._rows = None self._range_converter = None self._style_direct = None
[docs] def __getitem__(self, key: Any) -> WriteTableCell: """ Gets the cell as WriteTableCell. Args: key (Any): Key. can be a Tuple of (col, row) or a string such as "A1" or a CellObj. Returns: WriteTableCell: Table Cell Object. Example: .. code-block:: python >>> table = doc.tables[0] >>> cell = table["D3"] >>> print(cell, cell.value) WriteTableCell(cell_name=C4) Sean Connery See Also: - :meth:`get_cell` """ return self.get_cell(key)
def __iter__(self) -> Generator[WriteTableCell, None, None]: """Iterates through the cells of the table.""" for cell in self.get_cell_names(): yield self.get_cell_by_name(cell) # region TextTablePartial overrides
[docs] @override def get_columns(self) -> WriteTableColumns: # type: ignore """ Gets the columns of the table. """ return self.columns
[docs] @override def get_rows(self) -> WriteTableRows: # type: ignore """ Gets the rows of this table. Returns: WriteTableRows: Table Rows """ return self.rows
# endregion TextTablePartial overrides # region get_cell() @overload def get_cell(self, cell_obj: CellObj) -> WriteTableCell: """ Gets the cell as WriteTableCell. Args: cell_obj (CellObj): Cell Object. Returns: WriteTableCell: Cell Object. """ ... @overload def get_cell(self, values: Tuple[int, int]) -> WriteTableCell: """ Gets the cell as WriteTableCell. Args: val (Tuple[int, int]): Cell values. Tuple of (col, row). Values are Zero Based. Returns: WriteTableCell: Cell Object. """ ... @overload def get_cell(self, col: int, row: int) -> WriteTableCell: """ Gets the cell as WriteTableCell from column and row. Args: col (int): Column. Zero Based column index. row (int): Row. Zero Based row index. Returns: WriteTableCell: Cell Object. """ ... @overload def get_cell(self, addr: CellAddress) -> WriteTableCell: """ Gets the cell as WriteTableCell from a cell address. Args: addr (CellAddress): Cell Address. Returns: WriteTableCell: Cell Object. """ ... @overload def get_cell(self, cell: XCell) -> WriteTableCell: """ Gets the cell as WriteTableCell from a cell. Args: cell (XCell): Cell. Returns: WriteTableCell: Cell Object. """ ... @overload def get_cell(self, val: CellValues) -> WriteTableCell: """ Gets the cell as WriteTableCell from CellValues. Args: val (CellValues): Cell values. Returns: WriteTableCell: Cell Object. Hint: - ``CellValues`` can be imported from ``ooodev.utils.data_type.cell_values`` """ ... @overload def get_cell(self, name: str) -> WriteTableCell: """ Gets the cell as WriteTableCell from a cell name. Args: name (str): Cell name such as as ``A23`` or ``Sheet1.A23`` Returns: WriteTableCell: Cell Object. """ ...
[docs] def get_cell(self, *args, **kwargs) -> WriteTableCell: """Returns a single cell within the range.""" cell_obj = self.write_table.range_converter.get_cell_obj(*args, **kwargs) col_index = cell_obj.col_obj.index row_index = cell_obj.row - 1 return self.get_cell_by_position(col=col_index, row=row_index)
# endregion get_cell() # region get Table Cell Range @overload def get_cell_range(self, cell_obj: CellObj) -> WriteTableCellRange: """ Gets a range Object representing a range. Args: cell_obj (CellObj): Cell Object. Returns: WriteTableCellRange: Range object. """ ... @overload def get_cell_range(self, range_obj: RangeObj) -> WriteTableCellRange: """ Gets a range object. Returns the same object. Args: range_obj (RangeObj): Range Object Returns: WriteTableCellRange: Range object. """ ... @overload def get_cell_range( self, col_start: int, row_start: int, col_end: int, row_end: int, sheet_idx: int = ... ) -> WriteTableCellRange: """ Gets a range Object representing a range. Args: col_start (int): Zero-based start column index. row_start (int): Zero-based start row index. col_end (int): Zero-based end column index. row_end (int): Zero-based end row index. sheet_idx (int, optional): Zero-based sheet index that this range value belongs to. Default is -1. Returns: WriteTableCellRange: Range object. """ ... @overload def get_cell_range(self, addr: CellRangeAddress) -> WriteTableCellRange: """ Gets a range Object representing a range from a cell range address. Args: addr (CellRangeAddress): Cell Range Address. Returns: WriteTableCellRange: Range object. """ ... @overload def get_cell_range(self, rng: RangeValues) -> WriteTableCellRange: """ Gets a range Object representing a range from a cell range address. Args: rng (RangeValues): Cell Range Values. Returns: WriteTableCellRange: Range object. Hint: - ``RangeValues`` can be imported from ``ooodev.utils.data_type.range_values`` """ ... @overload def get_cell_range(self, rng_name: str) -> WriteTableCellRange: """ Gets a range Object representing a range. Args: rng_name (str): Range as string such as ``Sheet1.A1:C125`` or ``A1:C125`` Returns: WriteTableCellRange: Range object. """ ...
[docs] def get_cell_range(self, *args, **kwargs) -> WriteTableCellRange: """ Gets a range Object representing a range. Args: rng_name (str): Range as string such as ``Sheet1.A1:C125`` or ``A1:C125`` Returns: WriteTableCellRange: Range object. """ rng = self.range_converter.get_range_obj(*args, **kwargs) return self.get_cell_range_by_name(str(rng))
# endregion get Table Cell Range # region TextTablePartial Overrides
[docs] @override def create_cursor_by_cell_name(self, name: str) -> WriteTextTableCursor: # type: ignore """ Creates a text table cursor and returns the XTextTableCursor interface. Initially the cursor is positioned in the cell with the specified name. """ comp = self.component.createCursorByCellName(name) return WriteTextTableCursor(owner=self, cursor=comp)
[docs] @override def get_cell_by_name(self, name: str) -> WriteTableCell: # type: ignore """ Returns the cell with the specified name. The cell in the 4th column and third row has the name ``D3``. Args: name (str): The name of the cell. Returns: WriteTableCell: The cell with the specified name. Note: In cells that are split, the naming convention is more complex. In this case the name is a concatenation of the former cell name (i.e. ``D3``) and the number of the new column and row index inside of the original table cell separated by dots. This is done recursively. For example, if the cell ``D3`` is horizontally split, it now contains the cells ``D3.1.1`` and ``D3.1.2``. """ cell_obj = self.range_converter.get_cell_obj_from_str(name) return WriteTableCell( owner=self, component=self.component.getCellByName(name), cell_obj=cell_obj, ) # type: ignore
# endregion TextTablePartial Overrides # region CellRangePartial Overrides
[docs] @override def get_cell_by_position(self, col: int, row: int) -> WriteTableCell: # type: ignore """ Returns a single cell within the range. Raises: com.sun.star.lang.IndexOutOfBoundsException: ``IndexOutOfBoundsException`` """ try: cell_obj = self.range_converter.get_cell_obj(values=(col, row)) return WriteTableCell( owner=self, component=self.component.getCellByPosition(col, row), cell_obj=cell_obj, ) # type: ignore except IndexOutOfBoundsException as e: raise IndexError(f"Index out of range. column={col}, row={row}") from e
[docs] @override def get_cell_range_by_name(self, rng: str) -> WriteTableCellRange: # type: ignore """ Returns a sub-range of cells within the range. The sub-range is specified by its name. The format of the range name is dependent of the context of the table. In spreadsheets valid names may be ``A1:C5`` or ``$B$2`` or even defined names for cell ranges such as ``MySpecialCell``. """ range_obj = self.range_converter.rng_from_str(rng) return WriteTableCellRange( owner=self, component=self.component.getCellRangeByName(rng), range_obj=self.range_converter.get_offset_range_obj(range_obj), )
[docs] @override def get_cell_range_by_position(self, left: int, top: int, right: int, bottom: int) -> WriteTableCellRange: # type: ignore """ Returns a sub-range of cells within the range. Raises: com.sun.star.lang.IndexOutOfBoundsException: ``IndexOutOfBoundsException`` """ try: range_obj = self.range_converter.rng_from_position( col_start=left, row_start=top, col_end=right, row_end=bottom, ) return WriteTableCellRange( owner=self, component=self.component.getCellRangeByPosition(left, top, right, bottom), range_obj=self.range_converter.get_offset_range_obj(range_obj), ) except IndexOutOfBoundsException as e: raise IndexError(f"Index out of range: left:{left}, top:{top}, right:{right}, bottom:{bottom}") from e
# endregion CellRangePartial Overrides # region Ensure Column/Row Count # region ensure_colum_row() @overload def ensure_colum_row(self, cell_obj: CellObj) -> None: """ Ensures that the table has at least the specified number of columns and rows. Args: cell_obj (CellObj): Cell Object. Returns: None: None """ ... @overload def ensure_colum_row(self, col: int, row: int) -> None: """ Ensures that the table has at least the specified number of columns and rows. Args: col (int): Column. Zero Based column index. row (int): Row. Zero Based row index. Returns: None: None """ ... @overload def ensure_colum_row(self, addr: CellAddress) -> None: """ Ensures that the table has at least the specified number of columns and rows. Args: addr (CellAddress): Cell Address. Returns: None: None """ ... @overload def ensure_colum_row(self, cell: XCell) -> None: """ Ensures that the table has at least the specified number of columns and rows. Args: cell (XCell): Cell. Returns: None: None """ ... @overload def ensure_colum_row(self, val: CellValues) -> None: """ Ensures that the table has at least the specified number of columns and rows. Args: val (CellValues): Cell values. Returns: None: None Hint: - ``CellValues`` can be imported from ``ooodev.utils.data_type.cell_values`` """ ... @overload def ensure_colum_row(self, name: str) -> None: """ Ensures that the table has at least the specified number of columns and rows. Args: name (str): Cell name such as as ``A23`` or ``Sheet1.A23`` Returns: None: None """ ...
[docs] def ensure_colum_row(self, *args, **kwargs) -> None: """ Ensures that the table has at least the specified number of columns and rows. Returns: None: None """ cell_obj = self.write_table.range_converter.get_cell_obj(*args, **kwargs) self.ensure_column_count(cell_obj.col_obj.index + 1) self.ensure_row_count(cell_obj.row)
# endregion ensure_colum_row() # endregion Ensure Column/Row Count
[docs] def ensure_column_count(self, count: int) -> int: """ Ensures that the table has at least the specified number of columns. Args: count (int): Number of columns. Returns: int: Number of columns. """ current_count = len(self.columns) if current_count >= count: return current_count self.columns.append_columns(count - current_count) return len(self.columns)
[docs] def ensure_row_count(self, count: int) -> int: """ Ensures that the table has at least the specified number of rows. Args: count (int): Number of rows. Returns: int: Number of rows. """ current_count = len(self.rows) if current_count >= count: return current_count self.rows.append_rows(count - current_count) return len(self.rows)
[docs] def get_table_range(self) -> RangeObj: """ Gets a range object from a range string. Args: rng (str): Range string. Returns: RangeObj: Range Object. """ cursor = self.create_cursor_by_cell_name("A1") cursor.goto_start() cursor.goto_end(True) return cursor.get_range_obj()
[docs] def set_data_array_cell( self, data: Sequence[Sequence[Any]], cell: str | CellObj, ensure_rows: bool = True, ensure_cols: bool = False ) -> None: """ Sets the data array to the table starting from the specified cell. Args: data (Sequence[Sequence[Any]]): 2D Data array representing the table. cell (str | CellObj): Cell name or Cell Object. ensure_rows (bool, optional): Specifies if rows should be added if there are not enough in the table to allow the data to be added. Defaults to ``True``. ensure_cols (bool, optional): Specifies if columns should be added if there are not enough in the table to allow the data to be added. Defaults to ``False``. Returns: None: Example: In this example there are ``4`` columns and ``25`` rows in the table. The data array is ``4x25``. After adding the data array to the table, the table will have ``5`` columns and ``26`` rows. This is because we started the data array from the cell ``B2``. By setting ``ensure_rows`` to ``True`` and ``ensure_cols`` to ``True``, the table will have enough rows and columns to accommodate the data array. .. code-block:: python >>> cursor = doc.get_cursor() # add a table with a single cell empty cell >>> _ = cursor.add_table(table_data=[[[]]], first_row_header=False) >>> tbl.set_data_array_cell(tbl_data, cell="B2", ensure_cols=True, ensure_rows=True) >>> print(len(tbl.rows)) 26 >>> print(len(tbl.columns)) 5 >>> cell = tbl["E24"] >>> print(cell.value) "Marc Forster" """ if isinstance(cell, str): cell = self.range_converter.get_cell_obj_from_str(cell) # ensure there are enough rows and columns data_rng = self.range_converter.get_range_from_2d(data) data_rng_addr = data_rng.get_cell_range_address() cell_addr = cell.get_cell_address() if ensure_rows: min_row = cell_addr.Row + data_rng_addr.EndRow + 1 self.ensure_row_count(min_row) if ensure_cols: min_col = cell_addr.Column + data_rng_addr.EndColumn + 1 self.ensure_column_count(min_col) data_rng_addr.StartRow += cell_addr.Row data_rng_addr.StartColumn += cell_addr.Column data_rng_addr.EndRow += cell_addr.Row data_rng_addr.EndColumn += cell_addr.Column data_rng = self.get_cell_range(data_rng_addr) data_rng.set_data_array(data)
[docs] def get_cell_value(self, cell: XCell, formula_value: bool = False) -> float | str | None: """ Get the cell value. Args: cell (XCell): Cell to get the value from. formula_value (bool, optional): Specifies if formula value should be returned instead of the formula. Defaults to ``False``. Returns: float | str | None: Cell value. Note: - If the cell content is a float then a float is returned. - If the cell content is a string or a formula then a string is returned. - If the cell content is empty then ``None`` is returned. If the cell has a formula set then if ``formula_value`` is ``False`` then the formula is returned; Otherwise, the result of the formula is returned. """ t = cast(CellContentType, cell.getType()) if t == CellContentType.EMPTY: return None if t == CellContentType.VALUE: try: return float(cell.getValue()) except ValueError: return 0.0 if t == CellContentType.TEXT: return cell.getString() # type: ignore if t == CellContentType.FORMULA: if formula_value: # could be a string or a float val = cell.getValue() # by default getValue() will return 0.0 if the formula result is a string. # in the event the formula result is actually 0.0 then getString() will return '0' return val if val != 0.0 else cell.getString() # type: ignore return cell.getFormula()
# region Properties @property def owner(self) -> T: """Owner of this component.""" return self._owner @property def name(self) -> str: """ Get/Sets the name of the table. When setting the name, it will be converted to a valid name by replacing spaces with underscores and removing leading and trailing spaces. """ return self.component.getName() @name.setter def name(self, name: str) -> None: s = name.strip().replace(" ", "_") old_name = self.component.getName() if s != old_name: self.component.setName(s) @property def columns(self) -> WriteTableColumns: """Table Rows""" if self._cols is None: # pylint: disable=import-outside-toplevel from ooodev.write.table.write_table_columns import WriteTableColumns self._cols = WriteTableColumns(owner=self, component=self.component.getColumns()) return self._cols @property def rows(self) -> WriteTableRows: """Table Rows""" if self._rows is None: # pylint: disable=import-outside-toplevel from ooodev.write.table.write_table_rows import WriteTableRows self._rows = WriteTableRows(owner=self, component=self.component.getRows()) return self._rows @property @override def table_column_separators(self) -> TableColumnSeparators: # type: ignore """ Table Column Separators Moving a column separator will change the width of the column to the left and right of the separator. There is one less separator than there are columns. To get the number of separators, use the length of this property (``len(instance.table_column_separators)``). The position of each separator must be greater then the previous separator and less then the next separator. The position of the last separator must be less then the value of ``table_column_relative_sum`` property. Example: .. code-block:: python table = doc.tables[0] table.table_column_separators[1].position = 5312 for sep in table.table_column_separators: print(sep) """ return TableColumnSeparators(self.component) @property @override def table_column_relative_sum(self) -> int: # type: ignore """Gets the sum of the relative widths of all columns.""" return self.component.TableColumnRelativeSum @property def range_converter(self) -> RangeConverter: """Gets access to a range converter.""" if self._range_converter is None: self._range_converter = RangeConverter(lo_inst=self.lo_inst) return self._range_converter @property def style_direct(self) -> TableStyler: """ Direct Cell Styler. Returns: CellStyler: Character Styler """ if self._style_direct is None: # pylint: disable=import-outside-toplevel from ooodev.write.style.direct.table.table_styler import TableStyler self._style_direct = TableStyler(owner=self, component=self.component) self._style_direct.add_event_observers(self.event_observer) return self._style_direct
# endregion Properties if mock_g.FULL_IMPORT: from ooodev.write.table.write_table_rows import WriteTableRows from ooodev.write.table.write_table_columns import WriteTableColumns from ooodev.write.style.direct.table.table_styler import TableStyler