Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Provides a simple table class. A SimpleTable is essentially 

3a list of lists plus some formatting functionality. 

4 

5Dependencies: the Python 2.5+ standard library. 

6 

7Installation: just copy this module into your working directory (or 

8 anywhere in your pythonpath). 

9 

10Basic use:: 

11 

12 mydata = [[11,12],[21,22]] # data MUST be 2-dimensional 

13 myheaders = [ "Column 1", "Column 2" ] 

14 mystubs = [ "Row 1", "Row 2" ] 

15 tbl = SimpleTable(mydata, myheaders, mystubs, title="Title") 

16 print( tbl ) 

17 print( tbl.as_csv() ) 

18 

19A SimpleTable is inherently (but not rigidly) rectangular. 

20You should create it from a *rectangular* (2d!) iterable of data. 

21Each item in your rectangular iterable will become the data 

22of a single Cell. In principle, items can be any object, 

23not just numbers and strings. However, default conversion 

24during table production is by simple string interpolation. 

25(So you cannot have a tuple as a data item *and* rely on 

26the default conversion.) 

27 

28A SimpleTable allows only one column (the first) of stubs at 

29initilization, concatenation of tables allows you to produce tables 

30with interior stubs. (You can also assign the datatype 'stub' to the 

31cells in any column, or use ``insert_stubs``.) A SimpleTable can be 

32concatenated with another SimpleTable or extended by another 

33SimpleTable. :: 

34 

35 table1.extend_right(table2) 

36 table1.extend(table2) 

37 

38 

39A SimpleTable can be initialized with `datatypes`: a list of ints that 

40provide indexes into `data_fmts` and `data_aligns`. Each data cell is 

41assigned a datatype, which will control formatting. If you do not 

42specify the `datatypes` list, it will be set to ``range(ncols)`` where 

43`ncols` is the number of columns in the data. (I.e., cells in a 

44column have their own datatype.) This means that you can just specify 

45`data_fmts` without bothering to provide a `datatypes` list. If 

46``len(datatypes)<ncols`` then datatype assignment will cycle across a 

47row. E.g., if you provide 10 columns of data with ``datatypes=[0,1]`` 

48then you will have 5 columns of datatype 0 and 5 columns of datatype 

491, alternating. Correspoding to this specification, you should provide 

50a list of two ``data_fmts`` and a list of two ``data_aligns``. 

51 

52Cells can be assigned labels as their `datatype` attribute. 

53You can then provide a format for that lable. 

54Us the SimpleTable's `label_cells` method to do this. :: 

55 

56 def mylabeller(cell): 

57 if cell.data is np.nan: 

58 return 'missing' 

59 

60 mytable.label_cells(mylabeller) 

61 print(mytable.as_text(missing='-')) 

62 

63 

64Potential problems for Python 3 

65------------------------------- 

66 

67- Calls ``next`` instead of ``__next__``. 

68 The 2to3 tool should handle that no problem. 

69 (We will switch to the `next` function if 2.5 support is ever dropped.) 

70- Let me know if you find other problems. 

71 

72:contact: alan dot isaac at gmail dot com 

73:requires: Python 2.5.1+ 

74:note: current version 

75:note: HTML data format currently specifies tags 

76:todo: support a bit more of http://www.oasis-open.org/specs/tr9503.html 

77:todo: add labels2formatters method, that associates a cell formatter with a 

78 datatype 

79:todo: add colspan support to Cell 

80:since: 2008-12-21 

81:change: 2010-05-02 eliminate newlines that came before and after table 

82:change: 2010-05-06 add `label_cells` to `SimpleTable` 

83""" 

84 

85from statsmodels.compat.python import lmap, lrange, iteritems 

86 

87from itertools import cycle, zip_longest 

88import csv 

89 

90 

91def csv2st(csvfile, headers=False, stubs=False, title=None): 

92 """Return SimpleTable instance, 

93 created from the data in `csvfile`, 

94 which is in comma separated values format. 

95 The first row may contain headers: set headers=True. 

96 The first column may contain stubs: set stubs=True. 

97 Can also supply headers and stubs as tuples of strings. 

