Package logilab :: Package common :: Module table

[frames] | no frames]

# Source Code for Module logilab.common.table

      1  # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
      2  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
      3  #
      4  # This file is part of logilab-common.
      5  #
      6  # logilab-common is free software: you can redistribute it and/or modify it under
      7  # the terms of the GNU Lesser General Public License as published by the Free
      8  # Software Foundation, either version 2.1 of the License, or (at your option) any
      9  # later version.
     10  #
     11  # logilab-common is distributed in the hope that it will be useful, but WITHOUT
     12  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
     13  # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
     14  # details.
     15  #
     16  # You should have received a copy of the GNU Lesser General Public License along
     17  # with logilab-common.  If not, see <http://www.gnu.org/licenses/>.
     18  """Table management module."""
     19
     20  from __future__ import print_function
     21
     22  __docformat__ = "restructuredtext en"
     23
     24  from six.moves import range
     25
     26 -class Table(object):
     27      """Table defines a data table with column and row names.
     28      inv:
     29          len(self.data) <= len(self.row_names)
     30          forall(self.data, lambda x: len(x) <= len(self.col_names))
     31      """
     32
     33 -    def __init__(self, default_value=0, col_names=None, row_names=None):
     34          self.col_names = []
     35          self.row_names = []
     36          self.data = []
     37          self.default_value = default_value
     38          if col_names:
     39              self.create_columns(col_names)
     40          if row_names:
     41              self.create_rows(row_names)
     42
     43 -    def _next_row_name(self):
     44          return 'row%s' % (len(self.row_names)+1)
     45
     46 -    def __iter__(self):
     47          return iter(self.data)
     48
     49 -    def __eq__(self, other):
     50          if other is None:
     51              return False
     52          else:
     53              return list(self) == list(other)
     54
     55      __hash__ = object.__hash__
     56
     57 -    def __ne__(self, other):
     58          return not self == other
     59
     60 -    def __len__(self):
     61          return len(self.row_names)
     62
     63      ## Rows / Columns creation #################################################
     64 -    def create_rows(self, row_names):
     65          """Appends row_names to the list of existing rows
     66          """
     67          self.row_names.extend(row_names)
     68          for row_name in row_names:
     69              self.data.append([self.default_value]*len(self.col_names))
     70
     71 -    def create_columns(self, col_names):
     72          """Appends col_names to the list of existing columns
     73          """
     74          for col_name in col_names:
     75              self.create_column(col_name)
     76
     77 -    def create_row(self, row_name=None):
     78          """Creates a rowname to the row_names list
     79          """
     80          row_name = row_name or self._next_row_name()
     81          self.row_names.append(row_name)
     82          self.data.append([self.default_value]*len(self.col_names))
     83
     84
     85 -    def create_column(self, col_name):
     86          """Creates a colname to the col_names list
     87          """
     88          self.col_names.append(col_name)
     89          for row in self.data:
     90              row.append(self.default_value)
     91
     92      ## Sort by column ##########################################################
     93 -    def sort_by_column_id(self, col_id, method = 'asc'):
     94          """Sorts the table (in-place) according to data stored in col_id
     95          """
     96          try:
     97              col_index = self.col_names.index(col_id)
     98              self.sort_by_column_index(col_index, method)
     99          except ValueError:
    100              raise KeyError("Col (%s) not found in table" % (col_id))
    101
    102
    103 -    def sort_by_column_index(self, col_index, method = 'asc'):
    104          """Sorts the table 'in-place' according to data stored in col_index
    105
    106          method should be in ('asc', 'desc')
    107          """
    108          sort_list = sorted([(row[col_index], row, row_name)
    109                       for row, row_name in zip(self.data, self.row_names)])
    110          # Sorting sort_list will sort according to col_index
    111          # If we want reverse sort, then reverse list
    112          if method.lower() == 'desc':
    113              sort_list.reverse()
    114
    115          # Rebuild data / row names
    116          self.data = []
    117          self.row_names = []
    118          for val, row, row_name in sort_list:
    119              self.data.append(row)
    120              self.row_names.append(row_name)
    121
    122 -    def groupby(self, colname, *others):
    123          """builds indexes of data
    124          :returns: nested dictionaries pointing to actual rows
    125          """
    126          groups = {}
    127          colnames = (colname,) + others
    128          col_indexes = [self.col_names.index(col_id) for col_id in colnames]
    129          for row in self.data:
    130              ptr = groups
    131              for col_index in col_indexes[:-1]:
    132                  ptr = ptr.setdefault(row[col_index], {})
    133              ptr = ptr.setdefault(row[col_indexes[-1]],
    134                                   Table(default_value=self.default_value,
    135                                         col_names=self.col_names))
    136              ptr.append_row(tuple(row))
    137          return groups
    138
    139 -    def select(self, colname, value):
    140          grouped = self.groupby(colname)
    141          try:
    142              return grouped[value]
    143          except KeyError:
    144              return []
    145
    146 -    def remove(self, colname, value):
    147          col_index = self.col_names.index(colname)
    148          for row in self.data[:]:
    149              if row[col_index] == value:
    150                  self.data.remove(row)
    151
    152
    153      ## The 'setter' part #######################################################
    154 -    def set_cell(self, row_index, col_index, data):
    155          """sets value of cell 'row_indew', 'col_index' to data
    156          """
    157          self.data[row_index][col_index] = data
    158
    159
    160 -    def set_cell_by_ids(self, row_id, col_id, data):
    161          """sets value of cell mapped by row_id and col_id to data
    162          Raises a KeyError if row_id or col_id are not found in the table
    163          """
    164          try:
    165              row_index = self.row_names.index(row_id)
    166          except ValueError:
    167              raise KeyError("Row (%s) not found in table" % (row_id))
    168          else:
    169              try:
    170                  col_index = self.col_names.index(col_id)
    171                  self.data[row_index][col_index] = data
    172              except ValueError:
    173                  raise KeyError("Column (%s) not found in table" % (col_id))
    174
    175
    176 -    def set_row(self, row_index, row_data):
    177          """sets the 'row_index' row
    178          pre:
    179              type(row_data) == types.ListType
    180              len(row_data) == len(self.col_names)
    181          """
    182          self.data[row_index] = row_data
    183
    184
    185 -    def set_row_by_id(self, row_id, row_data):
    186          """sets the 'row_id' column
    187          pre:
    188              type(row_data) == types.ListType
    189              len(row_data) == len(self.row_names)
    190          Raises a KeyError if row_id is not found
    191          """
    192          try:
    193              row_index = self.row_names.index(row_id)
    194              self.set_row(row_index, row_data)
    195          except ValueError:
    196              raise KeyError('Row (%s) not found in table' % (row_id))
    197
    198
    199 -    def append_row(self, row_data, row_name=None):
    200          """Appends a row to the table
    201          pre:
    202              type(row_data) == types.ListType
    203              len(row_data) == len(self.col_names)
    204          """
    205          row_name = row_name or self._next_row_name()
    206          self.row_names.append(row_name)
    207          self.data.append(row_data)
    208          return len(self.data) - 1
    209
    210 -    def insert_row(self, index, row_data, row_name=None):
    211          """Appends row_data before 'index' in the table. To make 'insert'
    212          behave like 'list.insert', inserting in an out of range index will
    213          insert row_data to the end of the list
    214          pre:
    215              type(row_data) == types.ListType
    216              len(row_data) == len(self.col_names)
    217          """
    218          row_name = row_name or self._next_row_name()
    219          self.row_names.insert(index, row_name)
    220          self.data.insert(index, row_data)
    221
    222
    223 -    def delete_row(self, index):
    224          """Deletes the 'index' row in the table, and returns it.
    225          Raises an IndexError if index is out of range
    226          """
    227          self.row_names.pop(index)
    228          return self.data.pop(index)
    229
    230
    231 -    def delete_row_by_id(self, row_id):
    232          """Deletes the 'row_id' row in the table.
    233          Raises a KeyError if row_id was not found.
    234          """
    235          try:
    236              row_index = self.row_names.index(row_id)
    237              self.delete_row(row_index)
    238          except ValueError:
    239              raise KeyError('Row (%s) not found in table' % (row_id))
    240
    241
    242 -    def set_column(self, col_index, col_data):
    243          """sets the 'col_index' column
    244          pre:
    245              type(col_data) == types.ListType
    246              len(col_data) == len(self.row_names)
    247          """
    248
    249          for row_index, cell_data in enumerate(col_data):
    250              self.data[row_index][col_index] = cell_data
    251
    252
    253 -    def set_column_by_id(self, col_id, col_data):
    254          """sets the 'col_id' column
    255          pre:
    256              type(col_data) == types.ListType
    257              len(col_data) == len(self.col_names)
    258          Raises a KeyError if col_id is not found
    259          """
    260          try:
    261              col_index = self.col_names.index(col_id)
    262              self.set_column(col_index, col_data)
    263          except ValueError:
    264              raise KeyError('Column (%s) not found in table' % (col_id))
    265
    266
    267 -    def append_column(self, col_data, col_name):
    268          """Appends the 'col_index' column
    269          pre:
    270              type(col_data) == types.ListType
    271              len(col_data) == len(self.row_names)
    272          """
    273          self.col_names.append(col_name)
    274          for row_index, cell_data in enumerate(col_data):
    275              self.data[row_index].append(cell_data)
    276
    277
    278 -    def insert_column(self, index, col_data, col_name):
    279          """Appends col_data before 'index' in the table. To make 'insert'
    280          behave like 'list.insert', inserting in an out of range index will
    281          insert col_data to the end of the list
    282          pre:
    283              type(col_data) == types.ListType
    284              len(col_data) == len(self.row_names)
    285          """
    286          self.col_names.insert(index, col_name)
    287          for row_index, cell_data in enumerate(col_data):
    288              self.data[row_index].insert(index, cell_data)
    289
    290
    291 -    def delete_column(self, index):
    292          """Deletes the 'index' column in the table, and returns it.
    293          Raises an IndexError if index is out of range
    294          """
    295          self.col_names.pop(index)
    296          return [row.pop(index) for row in self.data]
    297
    298
    299 -    def delete_column_by_id(self, col_id):
    300          """Deletes the 'col_id' col in the table.
    301          Raises a KeyError if col_id was not found.
    302          """
    303          try:
    304              col_index = self.col_names.index(col_id)
    305              self.delete_column(col_index)
    306          except ValueError:
    307              raise KeyError('Column (%s) not found in table' % (col_id))
    308
    309
    310      ## The 'getter' part #######################################################
    311
    312 -    def get_shape(self):
    313          """Returns a tuple which represents the table's shape
    314          """
    315          return len(self.row_names), len(self.col_names)
    316      shape = property(get_shape)
    317
    318 -    def __getitem__(self, indices):
    319          """provided for convenience"""
    320          rows, multirows = None, False
    321          cols, multicols = None, False
    322          if isinstance(indices, tuple):
    323              rows = indices[0]
    324              if len(indices) > 1:
    325                  cols = indices[1]
    326          else:
    327              rows = indices
    328          # define row slice
    329          if isinstance(rows, str):
    330              try:
    331                  rows = self.row_names.index(rows)
    332              except ValueError:
    333                  raise KeyError("Row (%s) not found in table" % (rows))
    334          if isinstance(rows, int):
    335              rows = slice(rows, rows+1)
    336              multirows = False
    337          else:
    338              rows = slice(None)
    339              multirows = True
    340          # define col slice
    341          if isinstance(cols, str):
    342              try:
    343                  cols = self.col_names.index(cols)
    344              except ValueError:
    345                  raise KeyError("Column (%s) not found in table" % (cols))
    346          if isinstance(cols, int):
    347              cols = slice(cols, cols+1)
    348              multicols = False
    349          else:
    350              cols = slice(None)
    351              multicols = True
    352          # get sub-table
    353          tab = Table()
    354          tab.default_value = self.default_value
    355          tab.create_rows(self.row_names[rows])
    356          tab.create_columns(self.col_names[cols])
    357          for idx, row in enumerate(self.data[rows]):
    358              tab.set_row(idx, row[cols])
    359          if multirows :
    360              if multicols:
    361                  return tab
    362              else:
    363                  return [item[0] for item in tab.data]
    364          else:
    365              if multicols:
    366                  return tab.data[0]
    367              else:
    368                  return tab.data[0][0]
    369
    370 -    def get_cell_by_ids(self, row_id, col_id):
    371          """Returns the element at [row_id][col_id]
    372          """
    373          try:
    374              row_index = self.row_names.index(row_id)
    375          except ValueError:
    376              raise KeyError("Row (%s) not found in table" % (row_id))
    377          else:
    378              try:
    379                  col_index = self.col_names.index(col_id)
    380              except ValueError:
    381                  raise KeyError("Column (%s) not found in table" % (col_id))
    382          return self.data[row_index][col_index]
    383
    384 -    def get_row_by_id(self, row_id):
    385          """Returns the 'row_id' row
    386          """
    387          try:
    388              row_index = self.row_names.index(row_id)
    389          except ValueError:
    390              raise KeyError("Row (%s) not found in table" % (row_id))
    391          return self.data[row_index]
    392
    393 -    def get_column_by_id(self, col_id, distinct=False):
    394          """Returns the 'col_id' col
    395          """
    396          try:
    397              col_index = self.col_names.index(col_id)
    398          except ValueError:
    399              raise KeyError("Column (%s) not found in table" % (col_id))
    400          return self.get_column(col_index, distinct)
    401
    402 -    def get_columns(self):
    403          """Returns all the columns in the table
    404          """
    405          return [self[:, index] for index in range(len(self.col_names))]
    406
    407 -    def get_column(self, col_index, distinct=False):
    408          """get a column by index"""
    409          col = [row[col_index] for row in self.data]
    410          if distinct:
    411              col = list(set(col))
    412          return col
    413
    414 -    def apply_stylesheet(self, stylesheet):
    415          """Applies the stylesheet to this table
    416          """
    417          for instruction in stylesheet.instructions:
    418              eval(instruction)
    419
    420
    421 -    def transpose(self):
    422          """Keeps the self object intact, and returns the transposed (rotated)
    423          table.
    424          """
    425          transposed = Table()
    426          transposed.create_rows(self.col_names)
    427          transposed.create_columns(self.row_names)
    428          for col_index, column in enumerate(self.get_columns()):
    429              transposed.set_row(col_index, column)
    430          return transposed
    431
    432
    433 -    def pprint(self):
    434          """returns a string representing the table in a pretty
    435          printed 'text' format.
    436          """
    437          # The maximum row name (to know the start_index of the first col)
    438          max_row_name = 0
    439          for row_name in self.row_names:
    440              if len(row_name) > max_row_name:
    441                  max_row_name = len(row_name)
    442          col_start = max_row_name + 5
    443
    444          lines = []
    445          # Build the 'first' line <=> the col_names one
    446          # The first cell <=> an empty one
    447          col_names_line = [' '*col_start]
    448          for col_name in self.col_names:
    449              col_names_line.append(col_name + ' '*5)
    450          lines.append('|' + '|'.join(col_names_line) + '|')
    451          max_line_length = len(lines[0])
    452
    453          # Build the table
    454          for row_index, row in enumerate(self.data):
    455              line = []
    456              # First, build the row_name's cell
    457              row_name = self.row_names[row_index]
    458              line.append(row_name + ' '*(col_start-len(row_name)))
    459
    460              # Then, build all the table's cell for this line.
    461              for col_index, cell in enumerate(row):
    462                  col_name_length = len(self.col_names[col_index]) + 5
    463                  data = str(cell)
    464                  line.append(data + ' '*(col_name_length - len(data)))
    465              lines.append('|' + '|'.join(line) + '|')
    466              if len(lines[-1]) > max_line_length:
    467                  max_line_length = len(lines[-1])
    468
    469          # Wrap the table with '-' to make a frame
    470          lines.insert(0, '-'*max_line_length)
    471          lines.append('-'*max_line_length)
    472          return '\n'.join(lines)
    473
    474
    475 -    def __repr__(self):
    476          return repr(self.data)
    477
    478 -    def as_text(self):
    479          data = []
    480          # We must convert cells into strings before joining them
    481          for row in self.data:
    482              data.append([str(cell) for cell in row])
    483          lines = ['\t'.join(row) for row in data]
    484          return '\n'.join(lines)
    485
    486
    487
    488 -class TableStyle:
    489      """Defines a table's style
    490      """
    491
    492 -    def __init__(self, table):
    493
    494          self._table = table
    495          self.size = dict([(col_name, '1*') for col_name in table.col_names])
    496          # __row_column__ is a special key to define the first column which
    497          # actually has no name (<=> left most column <=> row names column)
    498          self.size['__row_column__'] = '1*'
    499          self.alignment = dict([(col_name, 'right')
    500                                 for col_name in table.col_names])
    501          self.alignment['__row_column__'] = 'right'
    502
    503          # We shouldn't have to create an entry for
    504          # the 1st col (the row_column one)
    505          self.units = dict([(col_name, '') for col_name in table.col_names])
    506          self.units['__row_column__'] = ''
    507
    508      # XXX FIXME : params order should be reversed for all set() methods
    509 -    def set_size(self, value, col_id):
    510          """sets the size of the specified col_id to value
    511          """
    512          self.size[col_id] = value
    513
    514 -    def set_size_by_index(self, value, col_index):
    515          """Allows to set the size according to the column index rather than
    516          using the column's id.
    517          BE CAREFUL : the '0' column is the '__row_column__' one !
    518          """
    519          if col_index == 0:
    520              col_id = '__row_column__'
    521          else:
    522              col_id = self._table.col_names[col_index-1]
    523
    524          self.size[col_id] = value
    525
    526
    527 -    def set_alignment(self, value, col_id):
    528          """sets the alignment of the specified col_id to value
    529          """
    530          self.alignment[col_id] = value
    531
    532
    533 -    def set_alignment_by_index(self, value, col_index):
    534          """Allows to set the alignment according to the column index rather than
    535          using the column's id.
    536          BE CAREFUL : the '0' column is the '__row_column__' one !
    537          """
    538          if col_index == 0:
    539              col_id = '__row_column__'
    540          else:
    541              col_id = self._table.col_names[col_index-1]
    542
    543          self.alignment[col_id] = value
    544
    545
    546 -    def set_unit(self, value, col_id):
    547          """sets the unit of the specified col_id to value
    548          """
    549          self.units[col_id] = value
    550
    551
    552 -    def set_unit_by_index(self, value, col_index):
    553          """Allows to set the unit according to the column index rather than
    554          using the column's id.
    555          BE CAREFUL :  the '0' column is the '__row_column__' one !
    556          (Note that in the 'unit' case, you shouldn't have to set a unit
    557          for the 1st column (the __row__column__ one))
    558          """
    559          if col_index == 0:
    560              col_id = '__row_column__'
    561          else:
    562              col_id = self._table.col_names[col_index-1]
    563
    564          self.units[col_id] = value
    565
    566
    567 -    def get_size(self, col_id):
    568          """Returns the size of the specified col_id
    569          """
    570          return self.size[col_id]
    571
    572
    573 -    def get_size_by_index(self, col_index):
    574          """Allows to get the size  according to the column index rather than
    575          using the column's id.
    576          BE CAREFUL : the '0' column is the '__row_column__' one !
    577          """
    578          if col_index == 0:
    579              col_id = '__row_column__'
    580          else:
    581              col_id = self._table.col_names[col_index-1]
    582
    583          return self.size[col_id]
    584
    585
    586 -    def get_alignment(self, col_id):
    587          """Returns the alignment of the specified col_id
    588          """
    589          return self.alignment[col_id]
    590
    591
    592 -    def get_alignment_by_index(self, col_index):
    593          """Allors to get the alignment according to the column index rather than
    594          using the column's id.
    595          BE CAREFUL : the '0' column is the '__row_column__' one !
    596          """
    597          if col_index == 0:
    598              col_id = '__row_column__'
    599          else:
    600              col_id = self._table.col_names[col_index-1]
    601
    602          return self.alignment[col_id]
    603
    604
    605 -    def get_unit(self, col_id):
    606          """Returns the unit of the specified col_id
    607          """
    608          return self.units[col_id]
    609
    610
    611 -    def get_unit_by_index(self, col_index):
    612          """Allors to get the unit according to the column index rather than
    613          using the column's id.
    614          BE CAREFUL : the '0' column is the '__row_column__' one !
    615          """
    616          if col_index == 0:
    617              col_id = '__row_column__'
    618          else:
    619              col_id = self._table.col_names[col_index-1]
    620
    621          return self.units[col_id]
    622
    623
    624  import re
    625  CELL_PROG = re.compile("([0-9]+)_([0-9]+)")
    626
    627 -class TableStyleSheet:
    628      """A simple Table stylesheet
    629      Rules are expressions where cells are defined by the row_index
    630      and col_index separated by an underscore ('_').
    631      For example, suppose you want to say that the (2,5) cell must be
    632      the sum of its two preceding cells in the row, you would create
    633      the following rule :
    634          2_5 = 2_3 + 2_4
    635      You can also use all the math.* operations you want. For example:
    636          2_5 = sqrt(2_3**2 + 2_4**2)
    637      """
    638
    639 -    def __init__(self, rules = None):
    640          rules = rules or []
    641          self.rules = []
    642          self.instructions = []
    643          for rule in rules:
    644              self.add_rule(rule)
    645
    646
    647 -    def add_rule(self, rule):
    648          """Adds a rule to the stylesheet rules
    649          """
    650          try:
    651              source_code = ['from math import *']
    652              source_code.append(CELL_PROG.sub(r'self.data[\1][\2]', rule))
    653              self.instructions.append(compile('\n'.join(source_code),
    654                  'table.py', 'exec'))
    655              self.rules.append(rule)
    656          except SyntaxError:
    657              print("Bad Stylesheet Rule : %s [skipped]" % rule)
    658
    659
    660 -    def add_rowsum_rule(self, dest_cell, row_index, start_col, end_col):
    661          """Creates and adds a rule to sum over the row at row_index from
    662          start_col to end_col.
    663          dest_cell is a tuple of two elements (x,y) of the destination cell
    664          No check is done for indexes ranges.
    665          pre:
    666              start_col >= 0
    667              end_col > start_col
    668          """
    669          cell_list = ['%d_%d'%(row_index, index) for index in range(start_col,
    670                                                                     end_col + 1)]
    671          rule = '%d_%d=' % dest_cell + '+'.join(cell_list)
    672          self.add_rule(rule)
    673
    674
    675 -    def add_rowavg_rule(self, dest_cell, row_index, start_col, end_col):
    676          """Creates and adds a rule to make the row average (from start_col
    677          to end_col)
    678          dest_cell is a tuple of two elements (x,y) of the destination cell
    679          No check is done for indexes ranges.
    680          pre:
    681              start_col >= 0
    682              end_col > start_col
    683          """
    684          cell_list = ['%d_%d'%(row_index, index) for index in range(start_col,
    685                                                                     end_col + 1)]
    686          num = (end_col - start_col + 1)
    687          rule = '%d_%d=' % dest_cell + '('+'+'.join(cell_list)+')/%f'%num
    688          self.add_rule(rule)
    689
    690
    691 -    def add_colsum_rule(self, dest_cell, col_index, start_row, end_row):
    692          """Creates and adds a rule to sum over the col at col_index from
    693          start_row to end_row.
    694          dest_cell is a tuple of two elements (x,y) of the destination cell
    695          No check is done for indexes ranges.
    696          pre:
    697              start_row >= 0
    698              end_row > start_row
    699          """
    700          cell_list = ['%d_%d'%(index, col_index) for index in range(start_row,
    701                                                                     end_row + 1)]
    702          rule = '%d_%d=' % dest_cell + '+'.join(cell_list)
    703          self.add_rule(rule)
    704
    705
    706 -    def add_colavg_rule(self, dest_cell, col_index, start_row, end_row):
    707          """Creates and adds a rule to make the col average (from start_row
    708          to end_row)
    709          dest_cell is a tuple of two elements (x,y) of the destination cell
    710          No check is done for indexes ranges.
    711          pre:
    712              start_row >= 0
    713              end_row > start_row
    714          """
    715          cell_list = ['%d_%d'%(index, col_index) for index in range(start_row,
    716                                                                     end_row + 1)]
    717          num = (end_row - start_row + 1)
    718          rule = '%d_%d=' % dest_cell + '('+'+'.join(cell_list)+')/%f'%num
    719          self.add_rule(rule)
    720
    721
    722
    723 -class TableCellRenderer:
    724      """Defines a simple text renderer
    725      """
    726
    727 -    def __init__(self, **properties):
    728          """keywords should be properties with an associated boolean as value.
    729          For example :
    730              renderer = TableCellRenderer(units = True, alignment = False)
    731          An unspecified property will have a 'False' value by default.
    732          Possible properties are :
    733              alignment, unit
    734          """
    735          self.properties = properties
    736
    737
    738 -    def render_cell(self, cell_coord, table, table_style):
    739          """Renders the cell at 'cell_coord' in the table, using table_style
    740          """
    741          row_index, col_index = cell_coord
    742          cell_value = table.data[row_index][col_index]
    743          final_content = self._make_cell_content(cell_value,
    744                                                  table_style, col_index  +1)
    745          return self._render_cell_content(final_content,
    746                                           table_style, col_index + 1)
    747
    748
    749 -    def render_row_cell(self, row_name, table, table_style):
    750          """Renders the cell for 'row_id' row
    751          """
    752          cell_value = row_name
    753          return self._render_cell_content(cell_value, table_style, 0)
    754
    755
    756 -    def render_col_cell(self, col_name, table, table_style):
    757          """Renders the cell for 'col_id' row
    758          """
    759          cell_value = col_name
    760          col_index = table.col_names.index(col_name)
    761          return self._render_cell_content(cell_value, table_style, col_index +1)
    762
    763
    764
    765 -    def _render_cell_content(self, content, table_style, col_index):
    766          """Makes the appropriate rendering for this cell content.
    767          Rendering properties will be searched using the
    768          *table_style.get_xxx_by_index(col_index)' methods
    769
    770          **This method should be overridden in the derived renderer classes.**
    771          """
    772          return content
    773
    774
    775 -    def _make_cell_content(self, cell_content, table_style, col_index):
    776          """Makes the cell content (adds decoration data, like units for
    777          example)
    778          """
    779          final_content = cell_content
    780          if 'skip_zero' in self.properties:
    781              replacement_char = self.properties['skip_zero']
    782          else:
    783              replacement_char = 0
    784          if replacement_char and final_content == 0:
    785              return replacement_char
    786
    787          try:
    788              units_on = self.properties['units']
    789              if units_on:
    790                  final_content = self._add_unit(
    791                      cell_content, table_style, col_index)
    792          except KeyError:
    793              pass
    794
    795          return final_content
    796
    797
    798 -    def _add_unit(self, cell_content, table_style, col_index):
    799          """Adds unit to the cell_content if needed
    800          """
    801          unit = table_style.get_unit_by_index(col_index)
    802          return str(cell_content) + " " + unit
    803
    804
    805
    806 -class DocbookRenderer(TableCellRenderer):
    807      """Defines how to render a cell for a docboook table
    808      """
    809
    810 -    def define_col_header(self, col_index, table_style):
    811          """Computes the colspec element according to the style
    812          """
    813          size = table_style.get_size_by_index(col_index)
    814          return '<colspec colname="c%d" colwidth="%s"/>\n' % \
    815                 (col_index, size)
    816
    817
    818 -    def _render_cell_content(self, cell_content, table_style, col_index):
    819          """Makes the appropriate rendering for this cell content.
    820          Rendering properties will be searched using the
    821          table_style.get_xxx_by_index(col_index)' methods.
    822          """
    823          try:
    824              align_on = self.properties['alignment']
    825              alignment = table_style.get_alignment_by_index(col_index)
    826              if align_on:
    827                  return "<entry align='%s'>%s</entry>\n" % \
    828                         (alignment, cell_content)
    829          except KeyError:
    830              # KeyError <=> Default alignment
    831              return "<entry>%s</entry>\n" % cell_content
    832
    833
    834 -class TableWriter:
    835      """A class to write tables
    836      """
    837
    838 -    def __init__(self, stream, table, style, **properties):
    839          self._stream = stream
    840          self.style = style or TableStyle(table)
    841          self._table = table
    842          self.properties = properties
    843          self.renderer = None
    844
    845
    846 -    def set_style(self, style):
    847          """sets the table's associated style
    848          """
    849          self.style = style
    850
    851
    852 -    def set_renderer(self, renderer):
    853          """sets the way to render cell
    854          """
    855          self.renderer = renderer
    856
    857
    858 -    def update_properties(self, **properties):
    859          """Updates writer's properties (for cell rendering)
    860          """
    861          self.properties.update(properties)
    862
    863
    864 -    def write_table(self, title = ""):
    865          """Writes the table
    866          """
    867          raise NotImplementedError("write_table must be implemented !")
    868
    869
    870
    871 -class DocbookTableWriter(TableWriter):
    872      """Defines an implementation of TableWriter to write a table in Docbook
    873      """
    874
    875 -    def _write_headers(self):
    876          """Writes col headers
    877          """
    878          # Define col_headers (colstpec elements)
    879          for col_index in range(len(self._table.col_names)+1):
    880              self._stream.write(self.renderer.define_col_header(col_index,
    881                                                                self.style))
    882
    883          self._stream.write("<thead>\n<row>\n")
    884          # XXX FIXME : write an empty entry <=> the first (__row_column) column
    885          self._stream.write('<entry></entry>\n')
    886          for col_name in self._table.col_names:
    887              self._stream.write(self.renderer.render_col_cell(
    888                  col_name, self._table,
    889                  self.style))
    890
    891          self._stream.write("</row>\n</thead>\n")
    892
    893
    894 -    def _write_body(self):
    895          """Writes the table body
    896          """
    897          self._stream.write('<tbody>\n')
    898
    899          for row_index, row in enumerate(self._table.data):
    900              self._stream.write('<row>\n')
    901              row_name = self._table.row_names[row_index]
    902              # Write the first entry (row_name)
    903              self._stream.write(self.renderer.render_row_cell(row_name,
    904                                                              self._table,
    905                                                              self.style))
    906
    907              for col_index, cell in enumerate(row):
    908                  self._stream.write(self.renderer.render_cell(
    909                      (row_index, col_index),
    910                      self._table, self.style))
    911
    912              self._stream.write('</row>\n')
    913
    914          self._stream.write('</tbody>\n')
    915
    916
    917 -    def write_table(self, title = ""):
    918          """Writes the table
    919          """
    920          self._stream.write('<table>\n<title>%s></title>\n'%(title))
    921          self._stream.write(
    922              '<tgroup cols="%d" align="left" colsep="1" rowsep="1">\n'%
    923              (len(self._table.col_names)+1))
    924          self._write_headers()
    925          self._write_body()
    926
    927          self._stream.write('</tgroup>\n</table>\n')
    928