1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """Table management module."""
19 __docformat__ = "restructuredtext en"
20
21
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
40 return 'row%s' % (len(self.row_names)+1)
41
43 return iter(self.data)
44
46 if other is None:
47 return False
48 else:
49 return list(self) == list(other)
50
52 return not self == other
53
55 return len(self.row_names)
56
57
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
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
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
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
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
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
105
106 if method.lower() == 'desc':
107 sort_list.reverse()
108
109
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
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
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
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
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
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
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
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
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
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
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
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
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
305
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
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
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
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
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
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
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
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
397 """Returns all the columns in the table
398 """
399 return [self[:, index] for index in range(len(self.col_names))]
400
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
409 """Applies the stylesheet to this table
410 """
411 for instruction in stylesheet.instructions:
412 eval(instruction)
413
414
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
428 """returns a string representing the table in a pretty
429 printed 'text' format.
430 """
431
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
440
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
448 for row_index, row in enumerate(self.data):
449 line = []
450
451 row_name = self.row_names[row_index].encode('iso-8859-1')
452 line.append(row_name + ' '*(col_start-len(row_name)))
453
454
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
464 lines.insert(0, '-'*max_line_length)
465 lines.append('-'*max_line_length)
466 return '\n'.join(lines)
467
468
470 return repr(self.data)
471
473 data = []
474
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
483 """Defines a table's style
484 """
485
487
488 self._table = table
489 self.size = dict([(col_name, '1*') for col_name in table.col_names])
490
491
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
498
499 self.units = dict([(col_name, '') for col_name in table.col_names])
500 self.units['__row_column__'] = ''
501
502
504 """sets the size of the specified col_id to value
505 """
506 self.size[col_id] = value
507
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
522 """sets the alignment of the specified col_id to value
523 """
524 self.alignment[col_id] = value
525
526
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
541 """sets the unit of the specified col_id to value
542 """
543 self.units[col_id] = value
544
545
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
562 """Returns the size of the specified col_id
563 """
564 return self.size[col_id]
565
566
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
581 """Returns the alignment of the specified col_id
582 """
583 return self.alignment[col_id]
584
585
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
600 """Returns the unit of the specified col_id
601 """
602 return self.units[col_id]
603
604
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
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
634 rules = rules or []
635 self.rules = []
636 self.instructions = []
637 for rule in rules:
638 self.add_rule(rule)
639
640
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
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
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
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
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
718 """Defines a simple text renderer
719 """
720
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
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
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
801 """Defines how to render a cell for a docboook table
802 """
803
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
825 return "<entry>%s</entry>\n" % cell_content
826
827
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
841 """sets the table's associated style
842 """
843 self.style = style
844
845
847 """sets the way to render cell
848 """
849 self.renderer = renderer
850
851
853 """Updates writer's properties (for cell rendering)
854 """
855 self.properties.update(properties)
856
857
859 """Writes the table
860 """
861 raise NotImplementedError("write_table must be implemented !")
862
863
864
866 """Defines an implementation of TableWriter to write a table in Docbook
867 """
868
870 """Writes col headers
871 """
872
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
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
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
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