98 """ 

99 rows = list() 

100 with open(csvfile, 'r') as fh: 

101 reader = csv.reader(fh) 

102 if headers is True: 

103 headers = next(reader) 

104 elif headers is False: 

105 headers = () 

106 if stubs is True: 

107 stubs = list() 

108 for row in reader: 

109 if row: 

110 stubs.append(row[0]) 

111 rows.append(row[1:]) 

112 else: # no stubs, or stubs provided 

113 for row in reader: 

114 if row: 

115 rows.append(row) 

116 if stubs is False: 

117 stubs = () 

118 nrows = len(rows) 

119 ncols = len(rows[0]) 

120 if any(nrows != ncols for row in rows): 

121 raise IOError('All rows of CSV file must have same length.') 

122 return SimpleTable(data=rows, headers=headers, stubs=stubs) 

123 

124 

125class SimpleTable(list): 

126 """Produce a simple ASCII, CSV, HTML, or LaTeX table from a 

127 *rectangular* (2d!) array of data, not necessarily numerical. 

128 Directly supports at most one header row, 

129 which should be the length of data[0]. 

130 Directly supports at most one stubs column, 

131 which must be the length of data. 

132 (But see `insert_stubs` method.) 

133 See globals `default_txt_fmt`, `default_csv_fmt`, `default_html_fmt`, 

134 and `default_latex_fmt` for formatting options. 

135 

136 Sample uses:: 

137 

138 mydata = [[11,12],[21,22]] # data MUST be 2-dimensional 

139 myheaders = [ "Column 1", "Column 2" ] 

140 mystubs = [ "Row 1", "Row 2" ] 

141 tbl = text.SimpleTable(mydata, myheaders, mystubs, title="Title") 

142 print( tbl ) 

143 print( tbl.as_html() ) 

144 # set column specific data formatting 

145 tbl = text.SimpleTable(mydata, myheaders, mystubs, 

146 data_fmts=["%3.2f","%d"]) 

147 print( tbl.as_csv() ) 

148 with open('c:/temp/temp.tex','w') as fh: 

149 fh.write( tbl.as_latex_tabular() ) 

150 """ 

151 def __init__(self, data, headers=None, stubs=None, title='', 

152 datatypes=None, csv_fmt=None, txt_fmt=None, ltx_fmt=None, 

153 html_fmt=None, celltype=None, rowtype=None, **fmt_dict): 

154 """ 

155 Parameters 

156 ---------- 

157 data : list of lists or 2d array (not matrix!) 

158 R rows by K columns of table elements 

159 headers : list (or tuple) of str 

160 sequence of K strings, one per header 

161 stubs : list (or tuple) of str 

162 sequence of R strings, one per stub 

163 title : str 

164 title of the table 

165 datatypes : list of int 

166 indexes to `data_fmts` 

167 txt_fmt : dict 

168 text formatting options 

169 ltx_fmt : dict 

170 latex formatting options 

171 csv_fmt : dict 

172 csv formatting options 

173 hmtl_fmt : dict 

174 hmtl formatting options 

175 celltype : class 

176 the cell class for the table (default: Cell) 

177 rowtype : class 

178 the row class for the table (default: Row) 

179 fmt_dict : dict 

180 general formatting options 

181 """ 

182 self.title = title 

183 self._datatypes = datatypes or lrange(len(data[0])) 

184 # start with default formatting 

185 self._txt_fmt = default_txt_fmt.copy() 

186 self._latex_fmt = default_latex_fmt.copy() 

187 self._csv_fmt = default_csv_fmt.copy() 

188 self._html_fmt = default_html_fmt.copy() 

189 # substitute any general user specified formatting 

190 # :note: these will be overridden by output specific arguments 

191 self._csv_fmt.update(fmt_dict) 

192 self._txt_fmt.update(fmt_dict) 

193 self._latex_fmt.update(fmt_dict) 

194 self._html_fmt.update(fmt_dict) 

195 # substitute any output-type specific formatting 

196 self._csv_fmt.update(csv_fmt or dict()) 

197 self._txt_fmt.update(txt_fmt or dict()) 

198 self._latex_fmt.update(ltx_fmt or dict()) 

199 self._html_fmt.update(html_fmt or dict()) 

200 self.output_formats = dict( 

201 txt=self._txt_fmt, 

202 csv=self._csv_fmt, 

203 html=self._html_fmt, 

204 latex=self._latex_fmt 

205 ) 

206 self._Cell = celltype or Cell 

207 self._Row = rowtype or Row 

208 rows = self._data2rows(data) # a list of Row instances 

209 list.__init__(self, rows) 

210 self._add_headers_stubs(headers, stubs) 

211 self._colwidths = dict() 

212 

213 def __str__(self): 

214 return self.as_text() 

215 

216 def __repr__(self): 

217 return str(type(self)) 

218 

219 def _repr_html_(self, **fmt_dict): 

220 return self.as_html(**fmt_dict) 

221 

222 def _add_headers_stubs(self, headers, stubs): 

223 """Return None. Adds headers and stubs to table, 

