def __init__(self, cells=None, styles=None, nature=None): """ Construct a table object from a collection of cells and a dictionary of styles. :type cells: typing.Iterable[benker.cell.Cell] :param cells: Collection of cells. :type styles: typing.Dict[str, str] :param styles: Dictionary of key-value pairs, where *keys* are the style names. :type nature: str :ivar nature: User-defined string value. Table *nature* is similar to HTML ``@class`` attribute, you can use it do identify the styles to apply to your table. """ super(Table, self).__init__(styles, nature) self._grid = Grid(cells)
def test_g6(): # fmt: off g6 = Grid([ Cell("a", x=1, y=1), Cell("bb", x=2, y=1), Cell("cc", x=1, y=2), Cell("d", x=2, y=2) ]) actual = draw(g6) expected = textwrap.dedent("""\ +-----------+-----------+ | a | bb | +-----------+-----------+ | cc | d | +-----------+-----------+""") assert expected == actual
def test_g1(): # fmt: off g1 = Grid([ Cell("aaa", x=1, y=1, width=2), Cell("bb", x=3, y=1), Cell("cc", x=1, y=2), Cell("dddddddddd", x=2, y=2, width=2) ]) actual = draw(g1) expected = textwrap.dedent("""\ +-----------------------+-----------+ | aaa | bb | +-----------+-----------------------+ | cc | ddddddddd | +-----------+-----------------------+""") assert expected == actual
def test_g5(): # fmt: off g5 = Grid([ Cell("aa", x=1, y=1, height=2), Cell("bb", x=2, y=1), Cell("cccc", x=3, y=1), Cell("ddd", x=2, y=2), Cell("eeeee", x=3, y=2), Cell("ff", x=1, y=3, width=2), Cell("gggggg", x=3, y=3), ]) actual = draw(g5) expected = textwrap.dedent("""\ +-----------+-----------+-----------+ | aa | bb | cccc | | +-----------+-----------+ | | ddd | eeeee | +-----------------------+-----------+ | ff | gggggg | +-----------------------+-----------+""") assert expected == actual
def test_g4(): # fmt: off g4 = Grid([ Cell("aa", x=1, y=1, height=2), Cell("bbb", x=2, y=1, width=2), Cell("ccc", x=2, y=2, height=2), Cell("dd", x=3, y=2), Cell("eeee", x=1, y=3), Cell("ffffff", x=3, y=3), ]) actual = draw(g4) expected = textwrap.dedent("""\ +-----------+-----------------------+ | aa | bbb | | +-----------+-----------+ | | ccc | dd | +-----------| +-----------+ | eeee | | ffffff | +-----------+-----------+-----------+""") assert expected == actual
class Table(Styled, MutableMapping): """ Table data structure used to simplify conversion to CALS or HTML. Short demonstration: .. doctest:: table_demo >>> from benker.cell import Cell >>> from benker.table import Table >>> table = Table(styles={'frame': 'all'}) >>> table[(1, 1)] = Cell("one") >>> table.rows[1].insert_cell("two") >>> table[(2, 1)] <Cell('two', styles={}, nature=None, x=2, y=1, width=1, height=1)> >>> table.cols[1].insert_cell("alpha") >>> table.cols[2].insert_cell("beta") >>> (1, 2) in table True >>> del table[(1, 2)] >>> (1, 2) in table False >>> len(table) 3 >>> for cell in table: ... print(cell) one two beta >>> for row in table.rows: ... print(row) [one, two] [beta] >>> table.merge((1, 2), (2, 2)) >>> print(table) +-----------+-----------+ | one | two | +-----------------------+ | beta | +-----------------------+ >>> table.expand((1, 1), width=3) >>> print(table) +-----------------------------------------------+ | onetwo | +-----------------------+-----------+-----------+ | beta | | | +-----------------------+-----------+-----------+ """ rows = ViewsProperty(RowView) cols = ViewsProperty(ColView) def __init__(self, cells=None, styles=None, nature=None): """ Construct a table object from a collection of cells and a dictionary of styles. :type cells: typing.Iterable[benker.cell.Cell] :param cells: Collection of cells. :type styles: typing.Dict[str, str] :param styles: Dictionary of key-value pairs, where *keys* are the style names. :type nature: str :ivar nature: User-defined string value. Table *nature* is similar to HTML ``@class`` attribute, you can use it do identify the styles to apply to your table. """ super(Table, self).__init__(styles, nature) self._grid = Grid(cells) def __str__(self): return str(self._grid) @property def bounding_box(self): """ Bounding box of the table (``None`` if the table is empty). :rtype: benker.box.Box :return: The bounding box. """ return self._grid.bounding_box def _refresh_views(self, cell=None): """ Refresh all the rows and column views. """ if cell: self.rows.adopt_cell(cell) self.cols.adopt_cell(cell) else: self.rows.refresh_all() self.cols.refresh_all() def __contains__(self, coord): """ Check if the table contains a cell (a owned cell) at the given coordinates. :type coord: Coord or tuple(int, int) :param coord: Coordinates in the tables (1-indexed). :return: ``True`` if the table contains a cell at the given coordinates, else ``False``. """ return coord in self._grid def __setitem__(self, coord, cell): """ Insert a cell in the table at the given coordinates. :type coord: Coord or tuple(int, int) :param coord: Coordinates in the tables (1-indexed). :type cell: benker.cell.Cell :param cell: Cell to insert. """ self._grid[coord] = cell # get the cell with its new coordinates: cell = self._grid[coord] self._refresh_views(cell) def __delitem__(self, coord): """ Delete a cell from the table at the given coordinates. :type coord: Coord or tuple(int, int) :param coord: Coordinates in the tables (1-indexed). """ del self._grid[coord] self._refresh_views() def __getitem__(self, coord): """ Get a cell at the given coordinates. :type coord: Coord or tuple(int, int) :param coord: Coordinates in the tables (1-indexed). :rtype: benker.cell.Cell :return: The cell. """ return self._grid[coord] def __len__(self): return len(self._grid) def __iter__(self): return iter(self._grid) def merge(self, start, end, content_appender=None): self._grid.merge(start, end, content_appender=content_appender) self._refresh_views() def expand(self, coord, width=0, height=0, content_appender=None): self._grid.expand(coord, width=width, height=height, content_appender=content_appender) self._refresh_views() def fill_missing(self, bounding_box, content, styles=None, nature=None): """ Fill the missing cells in the table. This method is useful when some rows has missing cells (holes). :type bounding_box: Box :param bounding_box: The bounding box delimiting the cells/rows to fill if missing. :param content: User-defined cell content. It can be of any type: ``None``, :class:`str`, :class:`int`, :class:`float`, a container (:class:`list`), a XML element, etc. The same content can be shared by several cells, it's your own responsibility to handle the copy (or deep copy) of the *content* reference when needed. :type styles: typing.Dict[str, str] :param styles: User-defined cell styles: a dictionary of key-value pairs. This values are useful to store some HTML-like styles (border-style, border-width, border-color, vertical-align, text-align, etc.). Of course, we are not tied to the HTML-like styles, you can use your own list of styles. :type nature: str :ivar nature: a way to distinguish the body cells, from the header and the footer. The default value is ``None``, but you can use "body", "header", "footer" or whatever is suitable for your needs. If set to ``None``, the cell nature is inherited from the row nature. .. versionadded:: 0.5.0 """ for y in range(bounding_box.min.y, bounding_box.max.y + 1): for x in range(bounding_box.min.x, bounding_box.max.x + 1): if (x, y) not in self: cell = Cell(content, styles=styles, nature=nature, x=x, y=y) self[(x, y)] = cell