[PATCH 2 of 6 seda] [profile gen] Drop support for SEDA 2.0 XSD export

Sylvain Thenault sylvain.thenault at logilab.fr
Wed Mar 29 10:28:37 CEST 2017


# HG changeset patch
# User Sylvain Thénault <sylvain.thenault at logilab.fr>
# Date 1490775728 -7200
#      Wed Mar 29 10:22:08 2017 +0200
# Node ID 3ef91b71cd859096c08fa1e27117e87f7836c0c0
# Parent  cad5126a0335566eeb71b8e15d01b831a3307ee6
[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.

diff --git a/cubicweb_seda/entities/profile_generation.py b/cubicweb_seda/entities/profile_generation.py
--- a/cubicweb_seda/entities/profile_generation.py
+++ b/cubicweb_seda/entities/profile_generation.py
@@ -367,220 +367,24 @@ class SEDA2ExportAdapter(EntityAdapter):
     def jumped_element(self, profile_element):
         """Return the last generated element, for insertion of its content."""
         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',
     }
 
@@ -792,20 +596,20 @@ class XAttr(namedtuple('_XAttr', ['name'
 
 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.
 
     The SEDA2XSDExport implementation may be driven by the SEDA 2.0 XSD model because it's used as
     the basis for the Yams model generation. We can't do the same thing with lower version of SEDA,
     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',
         'xsd': 'http://www.w3.org/2001/XMLSchema',
         'qdt': 'fr:gouv:culture:archivesdefrance:seda:v1.0:QualifiedDataType:1',
@@ -1467,60 +1271,10 @@ class SEDA02RNGExport(OldSEDARNGExportMi
         'ns': 'fr:gouv:ae:archive:draft:standard_echange_v0.2',
         'datatypeLibrary': 'http://www.w3.org/2001/XMLSchema-datatypes',
     }
 
 
-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.
 
     Values may be entities or final value if the last relation in the path is an attribute. Values
diff --git a/test/test_profile_generation.py b/test/test_profile_generation.py
--- a/test/test_profile_generation.py
+++ b/test/test_profile_generation.py
@@ -310,11 +310,11 @@ class PathTargetValuesTC(CubicWebTC):
             target_value = target_values[0]
             self.assertEqual(target_value[0], None)
             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:
             profile = self.profile_etree(cnx.create_entity('SEDAArchiveTransfer',
                                                            title=u'test profile'))
@@ -531,34 +531,10 @@ class SEDA2ExportTCMixIn(object):
             self.assertAttributeDefinition(algo, {'name': 'algorithm',
                                                   'use': 'required',
                                                   '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)
 
     def test_data_duplicates(self):
@@ -654,38 +630,10 @@ class SEDAExportFuncTCMixIn(object):
         # ensure element with skipped value are not there
         self.assertEqual(len(self.get_elements(root, 'TransactedDate')), 0)
         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):
         # ensure profile's temporary id are exported in custom seda:profid attribute
         self.assertEqual(len(self.xpath(root, '//rng:attribute[@seda:profid]')), 2)
diff --git a/test/test_views.py b/test/test_views.py
--- a/test/test_views.py
+++ b/test/test_views.py
@@ -277,11 +277,10 @@ class ArchiveTransferExportTC(CubicWebTC
                                          title=u'diagnosis testing',
                                          simplified_profile=True)
             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'),
             ):
                 req.form['version'], req.form['format'] = version, fmt


More information about the saem-devel mailing list