224 if these were provided at initialization. 

225 Parameters 

226 ---------- 

227 headers : list[str] 

228 K strings, where K is number of columns 

229 stubs : list[str] 

230 R strings, where R is number of non-header rows 

231 

232 :note: a header row does not receive a stub! 

233 """ 

234 if headers: 

235 self.insert_header_row(0, headers, dec_below='header_dec_below') 

236 if stubs: 

237 self.insert_stubs(0, stubs) 

238 

239 def insert(self, idx, row, datatype=None): 

240 """Return None. Insert a row into a table. 

241 """ 

242 if datatype is None: 

243 try: 

244 datatype = row.datatype 

245 except AttributeError: 

246 pass 

247 row = self._Row(row, datatype=datatype, table=self) 

248 list.insert(self, idx, row) 

249 

250 def insert_header_row(self, rownum, headers, dec_below='header_dec_below'): 

251 """Return None. Insert a row of headers, 

252 where ``headers`` is a sequence of strings. 

253 (The strings may contain newlines, to indicated multiline headers.) 

254 """ 

255 header_rows = [header.split('\n') for header in headers] 

256 # rows in reverse order 

257 rows = list(zip_longest(*header_rows, **dict(fillvalue=''))) 

258 rows.reverse() 

259 for i, row in enumerate(rows): 

260 self.insert(rownum, row, datatype='header') 

261 if i == 0: 

262 self[rownum].dec_below = dec_below 

263 else: 

264 self[rownum].dec_below = None 

265 

266 def insert_stubs(self, loc, stubs): 

267 """Return None. Insert column of stubs at column `loc`. 

268 If there is a header row, it gets an empty cell. 

269 So ``len(stubs)`` should equal the number of non-header rows. 

270 """ 

271 _Cell = self._Cell 

272 stubs = iter(stubs) 

273 for row in self: 

274 if row.datatype == 'header': 

275 empty_cell = _Cell('', datatype='empty') 

276 row.insert(loc, empty_cell) 

277 else: 

278 try: 

279 row.insert_stub(loc, next(stubs)) 

280 except StopIteration: 

281 raise ValueError('length of stubs must match table length') 

282 

283 def _data2rows(self, raw_data): 

284 """Return list of Row, 

285 the raw data as rows of cells. 

286 """ 

287 

288 _Cell = self._Cell 

289 _Row = self._Row 

290 rows = [] 

291 for datarow in raw_data: 

292 dtypes = cycle(self._datatypes) 

293 newrow = _Row(datarow, datatype='data', table=self, celltype=_Cell) 

294 for cell in newrow: 

295 cell.datatype = next(dtypes) 

296 cell.row = newrow # a cell knows its row 

297 rows.append(newrow) 

298 

299 return rows 

300 

301 def pad(self, s, width, align): 

302 """DEPRECATED: just use the pad function""" 

303 return pad(s, width, align) 

304 

305 def _get_colwidths(self, output_format, **fmt_dict): 

306 """Return list, the calculated widths of each column.""" 

307 output_format = get_output_format(output_format) 

308 fmt = self.output_formats[output_format].copy() 

309 fmt.update(fmt_dict) 

310 ncols = max(len(row) for row in self) 

311 request = fmt.get('colwidths') 

312 if request == 0: # assume no extra space desired (e.g, CSV) 

313 return [0] * ncols 

314 elif request is None: # assume no extra space desired (e.g, CSV) 

315 request = [0] * ncols 

316 elif isinstance(request, int): 

317 request = [request] * ncols 

318 elif len(request) < ncols: 

319 request = [request[i % len(request)] for i in range(ncols)] 

320 min_widths = [] 

321 for col in zip(*self): 

322 maxwidth = max(len(c.format(0, output_format, **fmt)) for c in col) 

323 min_widths.append(maxwidth) 

324 result = lmap(max, min_widths, request) 

325 return result 

326 

327 def get_colwidths(self, output_format, **fmt_dict): 

328 """Return list, the widths of each column.""" 

329 call_args = [output_format] 

330 for k, v in sorted(iteritems(fmt_dict)): 

331 if isinstance(v, list): 

332 call_args.append((k, tuple(v))) 

333 elif isinstance(v, dict): 

334 call_args.append((k, tuple(sorted(iteritems(v))))) 

335 else: 

336 call_args.append((k, v)) 

337 key = tuple(call_args) 

338 try: 

339 return self._colwidths[key] 

340 except KeyError: 

341 self._colwidths[key] = self._get_colwidths(output_format, 

342 **fmt_dict) 

343 return self._colwidths[key] 

344 

345 def _get_fmt(self, output_format, **fmt_dict): 

346 """Return dict, the formatting options. 

