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

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