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

Source Code for Module logilab.common.table

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