[profile gen] Drop support for SEDA 2.0 XSD export
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 29 Mar 2017 10:22:08 +0200
changeset 2546 377976b673e0
parent 2545 fe959b171205
child 2547 370716d04b28
[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.
cubicweb_seda/entities/profile_generation.py
test/test_profile_generation.py
test/test_views.py
--- a/cubicweb_seda/entities/profile_generation.py	Tue Mar 28 16:57:57 2017 +0200
+++ b/cubicweb_seda/entities/profile_generation.py	Wed Mar 29 10:22:08 2017 +0200
@@ -369,216 +369,20 @@
         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',
-    }
-    root_attributes = {
-        'attributeFormDefault': 'unqualified',
-        'elementFormDefault': 'qualified',
-        'targetNamespace': 'fr:gouv:culture:archivesdefrance:seda:v2.0',
-        'version': '1.0',
+        'rng': 'http://relaxng.org/ns/structure/1.0',
+        'a': 'http://relaxng.org/ns/compatibility/annotations/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',
         'datatypeLibrary': 'http://www.w3.org/2001/XMLSchema-datatypes',
@@ -794,7 +598,7 @@
 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 @@
     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 @@
     }
 
 
-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.
--- a/test/test_profile_generation.py	Tue Mar 28 16:57:57 2017 +0200
+++ b/test/test_profile_generation.py	Wed Mar 29 10:22:08 2017 +0200
@@ -312,7 +312,7 @@
             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 @@
                                                   '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 @@
         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):
--- a/test/test_views.py	Tue Mar 28 16:57:57 2017 +0200
+++ b/test/test_views.py	Wed Mar 29 10:22:08 2017 +0200
@@ -279,7 +279,6 @@
             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'),