0001-correctly-export-numbers-to-ODS-fixes-28058.patch
bijoe/visualization/ods.py | ||
---|---|---|
16 | 16 |
# You should have received a copy of the GNU Affero General Public License |
17 | 17 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 | |
19 |
import sys |
|
20 | ||
19 | 21 |
import zipfile |
20 | 22 |
import xml.etree.ElementTree as ET |
21 | 23 | |
24 |
from django.utils.encoding import force_text |
|
25 | ||
26 | ||
22 | 27 |
OFFICE_NS = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0' |
23 | 28 |
TABLE_NS = 'urn:oasis:names:tc:opendocument:xmlns:table:1.0' |
24 | 29 |
TEXT_NS = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0' |
25 | 30 |
XLINK_NS = 'http://www.w3.org/1999/xlink' |
26 | 31 | |
27 | 32 | |
33 |
def is_number(x): |
|
34 |
if sys.version_info >= (3, 0): |
|
35 |
return isinstance(x, (int, float)) |
|
36 |
else: |
|
37 |
return isinstance(x, (int, long, float)) |
|
38 | ||
39 | ||
28 | 40 |
class Workbook(object): |
29 | 41 |
def __init__(self, encoding='utf-8'): |
30 | 42 |
self.sheets = [] |
... | ... | |
94 | 106 | |
95 | 107 |
class WorkCell(object): |
96 | 108 |
def __init__(self, worksheet, value, hint=None): |
109 |
self.value_type = 'string' |
|
110 |
if is_number(value): |
|
111 |
self.value_type = 'float' |
|
97 | 112 |
if value is None: |
98 | 113 |
value = '' |
99 |
if type(value) is not unicode: |
|
100 |
value = unicode(value, 'utf-8') |
|
114 |
value = force_text(value) |
|
101 | 115 |
self.value = value |
102 | 116 |
self.worksheet = worksheet |
103 | 117 |
self.hint = hint |
104 | 118 | |
105 | 119 |
def get_node(self): |
106 | 120 |
root = ET.Element('{%s}table-cell' % TABLE_NS) |
107 |
root.attrib['{%s}value-type' % OFFICE_NS] = 'string' |
|
121 |
root.attrib['{%s}value-type' % OFFICE_NS] = self.value_type |
|
122 |
if self.value_type == 'float': |
|
123 |
root.attrib['{%s}value' % OFFICE_NS] = self.value |
|
108 | 124 |
p = ET.SubElement(root, '{%s}p' % TEXT_NS) |
109 | 125 |
if self.hint == 'uri': |
110 | 126 |
base_filename = self.value.split('/')[-1] |
bijoe/visualization/utils.py | ||
---|---|---|
277 | 277 |
sheet.write(0, 0, full_title) |
278 | 278 |
for j, row in enumerate(table.table()): |
279 | 279 |
for i, value in enumerate(row): |
280 |
sheet.write(j + 1, i, unicode(0 if value is None else value))
|
|
280 |
sheet.write(j + 1, i, 0 if value is None else value)
|
|
281 | 281 |
return workbook |
282 | 282 | |
283 | 283 |
def title(self): |
tests/test_schema1.py | ||
---|---|---|
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 | |
3 |
from utils import login, get_table |
|
3 |
from utils import login, get_table, get_ods_table, get_ods_document |
|
4 | ||
5 |
from bijoe.visualization.ods import OFFICE_NS, TABLE_NS |
|
4 | 6 | |
5 | 7 | |
6 | 8 |
def test_simple(schema1, app, admin): |
... | ... | |
99 | 101 |
'04/2017', '05/2017', '06/2017', '07/2017', '08/2017'], |
100 | 102 |
['number of rows', '10', '1', '1', '1', '1', '1', '1', '1'] |
101 | 103 |
] |
104 | ||
105 | ||
106 |
def test_ods(schema1, app, admin): |
|
107 |
login(app, admin) |
|
108 |
response = app.get('/').follow() |
|
109 |
response = response.click('Facts 1') |
|
110 |
form = response.form |
|
111 |
form.set('representation', 'table') |
|
112 |
form.set('measure', 'simple_count') |
|
113 |
form.set('drilldown_x', 'innersubcategory') |
|
114 |
response = form.submit('visualize') |
|
115 |
assert 'big-msg-info' not in response |
|
116 |
ods_response = response.form.submit('ods') |
|
117 |
# skip first line of ODS table as it's a header not present in the HTML display |
|
118 |
assert get_table(response) == get_ods_table(ods_response)[1:] |
|
119 |
root = get_ods_document(ods_response) |
|
120 |
nodes = root.findall('.//{%s}table-cell' % TABLE_NS) |
|
121 |
assert len([node for node in nodes if node.attrib['{%s}value-type' % OFFICE_NS] == 'float']) == 2 |
tests/utils.py | ||
---|---|---|
1 |
import io |
|
2 |
import zipfile |
|
3 |
import xml.etree.ElementTree as ET |
|
4 | ||
1 | 5 |
from django.conf import settings |
2 | 6 | |
3 | 7 | |
... | ... | |
29 | 33 |
row.append((td.text or '').strip()) |
30 | 34 |
return table |
31 | 35 | |
36 | ||
37 |
def xml_node_text_content(node): |
|
38 |
'''Extract text content from node and all its children. Equivalent to |
|
39 |
xmlNodeGetContent from libxml.''' |
|
40 | ||
41 |
if node is None: |
|
42 |
return '' |
|
43 | ||
44 |
def helper(node): |
|
45 |
s = [] |
|
46 |
if node.text: |
|
47 |
s.append(node.text) |
|
48 |
for child in node: |
|
49 |
s.extend(helper(child)) |
|
50 |
if child.tail: |
|
51 |
s.append(child.tail) |
|
52 |
return s |
|
53 |
return u''.join(helper(node)) |
|
54 | ||
55 | ||
56 |
def get_ods_document(response): |
|
57 |
return ET.fromstring(zipfile.ZipFile(io.BytesIO(response.content)).read('content.xml')) |
|
58 | ||
59 | ||
60 |
def get_ods_table(response): |
|
61 |
from bijoe.visualization.ods import TABLE_NS |
|
62 | ||
63 |
root = get_ods_document(response) |
|
64 |
table = [] |
|
65 |
for row_node in root.findall('.//{%s}table-row' % TABLE_NS): |
|
66 |
row = [] |
|
67 |
table.append(row) |
|
68 |
for cell_node in row_node.findall('.//{%s}table-cell' % TABLE_NS): |
|
69 |
row.append(xml_node_text_content(cell_node)) |
|
70 |
return table |
|
32 |
- |