347 """ 

348 output_format = get_output_format(output_format) 

349 # first get the default formatting 

350 try: 

351 fmt = self.output_formats[output_format].copy() 

352 except KeyError: 

353 raise ValueError('Unknown format: %s' % output_format) 

354 # then, add formatting specific to this call 

355 fmt.update(fmt_dict) 

356 return fmt 

357 

358 def as_csv(self, **fmt_dict): 

359 """Return string, the table in CSV format. 

360 Currently only supports comma separator.""" 

361 # fetch the format, which may just be default_csv_format 

362 fmt = self._get_fmt('csv', **fmt_dict) 

363 return self.as_text(**fmt) 

364 

365 def as_text(self, **fmt_dict): 

366 """Return string, the table as text.""" 

367 # fetch the text format, override with fmt_dict 

368 fmt = self._get_fmt('txt', **fmt_dict) 

369 # get rows formatted as strings 

370 formatted_rows = [row.as_string('text', **fmt) for row in self] 

371 rowlen = len(formatted_rows[-1]) # do not use header row 

372 

373 # place decoration above the table body, if desired 

374 table_dec_above = fmt.get('table_dec_above', '=') 

375 if table_dec_above: 

376 formatted_rows.insert(0, table_dec_above * rowlen) 

377 # next place a title at the very top, if desired 

378 # :note: user can include a newlines at end of title if desired 

379 title = self.title 

380 if title: 

381 title = pad(self.title, rowlen, fmt.get('title_align', 'c')) 

382 formatted_rows.insert(0, title) 

383 # add decoration below the table, if desired 

384 table_dec_below = fmt.get('table_dec_below', '-') 

385 if table_dec_below: 

386 formatted_rows.append(table_dec_below * rowlen) 

387 return '\n'.join(formatted_rows) 

388 

389 def as_html(self, **fmt_dict): 

390 """Return string. 

391 This is the default formatter for HTML tables. 

392 An HTML table formatter must accept as arguments 

393 a table and a format dictionary. 

394 """ 

395 # fetch the text format, override with fmt_dict 

396 fmt = self._get_fmt('html', **fmt_dict) 

397 formatted_rows = ['<table class="simpletable">'] 

398 if self.title: 

399 title = '<caption>%s</caption>' % self.title 

400 formatted_rows.append(title) 

401 formatted_rows.extend(row.as_string('html', **fmt) for row in self) 

402 formatted_rows.append('</table>') 

403 return '\n'.join(formatted_rows) 

404 

405 def as_latex_tabular(self, center=True, **fmt_dict): 

406 '''Return string, the table as a LaTeX tabular environment. 

407 Note: will require the booktabs package.''' 

408 # fetch the text format, override with fmt_dict 

409 fmt = self._get_fmt('latex', **fmt_dict) 

410 

411 formatted_rows = [] 

412 if center: 

413 formatted_rows.append(r'\begin{center}') 

414 

415 table_dec_above = fmt['table_dec_above'] or '' 

416 table_dec_below = fmt['table_dec_below'] or '' 

417 

418 prev_aligns = None 

419 last = None 

420 for row in self + [last]: 

421 if row == last: 

422 aligns = None 

423 else: 

424 aligns = row.get_aligns('latex', **fmt) 

425 

426 if aligns != prev_aligns: 

427 # When the number/type of columns changes... 

428 if prev_aligns: 

429 # ... if there is a tabular to close, close it... 

430 formatted_rows.append(table_dec_below) 

431 formatted_rows.append(r'\end{tabular}') 

432 if aligns: 

433 # ... and if there are more lines, open a new one: 

434 formatted_rows.append(r'\begin{tabular}{%s}' % aligns) 

435 if not prev_aligns: 

436 # (with a nice line if it's the top of the whole table) 

437 formatted_rows.append(table_dec_above) 

438 if row != last: 

439 formatted_rows.append( 

440 row.as_string(output_format='latex', **fmt)) 

441 prev_aligns = aligns 

442 # tabular does not support caption, but make it available for 

443 # figure environment 

444 if self.title: 

445 title = r'%%\caption{%s}' % self.title 

446 formatted_rows.append(title) 

447 if center: 

448 formatted_rows.append(r'\end{center}') 

449 

450 # Replace $$ due to bug in GH 5444 

451 return '\n'.join(formatted_rows).replace('$$', ' ') 

452 

453 def extend_right(self, table): 

454 """Return None. 

