ods: use well-known prefixes in export (#18726)

Some versions of LibreOffice Calc cannot handle namespaces in other
prefixes :/
This commit is contained in:
Frédéric Péters 2017-09-15 13:50:44 +02:00
parent 1bdb9d6e11
commit ab879ada51
2 changed files with 78 additions and 73 deletions

View File

@ -822,38 +822,38 @@ def test_backoffice_ods(pub):
zipf = zipfile.ZipFile(StringIO.StringIO(resp.body))
ods_sheet = ET.parse(zipf.open('content.xml'))
# check the ods contains a link to the document
elem = ods_sheet.findall('.//{%s}a' % ods.TEXT_NS)[0]
assert elem.attrib['{%s}href' % ods.XLINK_NS] == 'http://example.net/backoffice/management/form-title/%s/files/4/bar' % formdata.id
resp = app.get(elem.attrib['{%s}href' % ods.XLINK_NS])
elem = ods_sheet.findall('.//{%s}a' % ods.NS['text'])[0]
assert elem.attrib['{%s}href' % ods.NS['xlink']] == 'http://example.net/backoffice/management/form-title/%s/files/4/bar' % formdata.id
resp = app.get(elem.attrib['{%s}href' % ods.NS['xlink']])
assert resp.body == 'hello world'
all_texts = [x.text for x in ods_sheet.findall('.//{%s}table-row//{%s}p' % (ods.TABLE_NS, ods.TEXT_NS))]
all_texts = [x.text for x in ods_sheet.findall('.//{%s}table-row//{%s}p' % (ods.NS['table'], ods.NS['text']))]
created_column = all_texts.index('Created')
date_column = all_texts.index('date field')
number_column = all_texts.index('number field')
phone_column = all_texts.index('phone field')
for row in ods_sheet.findall('.//{%s}table-row' % ods.TABLE_NS):
for row in ods_sheet.findall('.//{%s}table-row' % ods.NS['table']):
if row.findall('.//{%s}table-cell/{%s}p' % (
ods.TABLE_NS, ods.TEXT_NS))[0].text == formdata.get_display_id():
ods.NS['table'], ods.NS['text']))[0].text == formdata.get_display_id():
break
else:
assert False, 'failed to find data row'
assert row.findall('.//{%s}table-cell' % ods.TABLE_NS)[created_column].attrib[
'{%s}value-type' % ods.OFFICE_NS] == 'date'
assert row.findall('.//{%s}table-cell' % ods.TABLE_NS)[created_column].attrib[
'{%s}style-name' % ods.TABLE_NS] == 'DateTime'
assert row.findall('.//{%s}table-cell' % ods.TABLE_NS)[date_column].attrib[
'{%s}value-type' % ods.OFFICE_NS] == 'date'
assert row.findall('.//{%s}table-cell' % ods.TABLE_NS)[date_column].attrib[
'{%s}style-name' % ods.TABLE_NS] == 'Date'
assert row.findall('.//{%s}table-cell' % ods.TABLE_NS)[number_column].attrib[
'{%s}value-type' % ods.OFFICE_NS] == 'float'
assert row.findall('.//{%s}table-cell' % ods.TABLE_NS)[number_column].attrib[
'{%s}value' % ods.OFFICE_NS] == '12345'
assert row.findall('.//{%s}table-cell' % ods.TABLE_NS)[phone_column].attrib[
'{%s}value-type' % ods.OFFICE_NS] == 'string'
assert row.findall('.//{%s}table-cell' % ods.NS['table'])[created_column].attrib[
'{%s}value-type' % ods.NS['office']] == 'date'
assert row.findall('.//{%s}table-cell' % ods.NS['table'])[created_column].attrib[
'{%s}style-name' % ods.NS['table']] == 'DateTime'
assert row.findall('.//{%s}table-cell' % ods.NS['table'])[date_column].attrib[
'{%s}value-type' % ods.NS['office']] == 'date'
assert row.findall('.//{%s}table-cell' % ods.NS['table'])[date_column].attrib[
'{%s}style-name' % ods.NS['table']] == 'Date'
assert row.findall('.//{%s}table-cell' % ods.NS['table'])[number_column].attrib[
'{%s}value-type' % ods.NS['office']] == 'float'
assert row.findall('.//{%s}table-cell' % ods.NS['table'])[number_column].attrib[
'{%s}value' % ods.NS['office']] == '12345'
assert row.findall('.//{%s}table-cell' % ods.NS['table'])[phone_column].attrib[
'{%s}value-type' % ods.NS['office']] == 'string'
def test_backoffice_statistics(pub):
create_superuser(pub)

View File

