1#!/usr/bin/env python
2#coding:utf-8
3# Purpose: test table
4# Created: 20.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
10# Standard Library
11try:
12    import unittest2 as unittest
13except ImportError:
14    import unittest
15
16from itertools import chain
17
18# trusted or separately tested modules
19from ezodf.xmlns import CN, etree, wrap
20from ezodf.compatibility import is_string
21
22# objects to test
23from ezodf.cells import Cell
24from ezodf.table import Table
25
26TESTTABLE = """
27<table:table xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" />
28"""
29def tofloats(values):
30    return [cell.value for cell in values]
31
32class TestTableAttributes(unittest.TestCase):
33    def test_has_TAG(self):
34        table = Table()
35        self.assertEqual(table.TAG, CN('table:table'))
36
37    def test_has_xmlnode(self):
38        table = Table()
39        self.assertIsNotNone(table.xmlnode)
40
41    def test_get_name(self):
42        table = Table()
43        name = table.name
44        self.assertTrue(is_string(name))
45
46    def test_set_name(self):
47        table = Table()
48        table.name = 'TABLE'
49        self.assertEqual(table.name, 'TABLE')
50        self.assertEqual(table.get_attr(CN('table:name')), 'TABLE', 'wrong tag name')
51
52    def test_name_with_cr_and_tabs(self):
53        table = Table('table\tname\r  ')
54        self.assertEqual(table.name, 'table name', "table name not normalized")
55
56    def test_name_with_apostroph(self):
57        table = Table('"table name"')
58        self.assertEqual(table.name, 'table name', "table name not normalized")
59
60    def test_name_with_apostroph2(self):
61        table = Table("  table'name")
62        self.assertEqual(table.name, 'table name', "table name not normalized")
63
64    def test_get_style_name(self):
65        table = Table()
66        self.assertIsNone(table.style_name)
67
68    def test_set_style_name(self):
69        table = Table()
70        table.style_name = 'STYLE'
71        self.assertEqual(table.style_name, 'STYLE')
72        self.assertEqual(table.get_attr(CN('table:style-name')), 'STYLE', 'wrong tag name')
73
74    def test_get_protected(self):
75        table = Table()
76        self.assertFalse(table.protected)
77
78    def test_set_protected(self):
79        table = Table()
80        table.protected = True
81        self.assertTrue(table.protected)
82        self.assertEqual(table.get_attr(CN('table:protected')), 'true', 'wrong tag name')
83
84    def test_protection_key_not_set(self):
85        table = Table()
86        key = table.get_attr(CN('table:protection-key'))
87        self.assertIsNone(key)
88
89    def test_protection_key_is_set(self):
90        table = Table()
91        table.protected = True
92        key = table.get_attr(CN('table:protection-key'))
93        self.assertIsNotNone(key, "protection-key not set")
94        self.assertGreater(len(key), 8, "protection-key is too short")
95
96    def test_get_print(self):
97        table = Table()
98        self.assertFalse(table.print_)
99
100    def test_set_print(self):
101        table = Table()
102        table.print_ = True
103        self.assertTrue(table.print_)
104        self.assertEqual(table.get_attr(CN('table:print')), 'true', 'table:print should be true')
105
106    def test_if_Table_class_is_registered(self):
107        table = wrap(etree.XML(TESTTABLE))
108        self.assertEqual(table.TAG, CN('table:table'), 'Table class is not registered')
109
110TABLE_5x3 = """
111<table:table xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0">
112<table:table-row><table:table-cell /><table:table-cell /><table:table-cell /></table:table-row>
113<table:table-row><table:table-cell /><table:table-cell /><table:table-cell /></table:table-row>
114<table:table-row><table:table-cell /><table:table-cell /><table:table-cell /></table:table-row>
115<table:table-row><table:table-cell /><table:table-cell /><table:table-cell /></table:table-row>
116<table:table-row><table:table-cell /><table:table-cell /><table:table-cell /></table:table-row>
117</table:table>
118"""
119
120TABLE_REP_7x7 = """
121<table:table xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0">
122<table:table-header-rows>
123  <table:table-row><table:table-cell table:number-columns-repeated="6"/><table:table-cell /></table:table-row>
124</table:table-header-rows>
125<table:table-rows>
126  <table:table-row table:number-rows-repeated="5"><table:table-cell table:number-columns-repeated="6" /><table:table-cell /></table:table-row>
127  <table:table-row><table:table-cell table:number-columns-repeated="6"/><table:table-cell /></table:table-row>
128</table:table-rows>
129</table:table>
130"""
131
132class TestTableMethods(unittest.TestCase):
133    def test_nrows(self):
134        table = wrap(etree.XML(TABLE_5x3))
135        self.assertEqual(table.nrows(), 5)
136
137    def test_nrows_repeated(self):
138        table = wrap(etree.XML(TABLE_REP_7x7))
139        self.assertEqual(table.nrows(), 7)
140
141    def test_ncols(self):
142        table = wrap(etree.XML(TABLE_5x3))
143        self.assertEqual(table.ncols(), 3)
144
145    def test_ncols_repeated(self):
146        table = wrap(etree.XML(TABLE_REP_7x7))
147        self.assertEqual(table.ncols(), 7)
148
149    def test_init_row_cols(self):
150        table = Table(name="TEST", size=(7, 5))
151        self.assertEqual(table.nrows(), 7)
152        self.assertEqual(table.ncols(), 5)
153
154    def test_reset(self):
155        table = Table(name="TEST", size=(7, 5))
156        table.reset(size=(8, 10))
157        self.assertEqual("TEST", table.name, "name attribute is deleted")
158        self.assertEqual(table.ncols(), 10)
159        self.assertEqual(table.nrows(), 8)
160
161    def test_clear_table(self):
162        table = Table(name="TEST")
163        table[0,0].set_value("marker")
164        table.clear()
165        self.assertIsNone(table[0,0].value, "cell value should be None")
166
167    def test_setup_error(self):
168        with self.assertRaises(ValueError):
169            Table(size=(1, 0))
170        with self.assertRaises(ValueError):
171            Table(size=(0, 1))
172
173    def test_copy_table_with_new_name(self):
174        table1 = Table(name='Test')
175        table1['A1'].set_value('marker')
176
177        table2 = table1.copy(newname='Copy of '+table1.name)
178
179        self.assertEqual('Copy of Test', table2.name, "table2 has wrong name.")
180        self.assertEqual('marker', table2['A1'].value, "marker not found in table2")
181
182    def test_copy_table_without_new_name(self):
183        table1 = Table(name='Test')
184        table2 = table1.copy()
185        self.assertEqual('CopyOfTest', table2.name, "table2 has wrong name.")
186
187TABLE_COMP = """
188<table:table xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0">
189<table:table-row table:number-rows-repeated="6">
190  <table:table-cell table:number-columns-repeated="6"/>
191  <table:table-cell/>
192</table:table-row>
193<table:table-row>
194  <table:table-cell table:number-columns-repeated="6"/>
195  <table:table-cell/>
196</table:table-row>
197</table:table>
198"""
199
200class TestTableContent(unittest.TestCase):
201    def setUp(self):
202        self.table = Table(xmlnode=etree.XML(TABLE_COMP))
203
204    def test_metrics(self):
205        self.assertEqual(self.table.ncols(), 7)
206        self.assertEqual(self.table.nrows(), 7)
207
208    def test_set_get_different_values(self):
209        ncols = self.table.ncols()
210        for row in range(self.table.nrows()):
211            for col in range(ncols):
212                cell = self.table[row, col]
213                cell.set_value(row*ncols + col, 'float')
214
215        for row in range(self.table.nrows()):
216            for col in range(ncols):
217                cell = self.table[row, col]
218                self.assertEqual(row*ncols + col, int(cell.value))
219
220
221SQUAREMATRIX = """
222<table:table
223 xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
224 xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
225 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
226 table:name="RowColAccess">
227
228<table:table-columns>
229  <table:table-column table:style-name="c0" />
230  <table:table-column table:style-name="c1" />
231  <table:table-column table:style-name="c2" />
232  <table:table-column table:style-name="c3" />
233</table:table-columns>
234
235<table:table-rows>
236  <table:table-row table:style-name='r0'>
237    <table:table-cell office:value-type="float" office:value="1" />
238    <table:table-cell office:value-type="float" office:value="2" />
239    <table:table-cell office:value-type="float" office:value="3" />
240    <table:table-cell office:value-type="float" office:value="4" />
241  </table:table-row>
242  <table:table-row table:style-name='r1'>
243    <table:table-cell office:value-type="float" office:value="5" />
244    <table:table-cell office:value-type="float" office:value="6" />
245    <table:table-cell office:value-type="float" office:value="7" />
246    <table:table-cell office:value-type="float" office:value="8" />
247  </table:table-row>
248  <table:table-row table:style-name='r2'>
249    <table:table-cell office:value-type="float" office:value="9" />
250    <table:table-cell office:value-type="float" office:value="10" />
251    <table:table-cell office:value-type="float" office:value="11" />
252    <table:table-cell office:value-type="float" office:value="12" />
253  </table:table-row>
254  <table:table-row table:style-name='r3'>
255    <table:table-cell office:value-type="float" office:value="13" />
256    <table:table-cell office:value-type="float" office:value="14" />
257    <table:table-cell office:value-type="float" office:value="15" />
258    <table:table-cell office:value-type="float" office:value="16" />
259  </table:table-row>
260</table:table-rows>
261</table:table>
262"""
263
264class TestTableContentAccess(unittest.TestCase):
265    def setUp(self):
266        self.table = Table(xmlnode=etree.XML(SQUAREMATRIX))
267
268    def test_metrics(self):
269        self.assertEqual(self.table.name, 'RowColAccess')
270        self.assertEqual(self.table.ncols(), 4)
271        self.assertEqual(self.table.nrows(), 4)
272
273    def test_row_neg_index(self):
274        cell = self.table[-1, 0]
275        self.assertEqual(cell.value, 13.)
276
277    def test_col_index_error(self):
278        with self.assertRaises(IndexError):
279            self.table[0, 4]
280
281    def test_access_cell_by_index(self):
282        cell = self.table[0, 0]
283        self.assertEqual(cell.value, 1.)
284
285    def test_access_cell_by_reference(self):
286        cell = self.table['B1']
287        self.assertEqual(cell.value, 2.)
288
289    def test_setting_cell_by_index(self):
290        self.table[0, 0] = Cell('Textcell')
291        cell = self.table['A1']
292        self.assertEqual(cell.plaintext(), "Textcell")
293
294    def test_setting_cell_by_address(self):
295        self.table['A1'] = Cell('Textcell')
296        cell = self.table[0, 0]
297        self.assertEqual(cell.plaintext(), "Textcell")
298
299    def test_set_cell_row_index_error(self):
300        with self.assertRaises(IndexError):
301            self.table[10, 0] = Cell()
302
303    def test_set_cell_column_index_error(self):
304        with self.assertRaises(IndexError):
305            self.table[0, 10] = Cell()
306
307    def test_set_cell_neg_row_index(self):
308        self.table[-1, 0] = Cell('Textcell')
309        cell = self.table[3, 0]
310        self.assertEqual(cell.plaintext(), "Textcell")
311
312    def test_set_cell_neg_column_index(self):
313        self.table[0, -1] = Cell('Textcell')
314        cell = self.table[0, 3]
315        self.assertEqual(cell.plaintext(), "Textcell")
316
317    def test_if_rows_generates_lists(self):
318        nrows = self.table.nrows()
319        ncols = self.table.ncols()
320        for row in self.table.rows():
321            cells = row
322            self.assertEqual(len(cells), ncols)
323
324class TestRowColumnAccess(unittest.TestCase):
325    def setUp(self):
326        self.table = Table(xmlnode=etree.XML(SQUAREMATRIX))
327
328    def test_get_row_1_by_index(self):
329        values = [cell.value for cell in self.table.row(1)]
330        self.assertEqual(values, [5., 6., 7., 8.])
331
332    def test_get_row_1_by_address(self):
333        self.assertEqual(tofloats(self.table.row('A2')), [5., 6., 7., 8.])
334
335    def test_row_slice(self):
336        self.assertEqual(tofloats(self.table.row(1)[1:3]), [6., 7.])
337
338    def test_row_index_error(self):
339        with self.assertRaises(IndexError):
340            self.table.row(4)
341
342    def test_row_neg_index(self):
343        self.assertEqual(tofloats(self.table.row(-1)), [13., 14., 15., 16.])
344
345    def test_get_column_1_by_index(self):
346        self.assertEqual(tofloats(self.table.column(1)), [2., 6., 10., 14.])
347
348    def test_get_column_1_by_address(self):
349        self.assertEqual(tofloats(self.table.column('B2')), [2., 6., 10., 14.])
350
351    def test_column_slice(self):
352        self.assertEqual(tofloats(self.table.column(1)[1:3]), [6., 10.])
353
354    def test_column_index_error(self):
355        with self.assertRaises(IndexError):
356            self.table.column(4)
357
358    def test_column_neg_index(self):
359        self.assertEqual(tofloats(self.table.column(-1)), [4., 8., 12., 16.])
360
361    def test_rows(self):
362        values = tofloats(chain(*self.table.rows()))
363        expected = [float(x) for x in range(1, 17)]
364        self.assertEqual(expected, values)
365
366    def test_columns(self):
367        values = tofloats(chain(*self.table.columns()))
368        expected = [1., 5., 9., 13., 2., 6., 10., 14., 3., 7., 11., 15., 4., 8., 12., 16.]
369        self.assertEqual(expected, values)
370
371class TestRowColumnInfoAccess(unittest.TestCase):
372    def setUp(self):
373        self.table = Table(xmlnode=etree.XML(SQUAREMATRIX))
374
375    def test_get_row_info(self):
376        row_info = self.table.row_info(0)
377        self.assertEqual(row_info.style_name, 'r0')
378
379    def test_get_row_info_by_address(self):
380        row_info = self.table.row_info('C2')
381        self.assertEqual(row_info.style_name, 'r1')
382
383    def test_get_row_info_neg_index(self):
384        row_info = self.table.row_info(-1)
385        self.assertEqual(row_info.style_name, 'r3')
386
387    def test_get_row_info_index_error(self):
388        with self.assertRaises(IndexError):
389            self.table.row_info(4)
390
391    def test_get_column_info(self):
392        column_info = self.table.column_info(0)
393        self.assertEqual(column_info.style_name, 'c0')
394
395    def test_get_column_info_by_address(self):
396        column_info = self.table.column_info('B3')
397        self.assertEqual(column_info.style_name, 'c1')
398
399    def test_get_column_info_neg_index(self):
400        column_info = self.table.column_info(-1)
401        self.assertEqual(column_info.style_name, 'c3')
402
403    def test_get_column_info_index_error(self):
404        with self.assertRaises(IndexError):
405            self.table.column_info(4)
406
407class TestCellSpan(unittest.TestCase):
408    # this test-case tests only, if cell spanning is available
409    # for extensive cell span testing see: test_cell_span_controller.py
410    def setUp(self):
411        self.table = Table(name="TEST", size=(10, 10))
412
413    def test_set_cell_span(self):
414        self.table.set_cell_span('A1', (3, 3))
415        self.assertEqual((3, 3), self.table['A1'].span, "Span values for cell 'A1' not set.")
416        self.assertTrue(self.table['B2'].covered, "cell 'B1' is not covered")
417
418    def test_remove_cell_span(self):
419        self.table.set_cell_span('A1', (3, 3))
420        self.table.remove_cell_span('A1')
421        self.assertEqual((1, 1), self.table['A1'].span, "Span values for cell 'A1' should be (1, 1).")
422        self.assertFalse(self.table['B2'].covered, "cell 'B1' is covered")
423
424
425if __name__=='__main__':
426    unittest.main()
427