455 Extend each row of `self` with corresponding row of `table`. 

456 Does **not** import formatting from ``table``. 

457 This generally makes sense only if the two tables have 

458 the same number of rows, but that is not enforced. 

459 :note: To extend append a table below, just use `extend`, 

460 which is the ordinary list method. This generally makes sense 

461 only if the two tables have the same number of columns, 

462 but that is not enforced. 

463 """ 

464 for row1, row2 in zip(self, table): 

465 row1.extend(row2) 

466 

467 def label_cells(self, func): 

468 """Return None. Labels cells based on `func`. 

469 If ``func(cell) is None`` then its datatype is 

470 not changed; otherwise it is set to ``func(cell)``. 

471 """ 

472 for row in self: 

473 for cell in row: 

474 label = func(cell) 

475 if label is not None: 

476 cell.datatype = label 

477 

478 @property 

479 def data(self): 

480 return [row.data for row in self] 

481 

482 

483def pad(s, width, align): 

484 """Return string padded with spaces, 

485 based on alignment parameter.""" 

486 if align == 'l': 

487 s = s.ljust(width) 

488 elif align == 'r': 

489 s = s.rjust(width) 

490 else: 

491 s = s.center(width) 

492 return s 

493 

494 

495class Row(list): 

496 """Provides a table row as a list of cells. 

497 A row can belong to a SimpleTable, but does not have to. 

498 """ 

499 def __init__(self, seq, datatype='data', table=None, celltype=None, 

500 dec_below='row_dec_below', **fmt_dict): 

501 """ 

502 Parameters 

503 ---------- 

504 seq : sequence of data or cells 

505 table : SimpleTable 

506 datatype : str ('data' or 'header') 

507 dec_below : str 

508 (e.g., 'header_dec_below' or 'row_dec_below') 

509 decoration tag, identifies the decoration to go below the row. 

510 (Decoration is repeated as needed for text formats.) 

511 """ 

512 self.datatype = datatype 

513 self.table = table 

514 if celltype is None: 

515 if table is None: 

516 celltype = Cell 

517 else: 

518 celltype = table._Cell 

519 self._Cell = celltype 

520 self._fmt = fmt_dict 

521 self.special_fmts = dict() # special formatting for any output format 

522 self.dec_below = dec_below 

523 list.__init__(self, (celltype(cell, row=self) for cell in seq)) 

524 

525 def add_format(self, output_format, **fmt_dict): 

526 """ 

527 Return None. Adds row-instance specific formatting 

528 for the specified output format. 

529 Example: myrow.add_format('txt', row_dec_below='+-') 

530 """ 

531 output_format = get_output_format(output_format) 

532 if output_format not in self.special_fmts: 

533 self.special_fmts[output_format] = dict() 

534 self.special_fmts[output_format].update(fmt_dict) 

535 

536 def insert_stub(self, loc, stub): 

537 """Return None. Inserts a stub cell 

538 in the row at `loc`. 

539 """ 

540 _Cell = self._Cell 

541 if not isinstance(stub, _Cell): 

542 stub = stub 

543 stub = _Cell(stub, datatype='stub', row=self) 

544 self.insert(loc, stub) 

545 

546 def _get_fmt(self, output_format, **fmt_dict): 

547 """Return dict, the formatting options. 