@ -23,13 +23,18 @@ import zipfile
from .misc import date_format, datetime_format
from .evalutils import make_date, make_datetime
FO_NS = 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'
OFFICE_NS = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
STYLE_NS = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'
NUMBER_NS = 'urn:oasis:names:tc:opendocument:xmlns:datastyle: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'
NS = {
'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
'office': 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
'xlink': 'http://www.w3.org/1999/xlink',
}
for prefix, uri in NS.items():
ET.register_namespace(prefix, uri)
def is_number(value):
@ -53,51 +58,51 @@ class Workbook(object):
return sheet
def get_content_node(self):
root = ET.Element('{%s}document-content' % OFFICE_NS)
ET.SubElement(root, '{%s}scripts' % OFFICE_NS)
ET.SubElement(root, '{%s}font-face-decls' % OFFICE_NS)
root = ET.Element('{%s}document-content' % NS['office'])
ET.SubElement(root, '{%s}scripts' % NS['office'])
ET.SubElement(root, '{%s}font-face-decls' % NS['office'])
body = ET.SubElement(root, '{%s}body' % OFFICE_NS)
spreadsheet = ET.SubElement(body, '{%s}spreadsheet' % OFFICE_NS)
body = ET.SubElement(root, '{%s}body' % NS['office'])
spreadsheet = ET.SubElement(body, '{%s}spreadsheet' % NS['office'])
for sheet in self.sheets:
spreadsheet.append(sheet.get_node())
return root
def get_styles_node(self):
root = ET.Element('{%s}document-styles' % OFFICE_NS)
ET.SubElement(root, '{%s}font-face-decls' % OFFICE_NS)
automatic_styles = ET.SubElement(root, '{%s}styles' % OFFICE_NS)
style = ET.SubElement(automatic_styles, '{%s}style' % STYLE_NS)
style.attrib['{%s}name' % STYLE_NS] = 'Default'
root = ET.Element('{%s}document-styles' % NS['office'])
ET.SubElement(root, '{%s}font-face-decls' % NS['office'])
automatic_styles = ET.SubElement(root, '{%s}styles' % NS['office'])
style = ET.SubElement(automatic_styles, '{%s}style' % NS['style'])
style.attrib['{%s}name' % NS['style']] = 'Default'
def define_date_style(name, strftime_string):
node = ET.SubElement(automatic_styles, '{%s}date-style' % NUMBER_NS)
node.attrib['{%s}name' % STYLE_NS] = name + 'NumberFormat'
node = ET.SubElement(automatic_styles, '{%s}date-style' % NS['number'])
node.attrib['{%s}name' % NS['style']] = name + 'NumberFormat'
for part in re.findall(r'%?.', strftime_string):
if part == '%Y':
ET.SubElement(node, '{%s}year' % NUMBER_NS)
ET.SubElement(node, '{%s}year' % NS['number'])
elif part == '%m':
ET.SubElement(node, '{%s}month' % NUMBER_NS)
ET.SubElement(node, '{%s}month' % NS['number'])
elif part == '%d':
ET.SubElement(node, '{%s}day' % NUMBER_NS)
ET.SubElement(node, '{%s}day' % NS['number'])
elif part == '%H':
ET.SubElement(node, '{%s}hours' % NUMBER_NS)
ET.SubElement(node, '{%s}hours' % NS['number'])
elif part == '%M':
ET.SubElement(node, '{%s}minutes' % NUMBER_NS)
ET.SubElement(node, '{%s}minutes' % NS['number'])
elif part == '%S':
ET.SubElement(node, '{%s}seconds' % NUMBER_NS)
ET.SubElement(node, '{%s}seconds' % NS['number'])
else:
ET.SubElement(node, '{%s}text' % NUMBER_NS).text = part
style = ET.SubElement(automatic_styles, '{%s}style' % STYLE_NS)
style.attrib['{%s}name' % STYLE_NS] = name
style.attrib['{%s}family' % STYLE_NS] = 'table-cell'
style.attrib['{%s}data-style-name' % STYLE_NS] = name + 'NumberFormat'
style.attrib['{%s}parent-style' % STYLE_NS] = 'Default'
ET.SubElement(node, '{%s}text' % NS['number']).text = part
style = ET.SubElement(automatic_styles, '{%s}style' % NS['style'])
style.attrib['{%s}name' % NS['style']] = name
style.attrib['{%s}family' % NS['style']] = 'table-cell'
style.attrib['{%s}data-style-name' % NS['style']] = name + 'NumberFormat'
style.attrib['{%s}parent-style' % NS['style']] = 'Default'
style = ET.SubElement(automatic_styles, '{%s}style' % STYLE_NS)
style.attrib['{%s}name' % STYLE_NS] = name + 'Column'
style.attrib['{%s}family' % STYLE_NS] = 'table-column'
ET.SubElement(style, '{%s}table-column-properties' % STYLE_NS).attrib['{%s}column-width' % STYLE_NS] = '80mm'
style = ET.SubElement(automatic_styles, '{%s}style' % NS['style'])
style.attrib['{%s}name' % NS['style']] = name + 'Column'
style.attrib['{%s}family' % NS['style']] = 'table-column'
ET.SubElement(style, '{%s}table-column-properties' % NS['style']).attrib['{%s}column-width' % NS['style']] = '80mm'
define_date_style('Date', date_format())
define_date_style('DateTime', datetime_format())
@ -138,15 +143,15 @@ class WorkSheet(object):
self.cells[row][column] = WorkCell(self, value, **kwargs)
def get_node(self):
root = ET.Element('{%s}table' % TABLE_NS)
root.attrib['{%s}name' % TABLE_NS] = self.name
ET.SubElement(root, '{%s}table-column' % TABLE_NS)
root = ET.Element('{%s}table' % NS['table'])
root.attrib['{%s}name' % NS['table']] = self.name
ET.SubElement(root, '{%s}table-column' % NS['table'])
for i in range(0, max(self.cells.keys())+1):
row = ET.SubElement(root, '{%s}table-row' % TABLE_NS)
row = ET.SubElement(root, '{%s}table-row' % NS['table'])
for j in range(0, max(self.cells.get(i).keys())+1):
cell = self.cells.get(i, {}).get(j, None)
if not cell:
ET.SubElement(row, '{%s}table-cell' % TABLE_NS)
ET.SubElement(row, '{%s}table-cell' % NS['table'])
else:
row.append(cell.get_node())
return root
@ -165,9 +170,9 @@ class WorkCell(object):
self.native_value = native_value
def get_node(self):
root = ET.Element('{%s}table-cell' % TABLE_NS)
root.attrib['{%s}value-type' % OFFICE_NS] = 'string'
p = ET.SubElement(root, '{%s}p' % TEXT_NS)
root = ET.Element('{%s}table-cell' % NS['table'])
root.attrib['{%s}value-type' % NS['office']] = 'string'
p = ET.SubElement(root, '{%s}p' % NS['text'])
p.text = self.value
if not self.data_field:
return root
@ -179,22 +184,22 @@ class WorkCell(object):
url = '%sfiles/%s/%s' % (
self.formdata.get_url(backoffice=True),
self.data_field.id, self.native_value.base_filename)
a = ET.SubElement(p, '{%s}a' % TEXT_NS)
a.attrib['{%s}href' % XLINK_NS] = url
a = ET.SubElement(p, '{%s}a' % NS['text'])
a.attrib['{%s}href' % NS['xlink']] = url
a.text = self.native_value.base_filename
p.text = None
elif self.data_field.type in ('time', 'last_update_time'):
root.attrib['{%s}style-name' % TABLE_NS] = 'DateTime'
root.attrib['{%s}value-type' % OFFICE_NS] = 'date'
root.attrib['{%s}date-value' % OFFICE_NS] = make_datetime(self.native_value).strftime('%Y-%m-%dT%H:%M:%S')
root.attrib['{%s}style-name' % NS['table']] = 'DateTime'
root.attrib['{%s}value-type' % NS['office']] = 'date'
root.attrib['{%s}date-value' % NS['office']] = make_datetime(self.native_value).strftime('%Y-%m-%dT%H:%M:%S')
p.text = make_datetime(self.native_value).strftime(datetime_format())
elif self.data_field.type == 'date':
root.attrib['{%s}style-name' % TABLE_NS] = 'Date'
root.attrib['{%s}value-type' % OFFICE_NS] = 'date'
root.attrib['{%s}date-value' % OFFICE_NS] = make_date(self.native_value).strftime('%Y-%m-%d')
root.attrib['{%s}style-name' % NS['table']] = 'Date'
root.attrib['{%s}value-type' % NS['office']] = 'date'
root.attrib['{%s}date-value' % NS['office']] = make_date(self.native_value).strftime('%Y-%m-%d')
p.text = make_datetime(self.native_value).strftime(date_format())
elif is_number(self.value):
root.attrib['{%s}value-type' % OFFICE_NS] = 'float'
root.attrib['{%s}value' % OFFICE_NS] = str(self.value)
root.attrib['{%s}value-type' % NS['office']] = 'float'
root.attrib['{%s}value' % NS['office']] = str(self.value)
return root