Package logilab :: Package common :: Module table
[frames] | no frames]

Source Code for Module logilab.common.table

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