548 """ 

549 output_format = get_output_format(output_format) 

550 # first get the default formatting 

551 try: 

552 fmt = default_fmts[output_format].copy() 

553 except KeyError: 

554 raise ValueError('Unknown format: %s' % output_format) 

555 # second get table specific formatting (if possible) 

556 try: 

557 fmt.update(self.table.output_formats[output_format]) 

558 except AttributeError: 

559 pass 

560 # finally, add formatting for this row and this call 

561 fmt.update(self._fmt) 

562 fmt.update(fmt_dict) 

563 special_fmt = self.special_fmts.get(output_format, None) 

564 if special_fmt is not None: 

565 fmt.update(special_fmt) 

566 return fmt 

567 

568 def get_aligns(self, output_format, **fmt_dict): 

569 """Return string, sequence of column alignments. 

570 Ensure comformable data_aligns in `fmt_dict`.""" 

571 fmt = self._get_fmt(output_format, **fmt_dict) 

572 return ''.join(cell.alignment(output_format, **fmt) for cell in self) 

573 

574 def as_string(self, output_format='txt', **fmt_dict): 

575 """Return string: the formatted row. 

576 This is the default formatter for rows. 

577 Override this to get different formatting. 

578 A row formatter must accept as arguments 

579 a row (self) and an output format, 

580 one of ('html', 'txt', 'csv', 'latex'). 

581 """ 

582 fmt = self._get_fmt(output_format, **fmt_dict) 

583 

584 # get column widths 

585 try: 

586 colwidths = self.table.get_colwidths(output_format, **fmt) 

587 except AttributeError: 

588 colwidths = fmt.get('colwidths') 

589 if colwidths is None: 

590 colwidths = (0,) * len(self) 

591 

592 colsep = fmt['colsep'] 

593 row_pre = fmt.get('row_pre', '') 

594 row_post = fmt.get('row_post', '') 

595 formatted_cells = [] 

596 for cell, width in zip(self, colwidths): 

597 content = cell.format(width, output_format=output_format, **fmt) 

598 formatted_cells.append(content) 

599 formatted_row = row_pre + colsep.join(formatted_cells) + row_post 

600 formatted_row = self._decorate_below(formatted_row, output_format, 

601 **fmt) 

602 return formatted_row 

603 

604 def _decorate_below(self, row_as_string, output_format, **fmt_dict): 

605 """This really only makes sense for the text and latex output formats. 

606 """ 

607 dec_below = fmt_dict.get(self.dec_below, None) 

608 if dec_below is None: 

609 result = row_as_string 

610 else: 

611 output_format = get_output_format(output_format) 

612 if output_format == 'txt': 

613 row0len = len(row_as_string) 

614 dec_len = len(dec_below) 

615 repeat, addon = divmod(row0len, dec_len) 

616 result = row_as_string + "\n" + (dec_below * repeat + 

617 dec_below[:addon]) 

618 elif output_format == 'latex': 

619 result = row_as_string + "\n" + dec_below 

620 else: 

621 raise ValueError("I cannot decorate a %s header." % 

622 output_format) 

623 return result 

624 

625 @property 

626 def data(self): 

627 return [cell.data for cell in self] 

628 

629 

630class Cell(object): 

631 """Provides a table cell. 

632 A cell can belong to a Row, but does not have to. 

633 """ 

634 def __init__(self, data='', datatype=None, row=None, **fmt_dict): 

635 if isinstance(data, Cell): 

636 # might have passed a Cell instance 

637 self.data = data.data 

638 self._datatype = data.datatype 

639 self._fmt = data._fmt 

640 else: 

641 self.data = data 

642 self._datatype = datatype 

643 self._fmt = dict() 

644 self._fmt.update(fmt_dict) 

645 self.row = row 

646 

647 def __str__(self): 

648 return '%s' % self.data 

649 

650 def _get_fmt(self, output_format, **fmt_dict): 

651 """Return dict, the formatting options. 

