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
51 __hash__ = object.__hash__
52
54 return not self == other
55
57 return len(self.row_names)
58
59
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
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
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
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
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
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
107
108 if method.lower() == 'desc':
109 sort_list.reverse()
110
111
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
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
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
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
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
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
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
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
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
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
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
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
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
307
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
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
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
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
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
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
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
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
399 """Returns all the columns in the table
400 """
401 return [self[:, index] for index in range(len(self.col_names))]
402
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
411 """Applies the stylesheet to this table
412 """
413 for instruction in stylesheet.instructions:
414 eval(instruction)
415
416
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
430 """returns a string representing the table in a pretty
431 printed 'text' format.
432 """
433
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
442
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
450 for row_index, row in enumerate(self.data):
451 line = []
452
453 row_name = self.row_names[row_index]
454 line.append(row_name + ' '*(col_start-len(row_name)))
455
456
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
466 lines.insert(0, '-'*max_line_length)
467 lines.append('-'*max_line_length)
468 return '\n'.join(lines)
469
470
472 return repr(self.data)
473
475 data = []
476
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
485 """Defines a table's style
486 """
487
489
490 self._table = table
491 self.size = dict([(col_name, '1*') for col_name in table.col_names])
492
493
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
500
501 self.units = dict([(col_name, '') for col_name in table.col_names])
502 self.units['__row_column__'] = ''
503
504
506 """sets the size of the specified col_id to value
507 """
508 self.size[col_id] = value
509
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
524 """sets the alignment of the specified col_id to value
525 """
526 self.alignment[col_id] = value
527
528
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
543 """sets the unit of the specified col_id to value
544 """
545 self.units[col_id] = value
546
547
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
564 """Returns the size of the specified col_id
565 """
566 return self.size[col_id]
567
568
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
583 """Returns the alignment of the specified col_id
584 """
585 return self.alignment[col_id]
586
587
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
602 """Returns the unit of the specified col_id
603 """
604 return self.units[col_id]
605
606
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
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
636 rules = rules or []
637 self.rules = []
638 self.instructions = []
639 for rule in rules:
640 self.add_rule(rule)
641
642
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
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
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
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
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
720 """Defines a simple text renderer
721 """
722
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
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
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
803 """Defines how to render a cell for a docboook table
804 """
805
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
827 return "<entry>%s</entry>\n" % cell_content
828
829
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
843 """sets the table's associated style
844 """
845 self.style = style
846
847
849 """sets the way to render cell
850 """
851 self.renderer = renderer
852
853
855 """Updates writer's properties (for cell rendering)
856 """
857 self.properties.update(properties)
858
859
861 """Writes the table
862 """
863 raise NotImplementedError("write_table must be implemented !")
864
865
866
868 """Defines an implementation of TableWriter to write a table in Docbook
869 """
870
872 """Writes col headers
873 """
874
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
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
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
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