correctly export numbers to ODS (fixes #28058)

This commit is contained in:
Benjamin Dauvergne 2018-11-15 16:34:14 +01:00
parent 5abfc1d236
commit 3eba242597
4 changed files with 80 additions and 5 deletions

View File

@ -16,15 +16,27 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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]

View File

@ -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):

View File

@ -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

View File

@ -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