Commit 4b30cb79 authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

[profile gen] Drop support for SEDA 2.0 XSD export

XSD isn't able to properly export SEDA 2.0, stop supporting it all together.
parent fe959b171205
......@@ -369,215 +369,19 @@ class SEDA2ExportAdapter(EntityAdapter):
raise NotImplementedError()
class SEDA2XSDExport(SEDA2ExportAdapter):
"""Adapter to build an XSD representation of a SEDA profile, using SEDA 2.0 specification."""
__regid__ = 'SEDA-2.0.xsd'
class SEDA2RelaxNGExport(RNGMixin, SEDA2ExportAdapter):
"""Adapter to build a Relax NG representation of a SEDA profile, using SEDA 2.0 specification.
"""
__regid__ = 'SEDA-2.0.rng'
namespaces = {
None: 'fr:gouv:culture:archivesdefrance:seda:v2.0',
'seda': 'fr:gouv:culture:archivesdefrance:seda:v2.0',
'xsd': 'http://www.w3.org/2001/XMLSchema',
'xlink': 'http://www.w3.org/1999/xlink',
'rng': 'http://relaxng.org/ns/structure/1.0',
'a': 'http://relaxng.org/ns/compatibility/annotations/1.0',
}
root_attributes = {
'attributeFormDefault': 'unqualified',
'elementFormDefault': 'qualified',
'targetNamespace': 'fr:gouv:culture:archivesdefrance:seda:v2.0',
'version': '1.0',
}
def dump_etree(self):
"""Return an XSD etree for the adapted SEDA profile."""
self.defined_content_types = set()
self.root = root = self.element('xsd:schema', attributes=self.root_attributes)
self.element('xsd:import', parent=root,
attributes={'namespace': 'http://www.w3.org/XML/1998/namespace',
'schemaLocation': 'http://www.w3.org/2001/xml.xsd'})
self.element('xsd:import', parent=root,
attributes={'namespace': 'http://www.w3.org/1999/xlink',
'schemaLocation': 'http://www.w3.org/1999/xlink.xsd'})
self._dump(root)
xsd_cleanup_etree(root)
return root
def init_transfer_element(self, xselement, root, entity):
profile_element = self.element('xsd:element', root,
{'name': xselement.local_name,
'documentation': entity.user_annotation})
self.element('xsd:sequence', self.element('xsd:complexType', profile_element))
open_type = self.element('xsd:complexType', root, {'name': 'OpenType', 'abstract': 'true'})
open_type_seq = self.element('xsd:sequence', open_type)
self.element('xsd:attribute', open_type, {'ref': 'xml:id', 'use': 'optional'})
self.element('xsd:attribute', open_type, {'ref': 'xlink:href', 'use': 'optional'})
self.element('xsd:any', open_type_seq, {'namespace': '##other', 'processContents': 'lax',
'minOccurs': '0'})
return profile_element
def jumped_element(self, profile_element):
return self._parent_element(profile_element)[-1]
def element_alternative(self, occ, profile_element, target_value, to_process, card_entity):
attrs = xsd_element_cardinality(occ, card_entity)
parent_element = self._parent_element(profile_element)
target_element = self.element('xsd:choice', parent_element, attrs)
to_process[occ.target].append((target_value, target_element))
def element_sequence(self, occ, profile_element, target_value, to_process, card_entity):
attrs = xsd_element_cardinality(occ, card_entity)
parent_element = self._parent_element(profile_element)
target_element = self.element('xsd:sequence', parent_element, attrs)
to_process[occ.target].append((target_value, target_element))
def element_xmlattribute(self, occ, profile_element, target_value, to_process, card_entity):
attrs = xsd_attribute_cardinality(occ, card_entity)
q = self.qname
xpath = q('xsd:complexType') + '/' + q('xsd:simpleContent') + '/' + q('xsd:extension')
parent = profile_element.find(xpath)
if parent is None:
parent = profile_element.find(self.qname('xsd:complexType'))
assert parent is not None
xselement = occ.target
attrs['name'] = xselement.local_name
content_type = self.xsd_content_type(xselement.textual_content_type)
attrs['type'] = content_type
target_element = self.element('xsd:attribute', parent, attrs)
value = serialize(target_value)
if value is not None:
attr = self.qname('seda:profid') if xselement.local_name == 'id' else 'fixed'
target_element.attrib[attr] = value
def element_xmlelement(self, occ, profile_element, target_value, to_process, card_entity): # noqa
attrs = xsd_element_cardinality(occ, card_entity)
attrs['documentation'] = getattr(card_entity, 'user_annotation', None)
xselement = occ.target
if xselement.local_name == 'Signature':
attrs['type'] = 'OpenType'
self._target_element(xselement, profile_element, attrs)
elif isinstance(occ, dict): # fake occurence introduced for some elements'content
# target element has already been introduced: it is now given as profile_element
target_element = profile_element
try:
extension_element = target_element[0][0][0]
except IndexError:
# XXX debugging information for traceback which occured on our demo
# should disappear at some point
descendants = []
while len(target_element) and len(descendants) < 3:
descendants.append(target_element[0])
target_element = target_element[0]
self.error('Unexpected target_element: %s', descendants)
raise
self.fill_element(xselement, target_element, extension_element,
target_value, card_entity)
else:
target_element = self._target_element(xselement, profile_element, attrs)
content_type = self.xsd_content_type(xselement.textual_content_type)
if content_type:
type_element = self.element('xsd:complexType', target_element)
content_element = self.element('xsd:simpleContent', parent=type_element)
extension_element = self.element('xsd:extension', parent=content_element,
attributes={'base': content_type})
self.fill_element(xselement, target_element, extension_element,
target_value, card_entity, copy_attributes=True)
else:
type_element = self.element('xsd:complexType', target_element)
seq_element = self.element('xsd:sequence', type_element)
# target is a complex element
if getattr(target_value, 'eid', None): # value is an entity
if target_value.cw_etype == 'AuthorityRecord':
self.fill_organization_element(seq_element, target_value)
elif xselement.local_name in ('ArchivalAgency', 'TransferringAgency'):
self.fill_organization_element(seq_element, None)
elif target_value is not None:
assert False, (xselement, target_value)
if getattr(target_value, 'eid', None): # value is an entity
to_process[xselement].append((target_value, target_element))
def fill_element(self, xselement, target_element, extension_element, value, card_entity,
copy_attributes=False):
if xselement.local_name == 'KeywordType':
if value:
attrs = {'fixed': value.scheme.description or value.scheme.dc_title()}
else:
attrs = {'default': 'edition 2009'}
attrs['name'] = 'listVersionID'
self.element('xsd:attribute', attributes=attrs, parent=extension_element)
elif (xselement.local_name == 'KeywordReference' and card_entity.scheme):
self.concept_scheme_attribute(xselement, extension_element, card_entity.scheme)
elif getattr(value, 'cw_etype', None) == 'Concept':
self.concept_scheme_attribute(xselement, extension_element, value.scheme)
elif copy_attributes:
for attrname, occ in xselement.attributes.items():
if attrname in ('id', 'href') or attrname.startswith(('list', 'scheme')):
attrs = xsd_attribute_cardinality(occ, None)
attrs['name'] = attrname
attrs['type'] = self.xsd_content_type(occ.target.textual_content_type)
self.element('xsd:attribute', attributes=attrs, parent=extension_element)
fixed_value = serialize(value)
if fixed_value is not None:
attr = 'default' if _internal_reference(value) else 'fixed'
target_element.attrib[attr] = fixed_value
def concept_scheme_attribute(self, xselement, type_element, scheme):
try:
scheme_attr = xselement_scheme_attribute(xselement)
except KeyError:
return
self.element('xsd:attribute', type_element,
attributes={'name': scheme_attr,
'fixed': scheme.absolute_url()})
def fill_organization_element(self, parent_element, value):
target_element = self.element('xsd:element', parent_element, {'name': 'Identifier'})
type_element = self.element('xsd:simpleType', target_element)
restriction_element = self.element('xsd:restriction', type_element,
{'base': 'xsd:string'})
if value:
self.element('xsd:enumeration', restriction_element,
{'value': value.absolute_url()})
def _parent_element(self, profile_element):
q = self.qname
if profile_element.tag in (q('xsd:choice'), q('xsd:sequence')):
parent = profile_element
else:
xpath = q('xsd:complexType') + '/' + q('xsd:sequence')
parent = profile_element.find(xpath)
assert parent is not None
return parent
def _target_element(self, xselement, profile_element, attrs):
parent_element = self._parent_element(profile_element)
attrs['name'] = xselement.local_name
return self.element('xsd:element', parent_element, attrs)
def xsd_content_type(self, content_type):
"""Return XSD content type from pyxst `textual_content_type` that may be None, a set or a string
value.
"""
if content_type:
if isinstance(content_type, set):
# to satisfy XSD schema, we've to create an intermediary type holding the union of
# types
type_name = ''.join(sorted(content_type))
if type_name not in self.defined_content_types:
type_element = self.element('xsd:simpleType', self.root, {'name': type_name})
union_element = self.element('xsd:union', parent=type_element)
content_type = ' '.join(sorted('xsd:' + ct for ct in content_type))
union_element.attrib['memberTypes'] = content_type
self.defined_content_types.add(type_name)
return type_name
content_type = 'xsd:' + content_type
return content_type
class SEDA2RelaxNGExport(RNGMixin, SEDA2ExportAdapter):
"""Adapter to build a Relax NG representation of a SEDA profile, using SEDA 2.0 specification.
"""
__regid__ = 'SEDA-2.0.rng'
namespaces = SEDA2XSDExport.namespaces.copy()
namespaces['rng'] = 'http://relaxng.org/ns/structure/1.0'
namespaces['a'] = 'http://relaxng.org/ns/compatibility/annotations/1.0'
root_attributes = {
'ns': 'fr:gouv:culture:archivesdefrance:seda:v2.0',
......@@ -794,7 +598,7 @@ LIST_VERSION_ID_2009 = XAttr('listVersionID', 'xsd:token', '1', 'edition 2009')
LIST_VERSION_ID_2011 = XAttr('listVersionID', 'xsd:token', '1', 'edition 2011')
class SEDA1XSDExport(SEDA2XSDExport):
class SEDA1XSDExport(SEDA2ExportAdapter):
"""Adapter to build an XSD representation of a simplified SEDA profile, using SEDA 1.0
specification.
......@@ -803,7 +607,7 @@ class SEDA1XSDExport(SEDA2XSDExport):
hence the limitation to simplified profile, and a direct implementation of the export.
"""
__regid__ = 'SEDA-1.0.xsd'
__select__ = SEDA2XSDExport.__select__ & simplified_profile()
__select__ = SEDA2ExportAdapter.__select__ & simplified_profile()
namespaces = {
None: 'fr:gouv:culture:archivesdefrance:seda:v1.0',
......@@ -1469,56 +1273,6 @@ class SEDA02RNGExport(OldSEDARNGExportMixin, SEDA02XSDExport):
}
def xsd_element_cardinality(occ, card_entity):
"""Return XSD element cardinality for the given pyxst Occurence. Cardinality may be overriden by
the data model's user_cardinality value.
"""
minimum, maximum = element_minmax_cardinality(occ, card_entity)
attribs = {}
if minimum != 1:
attribs['minOccurs'] = str(minimum)
if maximum != 1:
attribs['maxOccurs'] = 'unbounded'
return attribs
def xsd_attribute_cardinality(occ, card_entity):
"""Return XSD attribute cardinality for the given pyxst Occurence. Cardinality may be overriden by
the data model's user_cardinality value.
"""
if attribute_minimum_cardinality(occ, card_entity) == 1:
return {'use': 'required'}
else:
return {'use': 'optional'}
def xsd_cleanup_etree(element):
"""Cleanup given XSD element tree.
* forces attributes to be defined after sequence/choices (enforced by XSD schema),
* remove empty sequence/choice,
* skip sequence/choice with only one child and which are either not a complexType child or their
unique children is itself a sequence or choice.
"""
for subelement in list(element):
xsd_cleanup_etree(subelement)
if subelement.tag == '{http://www.w3.org/2001/XMLSchema}attribute':
element.remove(subelement)
element.append(subelement)
elif (subelement.tag in ('{http://www.w3.org/2001/XMLSchema}sequence',
'{http://www.w3.org/2001/XMLSchema}choice',
'{http://www.w3.org/2001/XMLSchema}complexType')
and len(subelement) == 0):
element.remove(subelement)
elif (subelement.tag in ('{http://www.w3.org/2001/XMLSchema}sequence',
'{http://www.w3.org/2001/XMLSchema}choice')
and len(subelement) == 1
and (element.tag != '{http://www.w3.org/2001/XMLSchema}complexType'
or subelement[0].tag in ('{http://www.w3.org/2001/XMLSchema}sequence',
'{http://www.w3.org/2001/XMLSchema}choice'))):
element.replace(subelement, subelement[0])
def _path_target_values(entity, path):
"""Given an entity and a path to traverse, return the list of (entity, value) at the end of the
path.
......
......@@ -312,7 +312,7 @@ class PathTargetValuesTC(CubicWebTC):
self.assertEqual(target_value[1].eid, bdo.eid)
class SEDA2ExportTCMixIn(object):
class SEDA2RNGExportTC(RelaxNGTestMixin, CubicWebTC):
def test_skipped_mandatory_simple(self):
with self.admin_access.client_cnx() as cnx:
......@@ -533,30 +533,6 @@ class SEDA2ExportTCMixIn(object):
'type': 'xsd:token',
'fixed': 'md5'})
class SEDA2XSDExportTC(SEDA2ExportTCMixIn, XMLSchemaTestMixin, CubicWebTC):
def assertOpenTypeIsDefined(self, profile):
open_types = self.xpath(profile, '//xs:complexType[@name="OpenType"]')
self.assertEqual(len(open_types), 1)
def test_organization(self):
"""Check that an agent is exported as expected in a SEDA profile."""
with self.admin_access.client_cnx() as cnx:
transfer = cnx.create_entity('SEDAArchiveTransfer', title=u'test profile')
archival_org = testutils.create_authority_record(
cnx, u'Archival inc.', reverse_seda_archival_agency=transfer)
profile = self.profile_etree(transfer)
enum_elts = self.xpath(profile,
'//xs:element[@name="ArchivalAgency"]/xs:complexType'
'/xs:sequence/xs:element/xs:simpleType/xs:restriction'
'/xs:enumeration')
self.assertEqual(len(enum_elts), 1)
self.assertEqual(enum_elts[0].attrib['value'], archival_org.absolute_url())
class SEDA2RNGExportTC(SEDA2ExportTCMixIn, RelaxNGTestMixin, CubicWebTC):
def assertOpenTypeIsDefined(self, profile):
open_types = self.xpath(profile, '//rng:define[@name="OpenType"]')
self.assertEqual(len(open_types), 1)
......@@ -656,34 +632,6 @@ class SEDAExportFuncTCMixIn(object):
self.assertProfileDetails(root)
class SEDAXSDExportFuncTC(SEDAExportFuncTCMixIn, XMLSchemaTestMixin, CubicWebTC):
def assertProfileDetails(self, root):
# ensure profile's temporary id are exported in custom seda:profid attribute
self.assertEqual(len(self.xpath(root, '//xs:attribute[@seda:profid]')), 2)
# ensure they are properly referenced using 'default' attribute
xmlid = eid2xmlid(self.bdo_eid)
references = self.xpath(root, '//xs:element[@default="{}"]'.format(xmlid))
self.assertEqual(len(references), 1)
self.assertEqual(references[0].attrib['name'], 'DataObjectReferenceId')
# ensure optional id are properly reinjected
references = self.xpath(root,
'//xs:element[@name="Keyword"]'
'//xs:attribute[@name="id" and @use="optional"]')
self.assertEqual(len(references), 1)
# ensure custodial item content type is properly serialized
chi = self.xpath(root, '//xs:element[@name="CustodialHistoryItem"]')
self.assertEqual(len(chi), 1)
self.assertXSDAttributes(
chi[0],
[{'name': 'when', 'use': 'optional', 'type': 'datedateTime'}])
# ensure types union handling
ddt = self.xpath(root, '//xs:simpleType[@name="datedateTime"]')
self.assertEqual(len(ddt), 1)
self.assertEqual(ddt[0][0].tag, '{http://www.w3.org/2001/XMLSchema}union')
self.assertEqual(ddt[0][0].attrib, {'memberTypes': 'xsd:date xsd:dateTime'})
class SEDARNGExportFuncTC(SEDAExportFuncTCMixIn, RelaxNGTestMixin, CubicWebTC):
def assertProfileDetails(self, root):
......
......@@ -279,7 +279,6 @@ class ArchiveTransferExportTC(CubicWebTC):
req.cnx.commit()
for version, fmt, expected_filename in (
('2.0', 'rng', 'diagnosis testing-2.0.rng'),
('2.0', 'xsd', 'diagnosis testing-2.0.xsd'),
('2.0', 'html', 'diagnosis testing-2.0.html'),
('1.0', 'rng', 'diagnosis testing-1.0.rng'),
('1.0', 'xsd', 'diagnosis testing-1.0.xsd'),
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment