Changeset - 15becebf5c4f
[Not reviewed]
0 2 0
Brett Smith - 4 years ago 2020-06-28 03:08:23
brettcsmith@brettcsmith.org
reports: Add BaseODS.border_style() method.
2 files changed with 78 insertions and 3 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/core.py
Show inline comments
...
 
@@ -18,6 +18,7 @@ import abc
 
import collections
 
import copy
 
import datetime
 
import enum
 
import itertools
 
import operator
 
import re
...
 
@@ -518,6 +519,14 @@ class BaseSpreadsheet(Generic[RT, ST], metaclass=abc.ABCMeta):
 
        self.end_spreadsheet()
 

	
 

	
 
class Border(enum.IntFlag):
 
    TOP = 1
 
    RIGHT = 2
 
    BOTTOM = 4
 
    LEFT = 8
 
    # in CSS order, clockwise from top
 

	
 

	
 
class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
 
    """Abstract base class to help write OpenDocument spreadsheets
 

	
...
 
@@ -548,7 +557,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
 
        self.locale = babel.core.Locale.default('LC_MONETARY')
 
        self.currency_fmt_key = 'accounting'
 
        self._name_counter = itertools.count(1)
 
        self._currency_style_cache: MutableMapping[str, odf.style.Style] = {}
 
        self._style_cache: MutableMapping[str, odf.style.Style] = {}
 
        self.document = odf.opendocument.OpenDocumentSpreadsheet()
 
        self.init_settings()
 
        self.init_styles()
...
 
@@ -666,6 +675,32 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
 

	
 
    ### Styles
 

	
 
    def border_style(self,
 
                     edges: int,
 
                     width: str='1px',
 
                     style: str='solid',
 
                     color: str='#000000',
 
    ) -> odf.style.Style:
 
        flags = [edge for edge in Border if edges & edge]
 
        if not flags:
 
            raise ValueError(f"no valid edges in {edges!r}")
 
        border_attr = f'{width} {style} {color}'
 
        key = f'{",".join(f.name for f in flags)} {border_attr}'
 
        try:
 
            retval = self._style_cache[key]
 
        except KeyError:
 
            props = odf.style.TableCellProperties()
 
            for flag in flags:
 
                props.setAttribute(f'border{flag.name.lower()}', border_attr)
 
            retval = odf.style.Style(
 
                name=f'Border{next(self._name_counter)}',
 
                family='table-cell',
 
            )
 
            retval.addElement(props)
 
            self.document.styles.addElement(retval)
 
            self._style_cache[key] = retval
 
        return retval
 

	
 
    def column_style(self, width: Union[float, str], **attrs: Any) -> odf.style.Style:
 
        if not isinstance(width, str) or (width and not width[-1].isalpha()):
 
            width = f'{width}in'
...
 
@@ -785,7 +820,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
 
                cache_parts.append(f'{key}={value}')
 
        cache_key = '\0'.join(cache_parts)
 
        try:
 
            style = self._currency_style_cache[cache_key]
 
            style = self._style_cache[cache_key]
 
        except KeyError:
 
            pos_style = self._build_currency_style(
 
                root, locale, code, 0, positive_properties, volatile=True,
...
 
@@ -803,7 +838,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
 
                family='table-cell',
 
                datastylename=curr_style,
 
            )
 
            self._currency_style_cache[cache_key] = style
 
            self._style_cache[cache_key] = style
 
        return style
 

	
 
    def _merge_style_iter_names(
tests/test_reports_spreadsheet.py
Show inline comments
...
 
@@ -313,6 +313,46 @@ def test_ods_writer_style(ods_writer, attr_name, child_type, checked_attr):
 
    child = get_child(actual, child_type)
 
    assert child.getAttribute(checked_attr)
 

	
 
@pytest.mark.parametrize('edges,width,style,color', [
 
    (core.Border.TOP,
 
     '5px', 'solid', '#ff0000'),
 
    (core.Border.RIGHT | core.Border.LEFT,
 
     '2pt', 'dashed', '#00ff00'),
 
    (core.Border.BOTTOM | core.Border.RIGHT | core.Border.LEFT,
 
     '1em', 'dotted', '#0000ff'),
 
    (core.Border.TOP | core.Border.BOTTOM | core.Border.RIGHT | core.Border.LEFT,
 
     '1cm', 'thick', '#aaaaaa'),
 
])
 
def test_ods_writer_border_style(ods_writer, edges, width, style, color):
 
    actual = ods_writer.border_style(edges, width, style, color)
 
    props, = actual.childNodes
 
    attr_s = f'{width} {style} {color}'
 
    for edge_exp, edge_name in enumerate(['top', 'right', 'bottom', 'left']):
 
        expected = attr_s if edges & (2 ** edge_exp) else None
 
        assert props.getAttribute(f'border{edge_name}') == expected
 

	
 
def test_ods_writer_border_style_caches(ods_writer):
 
    expected = ods_writer.border_style(core.Border.TOP)
 
    width, style, color = expected.childNodes[0].getAttribute('bordertop').split()
 
    actual = ods_writer.border_style(core.Border.TOP, width, style, color)
 
    assert actual is expected
 

	
 
@pytest.mark.parametrize('argname,val1,val2', [
 
    ('edges', core.Border.TOP, core.Border.LEFT),
 
    ('edges', core.Border.TOP, core.Border.TOP | core.Border.BOTTOM),
 
    ('style', 'solid', 'dashed'),
 
    ('width', '1px', '1em'),
 
    ('width', '1px', '2px'),
 
    ('color', '#0000fe', '#0000ff'),
 
])
 
def test_ods_writer_border_no_caching(ods_writer, argname, val1, val2):
 
    kwargs = {'edges': core.Border.TOP}
 
    kwargs[argname] = val1
 
    style1 = ods_writer.border_style(**kwargs)
 
    kwargs[argname] = val2
 
    style2 = ods_writer.border_style(**kwargs)
 
    assert style1 is not style2
 

	
 
def test_ods_writer_merge_styles(ods_writer):
 
    style = ods_writer.merge_styles(ods_writer.style_bold, ods_writer.style_dividerline)
 
    actual = get_child(
0 comments (0 inline, 0 general)