reports: Add BaseODS.border_style() method.
This commit is contained in:
parent
138928eebf
commit
15becebf5c
2 changed files with 78 additions and 3 deletions
|
@ -18,6 +18,7 @@ import abc
|
||||||
import collections
|
import collections
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
|
import enum
|
||||||
import itertools
|
import itertools
|
||||||
import operator
|
import operator
|
||||||
import re
|
import re
|
||||||
|
@ -518,6 +519,14 @@ class BaseSpreadsheet(Generic[RT, ST], metaclass=abc.ABCMeta):
|
||||||
self.end_spreadsheet()
|
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):
|
class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
|
||||||
"""Abstract base class to help write OpenDocument spreadsheets
|
"""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.locale = babel.core.Locale.default('LC_MONETARY')
|
||||||
self.currency_fmt_key = 'accounting'
|
self.currency_fmt_key = 'accounting'
|
||||||
self._name_counter = itertools.count(1)
|
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.document = odf.opendocument.OpenDocumentSpreadsheet()
|
||||||
self.init_settings()
|
self.init_settings()
|
||||||
self.init_styles()
|
self.init_styles()
|
||||||
|
@ -666,6 +675,32 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
### Styles
|
### 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:
|
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()):
|
if not isinstance(width, str) or (width and not width[-1].isalpha()):
|
||||||
width = f'{width}in'
|
width = f'{width}in'
|
||||||
|
@ -785,7 +820,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
|
||||||
cache_parts.append(f'{key}={value}')
|
cache_parts.append(f'{key}={value}')
|
||||||
cache_key = '\0'.join(cache_parts)
|
cache_key = '\0'.join(cache_parts)
|
||||||
try:
|
try:
|
||||||
style = self._currency_style_cache[cache_key]
|
style = self._style_cache[cache_key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pos_style = self._build_currency_style(
|
pos_style = self._build_currency_style(
|
||||||
root, locale, code, 0, positive_properties, volatile=True,
|
root, locale, code, 0, positive_properties, volatile=True,
|
||||||
|
@ -803,7 +838,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
|
||||||
family='table-cell',
|
family='table-cell',
|
||||||
datastylename=curr_style,
|
datastylename=curr_style,
|
||||||
)
|
)
|
||||||
self._currency_style_cache[cache_key] = style
|
self._style_cache[cache_key] = style
|
||||||
return style
|
return style
|
||||||
|
|
||||||
def _merge_style_iter_names(
|
def _merge_style_iter_names(
|
||||||
|
|
|
@ -313,6 +313,46 @@ def test_ods_writer_style(ods_writer, attr_name, child_type, checked_attr):
|
||||||
child = get_child(actual, child_type)
|
child = get_child(actual, child_type)
|
||||||
assert child.getAttribute(checked_attr)
|
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):
|
def test_ods_writer_merge_styles(ods_writer):
|
||||||
style = ods_writer.merge_styles(ods_writer.style_bold, ods_writer.style_dividerline)
|
style = ods_writer.merge_styles(ods_writer.style_bold, ods_writer.style_dividerline)
|
||||||
actual = get_child(
|
actual = get_child(
|
||||||
|
|
Loading…
Reference in a new issue