Package logilab-common-0 :: Package 39 :: Package 0 :: Module table
[frames] | no frames]

Source Code for Module logilab-common-0.39.0.table

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