def write_source_line(w, line, nchar=0): part1 = xml_escape(line[:nchar].decode('utf-8')) char = xml_escape(line[nchar:nchar+1].decode('utf-8')) part2 = xml_escape(line[nchar+1:].decode('utf-8')) w.write(' ') w.write(part1) w.write('<span class="highlight">{}</span>'.format(char)) w.write(part2) w.write('\n\n')
def write_source_line(w, line, nchar=0): part1 = xml_escape(line[:nchar].decode('utf-8')) char = xml_escape(line[nchar:nchar+1].decode('utf-8')) part2 = xml_escape(line[nchar+1:].decode('utf-8')) w.write(' ') w.write(part1) w.write(f'<span class="highlight">{char}</span>') w.write(part2) w.write('\n\n')
def test_escape_xml(): s = writer.xml_escape('This & That') assert type(s) == str assert s == 'This & That' s = writer.xml_escape(1) assert type(s) == str assert s == '1' s = writer.xml_escape(b'This & That') assert type(s) == bytes assert s == b'This & That'
def write_votlint_warning(w, line, xml_lines): match = re.search(r"(WARNING|ERROR|INFO) \(l.(?P<line>[0-9]+), c.(?P<column>[0-9]+)\): (?P<rest>.*)", line) if match: w.write('Line {:d}: {}\n'.format( int(match.group('line')), xml_escape(match.group('rest')))) write_source_line( w, xml_lines[int(match.group('line')) - 1], int(match.group('column')) - 1) else: w.data(line) w.data('\n')
def write_warning(w, line, xml_lines): warning = exceptions.parse_vowarning(line) if not warning['is_something']: w.data(line) else: w.write(f"Line {warning['nline']:d}: ") if warning['warning']: w.write('<a href="{}/{}">{}</a>: '.format( online_docs_root, warning['doc_url'], warning['warning'])) msg = warning['message'] if not isinstance(warning['message'], str): msg = msg.decode('utf-8') w.write(xml_escape(msg)) w.write('\n') if 1 <= warning['nline'] < len(xml_lines): write_source_line(w, xml_lines[warning['nline'] - 1], warning['nchar'])
def write_warning(w, line, xml_lines): warning = exceptions.parse_vowarning(line) if not warning['is_something']: w.data(line) else: w.write('Line {:d}: '.format(warning['nline'])) if warning['warning']: w.write('<a href="{}/{}">{}</a>: '.format( online_docs_root, warning['doc_url'], warning['warning'])) msg = warning['message'] if not isinstance(warning['message'], str): msg = msg.decode('utf-8') w.write(xml_escape(msg)) w.write('\n') if 1 <= warning['nline'] < len(xml_lines): write_source_line(w, xml_lines[warning['nline'] - 1], warning['nchar'])
def _pformat_table(self, table, max_lines=None, max_width=None, show_name=True, show_unit=None, show_dtype=False, html=False, tableid=None, tableclass=None, align=None): """Return a list of lines for the formatted string representation of the table. Parameters ---------- max_lines : int or None Maximum number of rows to output max_width : int or None Maximum character width of output show_name : bool Include a header row for column names. Default is True. show_unit : bool Include a header row for unit. Default is to show a row for units only if one or more columns has a defined value for the unit. show_dtype : bool Include a header row for column dtypes. Default is False. html : bool Format the output as an HTML table. Default is False. tableid : str or None An ID tag for the table; only used if html is set. Default is "table{id}", where id is the unique integer id of the table object, id(table) tableclass : str or list of str or `None` CSS classes for the table; only used if html is set. Default is none align : str or list or tuple Left/right alignment of columns. Default is '>' (right) for all columns. Other allowed values are '<', '^', and '0=' for left, centered, and 0-padded, respectively. A list of strings can be provided for alignment of tables with multiple columns. Returns ------- rows : list Formatted table as a list of strings outs : dict Dict which is used to pass back additional values defined within the iterator. """ # "Print" all the values into temporary lists by column for subsequent # use and to determine the width max_lines, max_width = self._get_pprint_size(max_lines, max_width) cols = [] if show_unit is None: show_unit = any(col.info.unit for col in table.columns.values()) # Coerce align into a correctly-sized list of alignments (if possible) n_cols = len(table.columns) if align is None or isinstance(align, str): align = [align] * n_cols elif isinstance(align, (list, tuple)): if len(align) != n_cols: raise ValueError('got {} alignment values instead of ' 'the number of columns ({})' .format(len(align), n_cols)) else: raise TypeError('align keyword must be str or list or tuple (got {})' .format(type(align))) for align_, col in zip(align, table.columns.values()): lines, outs = self._pformat_col(col, max_lines, show_name=show_name, show_unit=show_unit, show_dtype=show_dtype, align=align_) if outs['show_length']: lines = lines[:-1] cols.append(lines) if not cols: return ['<No columns>'], {'show_length': False} # Use the values for the last column since they are all the same n_header = outs['n_header'] n_rows = len(cols[0]) def outwidth(cols): return sum(len(c[0]) for c in cols) + len(cols) - 1 dots_col = ['...'] * n_rows middle = len(cols) // 2 while outwidth(cols) > max_width: if len(cols) == 1: break if len(cols) == 2: cols[1] = dots_col break if cols[middle] is dots_col: cols.pop(middle) middle = len(cols) // 2 cols[middle] = dots_col # Now "print" the (already-stringified) column values into a # row-oriented list. rows = [] if html: from astropy.utils.xml.writer import xml_escape if tableid is None: tableid = 'table{id}'.format(id=id(table)) if tableclass is not None: if isinstance(tableclass, list): tableclass = ' '.join(tableclass) rows.append('<table id="{tid}" class="{tcls}">'.format( tid=tableid, tcls=tableclass)) else: rows.append(f'<table id="{tableid}">') for i in range(n_rows): # _pformat_col output has a header line '----' which is not needed here if i == n_header - 1: continue td = 'th' if i < n_header else 'td' vals = ('<{}>{}</{}>'.format(td, xml_escape(col[i].strip()), td) for col in cols) row = ('<tr>' + ''.join(vals) + '</tr>') if i < n_header: row = ('<thead>' + row + '</thead>') rows.append(row) rows.append('</table>') else: for i in range(n_rows): row = ' '.join(col[i] for col in cols) rows.append(row) return rows, outs
def _pformat_col(self, col, max_lines=None, show_name=True, show_unit=None, show_dtype=False, show_length=None, html=False, align=None): """Return a list of formatted string representation of column values. Parameters ---------- max_lines : int Maximum lines of output (header + data rows) show_name : bool Include column name. Default is True. show_unit : bool Include a header row for unit. Default is to show a row for units only if one or more columns has a defined value for the unit. show_dtype : bool Include column dtype. Default is False. show_length : bool Include column length at end. Default is to show this only if the column is not shown completely. html : bool Output column as HTML align : str Left/right alignment of columns. Default is '>' (right) for all columns. Other allowed values are '<', '^', and '0=' for left, centered, and 0-padded, respectively. Returns ------- lines : list List of lines with formatted column values outs : dict Dict which is used to pass back additional values defined within the iterator. """ if show_unit is None: show_unit = col.info.unit is not None outs = {} # Some values from _pformat_col_iter iterator that are needed here col_strs_iter = self._pformat_col_iter(col, max_lines, show_name=show_name, show_unit=show_unit, show_dtype=show_dtype, show_length=show_length, outs=outs) col_strs = list(col_strs_iter) if len(col_strs) > 0: col_width = max(len(x) for x in col_strs) if html: from astropy.utils.xml.writer import xml_escape n_header = outs['n_header'] for i, col_str in enumerate(col_strs): # _pformat_col output has a header line '----' which is not needed here if i == n_header - 1: continue td = 'th' if i < n_header else 'td' val = '<{}>{}</{}>'.format(td, xml_escape(col_str.strip()), td) row = ('<tr>' + val + '</tr>') if i < n_header: row = ('<thead>' + row + '</thead>') col_strs[i] = row if n_header > 0: # Get rid of '---' header line col_strs.pop(n_header - 1) col_strs.insert(0, '<table>') col_strs.append('</table>') # Now bring all the column string values to the same fixed width else: col_width = max(len(x) for x in col_strs) if col_strs else 1 # Center line header content and generate dashed headerline for i in outs['i_centers']: col_strs[i] = col_strs[i].center(col_width) if outs['i_dashes'] is not None: col_strs[outs['i_dashes']] = '-' * col_width # Format columns according to alignment. `align` arg has precedent, otherwise # use `col.format` if it starts as a legal alignment string. If neither applies # then right justify. re_fill_align = re.compile(r'(?P<fill>.?)(?P<align>[<^>=])') match = None if align: # If there is an align specified then it must match match = re_fill_align.match(align) if not match: raise ValueError("column align must be one of '<', '^', '>', or '='") elif isinstance(col.info.format, str): # col.info.format need not match, in which case rjust gets used match = re_fill_align.match(col.info.format) if match: fill_char = match.group('fill') align_char = match.group('align') if align_char == '=': if fill_char != '0': raise ValueError("fill character must be '0' for '=' align") fill_char = '' # str.zfill gets used which does not take fill char arg else: fill_char = '' align_char = '>' justify_methods = {'<': 'ljust', '^': 'center', '>': 'rjust', '=': 'zfill'} justify_method = justify_methods[align_char] justify_args = (col_width, fill_char) if fill_char else (col_width,) for i, col_str in enumerate(col_strs): col_strs[i] = getattr(col_str, justify_method)(*justify_args) if outs['show_length']: col_strs.append('Length = {} rows'.format(len(col))) return col_strs, outs