262 lines
8.6 KiB
Python
262 lines
8.6 KiB
Python
#!/usr/bin/env python
|
|
#coding:utf-8
|
|
# Purpose: table cell objects
|
|
# Created: 03.01.2011
|
|
# Copyright (C) 2011, Manfred Moitzi
|
|
# License: MIT license
|
|
from __future__ import unicode_literals, print_function, division
|
|
__author__ = "mozman <mozman@gmx.at>"
|
|
|
|
from .xmlns import register_class, CN
|
|
from .base import GenericWrapper
|
|
from .text import Paragraph, Span
|
|
from .propertymixins import StringProperty, BooleanProperty
|
|
from .compatibility import tostr, is_string
|
|
|
|
VALID_VALUE_TYPES = frozenset( ('float', 'percentage', 'currency', 'date', 'time',
|
|
'boolean', 'string') )
|
|
NUMERIC_TYPES = frozenset( ('float', 'percentage', 'currency') )
|
|
|
|
TYPE_VALUE_MAP = {
|
|
'string': CN('office:string-value'),
|
|
'float': CN('office:value'),
|
|
'percentage': CN('office:value'),
|
|
'currency': CN('office:value'),
|
|
'date': CN('office:date-value'),
|
|
'time': CN('office:time-value'),
|
|
'boolean': CN('office:boolean-value'),
|
|
}
|
|
|
|
# These Classes are supported to read their plaintext content from the
|
|
# cell-content.
|
|
SUPPORTED_CELL_CONTENT = ("Paragraph", "Heading")
|
|
|
|
@register_class
|
|
class Cell(GenericWrapper):
|
|
CELL_ONLY_ATTRIBS = (CN('table:number-rows-spanned'),
|
|
CN('table:number-columns-spanned'),
|
|
CN('table:number-matrix-columns-spanned'),
|
|
CN('table:number-matrix-rows-spanned'))
|
|
|
|
TAG = CN('table:table-cell')
|
|
style_name = StringProperty(CN('table:style-name'))
|
|
formula = StringProperty(CN('table:formula'))
|
|
protected = BooleanProperty(CN('table:protect'))
|
|
content_validation_name = StringProperty(CN('table:content-validation-name'))
|
|
|
|
def __init__(self, value=None, value_type=None, currency=None, style_name=None, xmlnode=None):
|
|
super(Cell, self).__init__(xmlnode=xmlnode)
|
|
if xmlnode is None:
|
|
if style_name is not None:
|
|
self.style_name = style_name
|
|
if value is not None:
|
|
self.set_value(value, value_type, currency)
|
|
elif value_type is not None:
|
|
self._set_value_type(value_type)
|
|
|
|
@property
|
|
def value_type(self):
|
|
return self.get_attr(CN('office:value-type'))
|
|
|
|
@property
|
|
def value(self):
|
|
def convert(value, value_type):
|
|
if value is None:
|
|
pass
|
|
elif value_type in NUMERIC_TYPES:
|
|
value = float(value)
|
|
elif value_type == 'boolean':
|
|
value = True if value == 'true' else False
|
|
return value
|
|
|
|
t = self.value_type
|
|
if t is None:
|
|
result = None
|
|
elif t == 'string':
|
|
result = self.plaintext()
|
|
else:
|
|
result = convert(self.xmlnode.get(TYPE_VALUE_MAP[t]), t)
|
|
return result
|
|
|
|
def value_as(self, value_type=None):
|
|
def convert(value, value_type):
|
|
if value is None:
|
|
pass
|
|
elif value_type in NUMERIC_TYPES:
|
|
value = float(value)
|
|
elif value_type == 'boolean':
|
|
value = True if value == 'true' else False
|
|
return value
|
|
|
|
t = value_type
|
|
if t is None:
|
|
result = None
|
|
elif t == 'string':
|
|
result = self.plaintext()
|
|
else:
|
|
result = convert(self.xmlnode.get(TYPE_VALUE_MAP[t]), t)
|
|
return result
|
|
|
|
def set_value(self, value, value_type=None, currency=None):
|
|
|
|
def is_valid_value(value):
|
|
result = True
|
|
if value is None:
|
|
result = False
|
|
elif isinstance(value, GenericWrapper):
|
|
if value.kind not in SUPPORTED_CELL_CONTENT:
|
|
result = False
|
|
return result
|
|
|
|
def is_valid_type(value_type):
|
|
return True if value_type in VALID_VALUE_TYPES else False
|
|
|
|
def determine_value_type(value):
|
|
if type(value) == bool:
|
|
value_type = 'boolean'
|
|
elif isinstance(value, (float, int)):
|
|
value_type = 'float'
|
|
else:
|
|
value_type = 'string'
|
|
return value_type
|
|
|
|
def convert(value, value_type):
|
|
if isinstance(value, GenericWrapper):
|
|
pass
|
|
elif value_type == 'string':
|
|
value = Paragraph(tostr(value))
|
|
elif value_type == 'boolean':
|
|
value = 'true' if value else 'false'
|
|
else:
|
|
value = tostr(value)
|
|
return value
|
|
|
|
if not is_valid_value(value):
|
|
raise ValueError("invalid value: %s" % tostr(value))
|
|
if is_string(currency):
|
|
value_type = 'currency'
|
|
if value_type is None:
|
|
value_type = determine_value_type(value)
|
|
if not is_valid_type(value_type):
|
|
raise TypeError(value_type)
|
|
|
|
value = convert(value, value_type)
|
|
self._clear_old_value()
|
|
self._set_new_value(value, value_type, currency)
|
|
|
|
def _set_new_value(self, value, value_type, currency):
|
|
if isinstance(value, GenericWrapper):
|
|
value_type = 'string'
|
|
self.append(value)
|
|
else:
|
|
self.set_attr(TYPE_VALUE_MAP[value_type], value)
|
|
self._set_value_type(value_type)
|
|
|
|
if currency and (value_type == 'currency'):
|
|
self.set_attr(CN('office:currency'), currency)
|
|
|
|
def _set_value_type(self, value_type):
|
|
self.set_attr(CN('office:value-type'), value_type)
|
|
|
|
def _clear_old_value(self):
|
|
self._clear_value_attribute(self.value_type)
|
|
self._clear_content()
|
|
|
|
def _clear_content(self):
|
|
xmlnode = self.xmlnode
|
|
for _ in range(len(xmlnode)):
|
|
del xmlnode[0]
|
|
|
|
def _clear_value_attribute(self, value_type):
|
|
try:
|
|
attribute_name = TYPE_VALUE_MAP[value_type]
|
|
del self.xmlnode.attrib[attribute_name]
|
|
except KeyError:
|
|
pass
|
|
|
|
@property
|
|
def display_form(self):
|
|
return self.plaintext()
|
|
@display_form.setter
|
|
def display_form(self, text):
|
|
t = self.value_type
|
|
if t is None or t == 'string':
|
|
raise TypeError("not supported for value type 'None' and 'string'")
|
|
display_form = Paragraph(text)
|
|
first_paragraph = self.find(Paragraph.TAG)
|
|
if first_paragraph is None:
|
|
self.append(display_form)
|
|
else:
|
|
self.replace(first_paragraph, display_form)
|
|
|
|
def plaintext(self):
|
|
return "\n".join([p.plaintext() for p in iter(self)
|
|
if p.kind in SUPPORTED_CELL_CONTENT])
|
|
|
|
def append_text(self, text, style_name=None):
|
|
if self.value_type != 'string':
|
|
raise TypeError('invalid cell type: %s' % self.value_type)
|
|
try:
|
|
last_child = self.get_child(-1)
|
|
if last_child.kind in ("Paragraph", "Heading"):
|
|
last_child.append(Span(text, style_name=style_name))
|
|
return
|
|
except IndexError:
|
|
pass
|
|
self.append(Paragraph(text, style_name=style_name))
|
|
|
|
@property
|
|
def currency(self):
|
|
return self.xmlnode.get(CN('office:currency'))
|
|
|
|
@property
|
|
def span(self):
|
|
rows = self.xmlnode.get(CN('table:number-rows-spanned'))
|
|
cols = self.xmlnode.get(CN('table:number-columns-spanned'))
|
|
rows = 1 if rows is None else max(1, int(rows))
|
|
cols = 1 if cols is None else max(1, int(cols))
|
|
return (rows, cols)
|
|
|
|
def _set_span(self, value):
|
|
rows, cols = value
|
|
rows = max(1, int(rows))
|
|
cols = max(1, int(cols))
|
|
if rows == 1 and cols == 1:
|
|
self._del_span_attributes()
|
|
else:
|
|
self._set_span_attributes(rows, cols)
|
|
|
|
def _del_span_attributes(self):
|
|
del self.xmlnode.attrib[CN('table:number-rows-spanned')]
|
|
del self.xmlnode.attrib[CN('table:number-columns-spanned')]
|
|
|
|
def _set_span_attributes(self, rows, cols):
|
|
self.xmlnode.set(CN('table:number-rows-spanned'), tostr(rows))
|
|
self.xmlnode.set(CN('table:number-columns-spanned'), tostr(cols))
|
|
|
|
@property
|
|
def covered(self):
|
|
return self.xmlnode.tag == CN('table:covered-table-cell')
|
|
|
|
def _set_covered(self, value):
|
|
if value:
|
|
self.TAG = CN('table:covered-table-cell')
|
|
self.xmlnode.tag = self.TAG
|
|
self._remove_exclusive_cell_attributes()
|
|
else:
|
|
self.TAG = CN('table:table-cell')
|
|
self.xmlnode.tag = self.TAG
|
|
|
|
def _remove_exclusive_cell_attributes(self):
|
|
for key in self.CELL_ONLY_ATTRIBS:
|
|
if key in self.xmlnode.attrib:
|
|
del self.xmlnode.attrib[key]
|
|
|
|
@register_class
|
|
class CoveredCell(Cell):
|
|
TAG = CN('table:covered-table-cell')
|
|
|
|
@property
|
|
def kind(self):
|
|
return 'Cell'
|