1. Sergey Astanin
  2. python-tabulate
Issue #28 new

Feature request: Multiline cells and headers

Kristof Speeckaert
created an issue
from text wrapper import TextWrapper

t = TextWrapper(width=80)

text = 'If true, wrapping will occur preferably on whitespaces and right after hyphens in compound words, as it is customary in English. If false, only whitespaces will be considered as potentially good places for line breaks, but you need to set break_long_words to false if you want truly insecable words. Default behaviour in previous versions was to always allow breaking hyphenated words.'

data = [t.fill(text), 'yes', 1, 500]
>>>len(text)
385

>>>len(t.wrap(text)[0])
78
print(tabulate([data]))

Result:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  ---  -  ---
If true, wrapping will occur preferably on whitespaces and right after hyphens
in compound words, as it is customary in English. If false, only whitespaces
will be considered as potentially good places for line breaks, but you need to
set break_long_words to false if you want truly insecable words. Default
behaviour in previous versions was to always allow breaking hyphenated words.  yes  1  500
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  ---  -  ---

The contents of the table are fine, but the column width of the header and footer lines do not take into account the line breaks that appear in the text. Instead, they seem to look at the length of the text as a whole.

Comments (10)

  1. Sergey Astanin repo owner

    Hi Kristof,

    Multiline cells are not currently supported. It is easy to calculate their width, but

    • most supported output formats are not suitable for displaying tables with multiline cells
    • supporting multiline cells will require changing how padding and formatting works: the program will have to count lines in every cell and juxtapose multiline cells line by line
    • I expect that it will result in a noticiable performace loss (> 10%)

    This is the patch to calculate cell width for multiline content correctly:

    diff --git a/tabulate.py b/tabulate.py
    index ddd286d..2d1688a 100644
    --- a/tabulate.py
    +++ b/tabulate.py
    @@ -345,6 +345,16 @@ def _strip_invisible(s):
             return re.sub(_invisible_codes_bytes, "", s)
    
    
    +def _max_line_width(s):
    +    """Visible width of a potentially multiline content.
    +
    +    >>> _max_line_width('foo\nbarbaz\nspam\neggs')
    +    6
    +
    +    """
    +    return max(map(len, s.splitlines()))
    +
    +
     def _visible_width(s):
         """Visible width of a printed string. ANSI color codes are removed.
    
    @@ -353,9 +363,9 @@ def _visible_width(s):
    
         """
         if isinstance(s, _text_type) or isinstance(s, _binary_type):
    -        return len(_strip_invisible(s))
    +        return _max_line_width(_strip_invisible(s))
         else:
    -        return len(_text_type(s))
    +        return _max_line_width(_text_type(s))
    
    
     def _align_column(strings, alignment, minwidth=0, has_invisible=True):
    @@ -389,7 +399,7 @@ def _align_column(strings, alignment, minwidth=0, has_invisible=True):
         if has_invisible:
             width_fn = _visible_width
         else:
    -        width_fn = len
    +        width_fn = _max_line_width
    
         maxwidth = max(max(map(width_fn, strings)), minwidth)
         padded_strings = [padfn(maxwidth, s, has_invisible) for s in strings]
    @@ -775,7 +785,7 @@ def tabulate(tabular_data, headers=[], tablefmt="simple",
         if has_invisible:
             width_fn = _visible_width
         else:
    -        width_fn = len
    +        width_fn = _max_line_width
    
         # format rows and columns, convert numeric values to strings
         cols = list(zip(*list_of_lists))
    

    To implement multiline cells correctly, _format_table() function will have to be modified too. If you start working on this problem, please make sure that all new functionality is covered by tests and the library remains backwards compatible.

  2. Kristof Speeckaert reporter

    Actually, you have a point regarding multiple cells with line breaks. I can imagine performance would deteriorate quite rapidly for a feature which only be marginally used.

    Also, I didn't realise this was not supported, that's why I logged it as a bug. In retrospect, it should've been a feature request.

  3. Amjith Ramanujam

    Is it possible to use the feature by passing in an optional parameter to the tabulate() method?

    I'm not too concerned by the performance overhead, but displaying multiline cells correctly is a priority.

  4. Log in to comment