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