652 """ 

653 output_format = get_output_format(output_format) 

654 # first get the default formatting 

655 try: 

656 fmt = default_fmts[output_format].copy() 

657 except KeyError: 

658 raise ValueError('Unknown format: %s' % output_format) 

659 # then get any table specific formtting 

660 try: 

661 fmt.update(self.row.table.output_formats[output_format]) 

662 except AttributeError: 

663 pass 

664 # then get any row specific formtting 

665 try: 

666 fmt.update(self.row._fmt) 

667 except AttributeError: 

668 pass 

669 # finally add formatting for this instance and call 

670 fmt.update(self._fmt) 

671 fmt.update(fmt_dict) 

672 return fmt 

673 

674 def alignment(self, output_format, **fmt_dict): 

675 fmt = self._get_fmt(output_format, **fmt_dict) 

676 datatype = self.datatype 

677 data_aligns = fmt.get('data_aligns', 'c') 

678 if isinstance(datatype, int): 

679 align = data_aligns[datatype % len(data_aligns)] 

680 elif datatype == 'stub': 

681 # still support deprecated `stubs_align` 

682 align = fmt.get('stubs_align') or fmt.get('stub_align', 'l') 

683 elif datatype in fmt: 

684 label_align = '%s_align' % datatype 

685 align = fmt.get(label_align, 'c') 

686 else: 

687 raise ValueError('Unknown cell datatype: %s' % datatype) 

688 return align 

689 

690 @staticmethod 

691 def _latex_escape(data, fmt, output_format): 

692 if output_format != 'latex': 

693 return data 

694 if "replacements" in fmt: 

695 if isinstance(data, str): 

696 for repl in sorted(fmt["replacements"]): 

697 data = data.replace(repl, fmt["replacements"][repl]) 

698 return data 

699 

700 def format(self, width, output_format='txt', **fmt_dict): 

701 """Return string. 

702 This is the default formatter for cells. 

703 Override this to get different formating. 

704 A cell formatter must accept as arguments 

705 a cell (self) and an output format, 

706 one of ('html', 'txt', 'csv', 'latex'). 

707 It will generally respond to the datatype, 

708 one of (int, 'header', 'stub'). 

709 """ 

710 fmt = self._get_fmt(output_format, **fmt_dict) 

711 

712 data = self.data 

713 datatype = self.datatype 

714 data_fmts = fmt.get('data_fmts') 

715 if data_fmts is None: 

716 # chk allow for deprecated use of data_fmt 

717 data_fmt = fmt.get('data_fmt') 

718 if data_fmt is None: 

719 data_fmt = '%s' 

720 data_fmts = [data_fmt] 

721 if isinstance(datatype, int): 

722 datatype = datatype % len(data_fmts) # constrain to indexes 

723 content = data_fmts[datatype] % (data,) 

724 if datatype == 0: 

725 content = self._latex_escape(content, fmt, output_format) 

726 elif datatype in fmt: 

727 data = self._latex_escape(data, fmt, output_format) 

728 

729 dfmt = fmt.get(datatype) 

730 try: 

731 content = dfmt % (data,) 

732 except TypeError: # dfmt is not a substitution string 

733 content = dfmt 

734 else: 

735 raise ValueError('Unknown cell datatype: %s' % datatype) 

736 align = self.alignment(output_format, **fmt) 

737 return pad(content, width, align) 

738 

739 def get_datatype(self): 

740 if self._datatype is None: 

741 dtype = self.row.datatype 

742 else: 

743 dtype = self._datatype 

744 return dtype 

745 

746 def set_datatype(self, val): 

747 # TODO: add checking 

748 self._datatype = val 

749 datatype = property(get_datatype, set_datatype) 

750 

751 

752# begin: default formats for SimpleTable 

753""" Some formatting suggestions: 

754 

755- if you want rows to have no extra spacing, 

756 set colwidths=0 and colsep=''. 

757 (Naturally the columns will not align.) 

758- if you want rows to have minimal extra spacing, 

759 set colwidths=1. The columns will align. 

760- to get consistent formatting, you should leave 

761 all field width handling to SimpleTable: 

762 use 0 as the field width in data_fmts. E.g., :: 

763 

764 data_fmts = ["%#0.6g","%#0.6g","%#0.4g","%#0.4g"], 

765 colwidths = 14, 

766 data_aligns = "r", 

