correctly export numbers to ODS (fixes #28058)
This commit is contained in:
parent
5abfc1d236
commit
3eba242597
|
@ -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]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue