From 3eba242597639425094fe3e6a44202aab9089592 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 15 Nov 2018 16:34:14 +0100 Subject: [PATCH] correctly export numbers to ODS (fixes #28058) --- bijoe/visualization/ods.py | 22 +++++++++++++++++--- bijoe/visualization/utils.py | 2 +- tests/test_schema1.py | 22 +++++++++++++++++++- tests/utils.py | 39 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/bijoe/visualization/ods.py b/bijoe/visualization/ods.py index fe81930..1b0e964 100644 --- a/bijoe/visualization/ods.py +++ b/bijoe/visualization/ods.py @@ -16,15 +16,27 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import sys + import zipfile import xml.etree.ElementTree as ET +from django.utils.encoding import force_text + + OFFICE_NS = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0' TABLE_NS = 'urn:oasis:names:tc:opendocument:xmlns:table:1.0' TEXT_NS = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0' XLINK_NS = 'http://www.w3.org/1999/xlink' +def is_number(x): + if sys.version_info >= (3, 0): + return isinstance(x, (int, float)) + else: + return isinstance(x, (int, long, float)) + + class Workbook(object): def __init__(self, encoding='utf-8'): self.sheets = [] @@ -94,17 +106,21 @@ class WorkSheet(object): class WorkCell(object): def __init__(self, worksheet, value, hint=None): + self.value_type = 'string' + if is_number(value): + self.value_type = 'float' if value is None: value = '' - if type(value) is not unicode: - value = unicode(value, 'utf-8') + value = force_text(value) self.value = value self.worksheet = worksheet self.hint = hint def get_node(self): root = ET.Element('{%s}table-cell' % TABLE_NS) - root.attrib['{%s}value-type' % OFFICE_NS] = 'string' + root.attrib['{%s}value-type' % OFFICE_NS] = self.value_type + if self.value_type == 'float': + root.attrib['{%s}value' % OFFICE_NS] = self.value p = ET.SubElement(root, '{%s}p' % TEXT_NS) if self.hint == 'uri': base_filename = self.value.split('/')[-1] diff --git a/bijoe/visualization/utils.py b/bijoe/visualization/utils.py index db6077c..13d21b5 100644 --- a/bijoe/visualization/utils.py +++ b/bijoe/visualization/utils.py @@ -277,7 +277,7 @@ class Visualization(object): sheet.write(0, 0, full_title) for j, row in enumerate(table.table()): for i, value in enumerate(row): - sheet.write(j + 1, i, unicode(0 if value is None else value)) + sheet.write(j + 1, i, 0 if value is None else value) return workbook def title(self): diff --git a/tests/test_schema1.py b/tests/test_schema1.py index 72074b1..63f91ec 100644 --- a/tests/test_schema1.py +++ b/tests/test_schema1.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- -from utils import login, get_table +from utils import login, get_table, get_ods_table, get_ods_document + +from bijoe.visualization.ods import OFFICE_NS, TABLE_NS def test_simple(schema1, app, admin): @@ -99,3 +101,21 @@ def test_yearmonth_drilldown(schema1, app, admin): '04/2017', '05/2017', '06/2017', '07/2017', '08/2017'], ['number of rows', '10', '1', '1', '1', '1', '1', '1', '1'] ] + + +def test_ods(schema1, app, admin): + login(app, admin) + response = app.get('/').follow() + response = response.click('Facts 1') + form = response.form + form.set('representation', 'table') + form.set('measure', 'simple_count') + form.set('drilldown_x', 'innersubcategory') + response = form.submit('visualize') + assert 'big-msg-info' not in response + ods_response = response.form.submit('ods') + # skip first line of ODS table as it's a header not present in the HTML display + assert get_table(response) == get_ods_table(ods_response)[1:] + root = get_ods_document(ods_response) + nodes = root.findall('.//{%s}table-cell' % TABLE_NS) + assert len([node for node in nodes if node.attrib['{%s}value-type' % OFFICE_NS] == 'float']) == 2 diff --git a/tests/utils.py b/tests/utils.py index 4a6956e..9e75194 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,7 @@ +import io +import zipfile +import xml.etree.ElementTree as ET + from django.conf import settings @@ -29,3 +33,38 @@ def get_table(response): row.append((td.text or '').strip()) return table + +def xml_node_text_content(node): + '''Extract text content from node and all its children. Equivalent to + xmlNodeGetContent from libxml.''' + + if node is None: + return '' + + def helper(node): + s = [] + if node.text: + s.append(node.text) + for child in node: + s.extend(helper(child)) + if child.tail: + s.append(child.tail) + return s + return u''.join(helper(node)) + + +def get_ods_document(response): + return ET.fromstring(zipfile.ZipFile(io.BytesIO(response.content)).read('content.xml')) + + +def get_ods_table(response): + from bijoe.visualization.ods import TABLE_NS + + root = get_ods_document(response) + table = [] + for row_node in root.findall('.//{%s}table-row' % TABLE_NS): + row = [] + table.append(row) + for cell_node in row_node.findall('.//{%s}table-cell' % TABLE_NS): + row.append(xml_node_text_content(cell_node)) + return table -- 2.20.1