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