767""" 

768default_txt_fmt = dict( 

769 fmt='txt', 

770 # basic table formatting 

771 table_dec_above='=', 

772 table_dec_below='-', 

773 title_align='c', 

774 # basic row formatting 

775 row_pre='', 

776 row_post='', 

777 header_dec_below='-', 

778 row_dec_below=None, 

779 colwidths=None, 

780 colsep=' ', 

781 data_aligns="r", # GH 1477 

782 # data formats 

783 # data_fmt="%s", #deprecated; use data_fmts 

784 data_fmts=["%s"], 

785 # labeled alignments 

786 # stubs_align='l', #deprecated; use data_fmts 

787 stub_align='l', 

788 header_align='c', 

789 # labeled formats 

790 header_fmt='%s', # deprecated; just use 'header' 

791 stub_fmt='%s', # deprecated; just use 'stub' 

792 header='%s', 

793 stub='%s', 

794 empty_cell='', # deprecated; just use 'empty' 

795 empty='', 

796 missing='--', 

797) 

798 

799default_csv_fmt = dict( 

800 fmt='csv', 

801 table_dec_above=None, # '', 

802 table_dec_below=None, # '', 

803 # basic row formatting 

804 row_pre='', 

805 row_post='', 

806 header_dec_below=None, # '', 

807 row_dec_below=None, 

808 title_align='', 

809 data_aligns="l", 

810 colwidths=None, 

811 colsep=',', 

812 # data formats 

813 data_fmt='%s', # deprecated; use data_fmts 

814 data_fmts=['%s'], 

815 # labeled alignments 

816 # stubs_align='l', # deprecated; use data_fmts 

817 stub_align="l", 

818 header_align='c', 

819 # labeled formats 

820 header_fmt='"%s"', # deprecated; just use 'header' 

821 stub_fmt='"%s"', # deprecated; just use 'stub' 

822 empty_cell='', # deprecated; just use 'empty' 

823 header='%s', 

824 stub='%s', 

825 empty='', 

826 missing='--', 

827) 

828 

829default_html_fmt = dict( 

830 # basic table formatting 

831 table_dec_above=None, 

832 table_dec_below=None, 

833 header_dec_below=None, 

834 row_dec_below=None, 

835 title_align='c', 

836 # basic row formatting 

837 colwidths=None, 

838 colsep=' ', 

839 row_pre='<tr>\n ', 

840 row_post='\n</tr>', 

841 data_aligns="c", 

842 # data formats 

843 data_fmts=['<td>%s</td>'], 

844 data_fmt="<td>%s</td>", # deprecated; use data_fmts 

845 # labeled alignments 

846 # stubs_align='l', #deprecated; use data_fmts 

847 stub_align='l', 

848 header_align='c', 

849 # labeled formats 

850 header_fmt='<th>%s</th>', # deprecated; just use `header` 

851 stub_fmt='<th>%s</th>', # deprecated; just use `stub` 

852 empty_cell='<td></td>', # deprecated; just use `empty` 

853 header='<th>%s</th>', 

854 stub='<th>%s</th>', 

855 empty='<td></td>', 

856 missing='<td>--</td>', 

857) 

858 

859default_latex_fmt = dict( 

860 fmt='ltx', 

861 # basic table formatting 

862 table_dec_above=r'\toprule', 

863 table_dec_below=r'\bottomrule', 

864 header_dec_below=r'\midrule', 

865 row_dec_below=None, 

866 strip_backslash=True, # NotImplemented 

867 # row formatting 

868 row_post=r' \\', 

869 data_aligns='c', 

870 colwidths=None, 

871 colsep=' & ', 

872 # data formats 

873 data_fmts=['%s'], 

874 data_fmt='%s', # deprecated; use data_fmts 

875 # labeled alignments 

876 # stubs_align='l', # deprecated; use data_fmts 

877 stub_align='l', 

878 header_align='c', 

879 empty_align='l', 

880 # labeled formats 

881 header_fmt=r'\textbf{%s}', # deprecated; just use 'header' 

882 stub_fmt=r'\textbf{%s}', # deprecated; just use 'stub' 

883 empty_cell='', # deprecated; just use 'empty' 

884 header=r'\textbf{%s}', 

885 stub=r'\textbf{%s}', 

886 empty='', 

887 missing='--', 

888 # replacements will be processed in lexicographical order 

889 replacements={"#": r"\#", 

890 "$": r"\$", 

891 "%": r"\%", 

892 "&": r"\&", 

893 ">": r"$>$", 

894 "_": r"\_", 

895 "|": r"$|$"} 

896) 

897 

898default_fmts = dict( 

899 html=default_html_fmt, 

900 txt=default_txt_fmt, 

901 latex=default_latex_fmt, 

902 csv=default_csv_fmt 

903) 

904output_format_translations = dict( 

905 htm='html', 

906 text='txt', 

907 ltx='latex' 

908) 

909 

910 

911def get_output_format(output_format): 

912 if output_format not in ('html', 'txt', 'latex', 'csv'): 

913 try: 

914 output_format = output_format_translations[output_format] 

915 except KeyError: 

916 raise ValueError('unknown output format %s' % output_format) 

917 return output_format