1#!/usr/bin/env python 2#coding:utf-8 3# Purpose: table cell objects 4# Created: 03.01.2011 5# Copyright (C) 2011, Manfred Moitzi 6# License: MIT license 7from __future__ import unicode_literals, print_function, division 8__author__ = "mozman <mozman@gmx.at>" 9 10from .xmlns import register_class, CN 11from .base import GenericWrapper 12from .text import Paragraph, Span 13from .propertymixins import StringProperty, BooleanProperty 14from .compatibility import tostr, is_string 15 16VALID_VALUE_TYPES = frozenset( ('float', 'percentage', 'currency', 'date', 'time', 17 'boolean', 'string') ) 18NUMERIC_TYPES = frozenset( ('float', 'percentage', 'currency') ) 19 20TYPE_VALUE_MAP = { 21 'string': CN('office:string-value'), 22 'float': CN('office:value'), 23 'percentage': CN('office:value'), 24 'currency': CN('office:value'), 25 'date': CN('office:date-value'), 26 'time': CN('office:time-value'), 27 'boolean': CN('office:boolean-value'), 28} 29 30# These Classes are supported to read their plaintext content from the 31# cell-content. 32SUPPORTED_CELL_CONTENT = ("Paragraph", "Heading") 33 34@register_class 35class Cell(GenericWrapper): 36 CELL_ONLY_ATTRIBS = (CN('table:number-rows-spanned'), 37 CN('table:number-columns-spanned'), 38 CN('table:number-matrix-columns-spanned'), 39 CN('table:number-matrix-rows-spanned')) 40 41 TAG = CN('table:table-cell') 42 style_name = StringProperty(CN('table:style-name')) 43 formula = StringProperty(CN('table:formula')) 44 protected = BooleanProperty(CN('table:protect')) 45 content_validation_name = StringProperty(CN('table:content-validation-name')) 46 47 def __init__(self, value=None, value_type=None, currency=None, style_name=None, xmlnode=None): 48 super(Cell, self).__init__(xmlnode=xmlnode) 49 if xmlnode is None: 50 if style_name is not None: 51 self.style_name = style_name 52 if value is not None: 53 self.set_value(value, value_type, currency) 54 elif value_type is not None: 55 self._set_value_type(value_type) 56 57 @property 58 def value_type(self): 59 return self.get_attr(CN('office:value-type')) 60 61 @property 62 def value(self): 63 def convert(value, value_type): 64 if value is None: 65 pass 66 elif value_type in NUMERIC_TYPES: 67 value = float(value) 68 elif value_type == 'boolean': 69 value = True if value == 'true' else False 70 return value 71 72 t = self.value_type 73 if t is None: 74 result = None 75 elif t == 'string': 76 result = self.plaintext() 77 else: 78 result = convert(self.xmlnode.get(TYPE_VALUE_MAP[t]), t) 79 return result 80 81 def value_as(self, value_type=None): 82 def convert(value, value_type): 83 if value is None: 84 pass 85 elif value_type in NUMERIC_TYPES: 86 value = float(value) 87 elif value_type == 'boolean': 88 value = True if value == 'true' else False 89 return value 90 91 t = value_type 92 if t is None: 93 result = None 94 elif t == 'string': 95 result = self.plaintext() 96 else: 97 result = convert(self.xmlnode.get(TYPE_VALUE_MAP[t]), t) 98 return result 99 100 def set_value(self, value, value_type=None, currency=None): 101 102 def is_valid_value(value): 103 result = True 104 if value is None: 105 result = False 106 elif isinstance(value, GenericWrapper): 107 if value.kind not in SUPPORTED_CELL_CONTENT: 108 result = False 109 return result 110 111 def is_valid_type(value_type): 112 return True if value_type in VALID_VALUE_TYPES else False 113 114 def determine_value_type(value): 115 if type(value) == bool: 116 value_type = 'boolean' 117 elif isinstance(value, (float, int)): 118 value_type = 'float' 119 else: 120 value_type = 'string' 121 return value_type 122 123 def convert(value, value_type): 124 if isinstance(value, GenericWrapper): 125 pass 126 elif value_type == 'string': 127 value = Paragraph(tostr(value)) 128 elif value_type == 'boolean': 129 value = 'true' if value else 'false' 130 else: 131 value = tostr(value) 132 return value 133 134 if not is_valid_value(value): 135 raise ValueError("invalid value: %s" % tostr(value)) 136 if is_string(currency): 137 value_type = 'currency' 138 if value_type is None: 139 value_type = determine_value_type(value) 140 if not is_valid_type(value_type): 141 raise TypeError(value_type) 142 143 value = convert(value, value_type) 144 self._clear_old_value() 145 self._set_new_value(value, value_type, currency) 146 147 def _set_new_value(self, value, value_type, currency): 148 if isinstance(value, GenericWrapper): 149 value_type = 'string' 150 self.append(value) 151 else: 152 self.set_attr(TYPE_VALUE_MAP[value_type], value) 153 self._set_value_type(value_type) 154 155 if currency and (value_type == 'currency'): 156 self.set_attr(CN('office:currency'), currency) 157 158 def _set_value_type(self, value_type): 159 self.set_attr(CN('office:value-type'), value_type) 160 161 def _clear_old_value(self): 162 self._clear_value_attribute(self.value_type) 163 self._clear_content() 164 165 def _clear_content(self): 166 xmlnode = self.xmlnode 167 for _ in range(len(xmlnode)): 168 del xmlnode[0] 169 170 def _clear_value_attribute(self, value_type): 171 try: 172 attribute_name = TYPE_VALUE_MAP[value_type] 173 del self.xmlnode.attrib[attribute_name] 174 except KeyError: 175 pass 176 177 @property 178 def display_form(self): 179 return self.plaintext() 180 @display_form.setter 181 def display_form(self, text): 182 t = self.value_type 183 if t is None or t == 'string': 184 raise TypeError("not supported for value type 'None' and 'string'") 185 display_form = Paragraph(text) 186 first_paragraph = self.find(Paragraph.TAG) 187 if first_paragraph is None: 188 self.append(display_form) 189 else: 190 self.replace(first_paragraph, display_form) 191 192 def plaintext(self): 193 return "\n".join([p.plaintext() for p in iter(self) 194 if p.kind in SUPPORTED_CELL_CONTENT]) 195 196 def append_text(self, text, style_name=None): 197 if self.value_type != 'string': 198 raise TypeError('invalid cell type: %s' % self.value_type) 199 try: 200 last_child = self.get_child(-1) 201 if last_child.kind in ("Paragraph", "Heading"): 202 last_child.append(Span(text, style_name=style_name)) 203 return 204 except IndexError: 205 pass 206 self.append(Paragraph(text, style_name=style_name)) 207 208 @property 209 def currency(self): 210 return self.xmlnode.get(CN('office:currency')) 211 212 @property 213 def span(self): 214 rows = self.xmlnode.get(CN('table:number-rows-spanned')) 215 cols = self.xmlnode.get(CN('table:number-columns-spanned')) 216 rows = 1 if rows is None else max(1, int(rows)) 217 cols = 1 if cols is None else max(1, int(cols)) 218 return (rows, cols) 219 220 def _set_span(self, value): 221 rows, cols = value 222 rows = max(1, int(rows)) 223 cols = max(1, int(cols)) 224 if rows == 1 and cols == 1: 225 self._del_span_attributes() 226 else: 227 self._set_span_attributes(rows, cols) 228 229 def _del_span_attributes(self): 230 del self.xmlnode.attrib[CN('table:number-rows-spanned')] 231 del self.xmlnode.attrib[CN('table:number-columns-spanned')] 232 233 def _set_span_attributes(self, rows, cols): 234 self.xmlnode.set(CN('table:number-rows-spanned'), tostr(rows)) 235 self.xmlnode.set(CN('table:number-columns-spanned'), tostr(cols)) 236 237 @property 238 def covered(self): 239 return self.xmlnode.tag == CN('table:covered-table-cell') 240 241 def _set_covered(self, value): 242 if value: 243 self.TAG = CN('table:covered-table-cell') 244 self.xmlnode.tag = self.TAG 245 self._remove_exclusive_cell_attributes() 246 else: 247 self.TAG = CN('table:table-cell') 248 self.xmlnode.tag = self.TAG 249 250 def _remove_exclusive_cell_attributes(self): 251 for key in self.CELL_ONLY_ATTRIBS: 252 if key in self.xmlnode.attrib: 253 del self.xmlnode.attrib[key] 254 255@register_class 256class CoveredCell(Cell): 257 TAG = CN('table:covered-table-cell') 258 259 @property 260 def kind(self): 261 return 'Cell' 262