[profile gen] Support for multiple concepts on mime type and format id
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 12 Oct 2017 12:52:30 +0200
changeset 2760 cc3b3e0f6d42
parent 2758 a9d8ac0426d6
child 2762 1d2ccc92ac3c
[profile gen] Support for multiple concepts on mime type and format id since this may now occurs with values deducted from file category. In such case, generates proper rng:choices / xsd:enumeration. Related to #36331831
cubicweb_seda/entities/custom.py
cubicweb_seda/entities/profile_generation.py
test/data/seda_02_export.rng
test/data/seda_02_export.xsd
test/data/seda_1_export.rng
test/data/seda_1_export.xsd
test/test_profile_generation.py
--- a/cubicweb_seda/entities/custom.py	Thu Oct 12 09:39:54 2017 +0200
+++ b/cubicweb_seda/entities/custom.py	Thu Oct 12 12:52:30 2017 +0200
@@ -345,8 +345,8 @@
 class SEDAFormatId(generated.SEDAFormatId):
 
     @property
-    def concept(self):
-        return self.seda_format_id_to[0] if self.seda_format_id_to else None
+    def concepts(self):
+        return self.seda_format_id_to
 
 
 class SEDAEncoding(generated.SEDAEncoding):
@@ -359,8 +359,8 @@
 class SEDAMimeType(generated.SEDAMimeType):
 
     @property
-    def concept(self):
-        return self.seda_mime_type_to[0] if self.seda_mime_type_to else None
+    def concepts(self):
+        return self.seda_mime_type_to
 
 
 class SEDAType(generated.SEDAType):
--- a/cubicweb_seda/entities/profile_generation.py	Thu Oct 12 09:39:54 2017 +0200
+++ b/cubicweb_seda/entities/profile_generation.py	Thu Oct 12 12:52:30 2017 +0200
@@ -230,7 +230,12 @@
             datatype = 'string'
         type_attrs = {'type': datatype}
         if fixed_value is not None:
-            self.element('rng:value', element, type_attrs, text=fixed_value)
+            if isinstance(fixed_value, list):
+                choice = self.element('rng:choice', element)
+                for value in fixed_value:
+                    self.element('rng:value', choice, type_attrs, text=value)
+            else:
+                self.element('rng:value', element, type_attrs, text=fixed_value)
         elif default_value is not None:
             element.attrib[self.qname('a:defaultValue')] = default_value
             self.element('rng:data', element, type_attrs)
@@ -548,6 +553,8 @@
         # of label of its concept value
         if value is not None and xselement.local_name == 'KeywordReference':
             fixed_value = self.cwuri_url(value)
+        elif isinstance(value, list):
+            fixed_value = [serialize(val, self.cwuri_url) for val in value]
         else:
             fixed_value = serialize(value, self.cwuri_url)
         if fixed_value is not None:
@@ -560,7 +567,12 @@
                     xstype = profile_element[-1].attrib.get('type')
                     profile_element.remove(profile_element[-1])
                 attrs = {'type': xstype} if xstype else {}
-                self.element('rng:value', profile_element, attrs, text=fixed_value)
+                if isinstance(fixed_value, list):
+                    choice = self.element('rng:choice', profile_element)
+                    for val in fixed_value:
+                        self.element('rng:value', choice, attrs, text=val)
+                else:
+                    self.element('rng:value', profile_element, attrs, text=fixed_value)
         elif xstype is not None:
             self.element('rng:data', profile_element, {'type': xstype})
 
@@ -639,8 +651,12 @@
     """
     def __new__(cls, name, qualified_type, cardinality='0..1', fixed_value=None):
         assert cardinality in (None, '1', '0..1'), cardinality
-        if fixed_value is not None:
+        if fixed_value:
             cardinality = '1'
+            if isinstance(fixed_value, list) and len(fixed_value) == 1:
+                fixed_value = fixed_value[0]
+        else:
+            fixed_value = None
         return super(XAttr, cls).__new__(cls, name, qualified_type, cardinality, fixed_value)
 
 
@@ -717,16 +733,24 @@
         return children_parent
 
     def attribute_schema(self, parent, xattr):
-        attrs = {'name': xattr.name, 'type': xattr.qualified_type}
+        attrs = {'name': xattr.name}
         if xattr.cardinality is None:
             attrs['use'] = 'prohibited'
         elif xattr.cardinality == '1':
             attrs['use'] = 'required'
         else:
             attrs['use'] = 'optional'
-        if xattr.fixed_value is not None:
-            attrs['fixed'] = text_type(xattr.fixed_value)
-        self.element('xsd:attribute', parent, attrs)
+        if not isinstance(xattr.fixed_value, list):
+            attrs['type'] = xattr.qualified_type
+            if isinstance(xattr.fixed_value, string_types):
+                attrs['fixed'] = text_type(xattr.fixed_value)
+        attribute_element = self.element('xsd:attribute', parent, attrs)
+        if isinstance(xattr.fixed_value, list):
+            type_element = self.element('xsd:simpleType', attribute_element)
+            restriction_element = self.element('xsd:restriction', type_element,
+                                               {'base': 'xsd:token'})
+            for value in xattr.fixed_value:
+                self.element('xsd:enumeration', restriction_element, {'value': value})
 
     # business visit methods #######################################################################
 
@@ -858,20 +882,24 @@
         _safe_concept = partial(_safe_concept_value, concepts_language=self.concepts_language)
 
         format_id = data_object.format_id
+        format_ids = [_concept_value(concept, self.concepts_language)
+                      for concept in format_id.concepts]
+        mime_type = data_object.mime_type
+        mime_types = [_concept_value(concept, self.concepts_language)
+                      for concept in mime_type.concepts]
         encoding = data_object.encoding
-        mime_type = data_object.mime_type
         self.element_schema(parent, 'Attachment', 'qdt:ArchivesBinaryObjectType',
                             xsd_attributes=[
                                 XAttr('format', 'clmDAFFileTypeCode:FileTypeCodeType',
-                                      cardinality=_safe_cardinality(format_id),
-                                      fixed_value=_safe_concept(format_id)),
+                                      cardinality=format_id.user_cardinality,
+                                      fixed_value=format_ids),
                                 XAttr('encodingCode',
                                       'clm60133:CharacterSetEncodingCodeContentType',
                                       cardinality=_safe_cardinality(encoding),
                                       fixed_value=_safe_concept(encoding)),
                                 XAttr('mimeCode', 'clmIANAMIMEMediaType:MIMEMediaTypeContentType',
-                                      cardinality=_safe_cardinality(mime_type),
-                                      fixed_value=_safe_concept(mime_type)),
+                                      cardinality=mime_type.user_cardinality,
+                                      fixed_value=mime_types),
                                 XAttr('filename', 'xsd:string',
                                       cardinality='0..1',
                                       fixed_value=data_object.filename),
@@ -1384,7 +1412,12 @@
                     rtype_targets.append((entity, None))
                     continue
                 if targets:
-                    rtype_targets += [(entity, t) for t in targets]
+                    if len(targets) > 1 and targets[0].cw_etype == 'Concept':
+                        # same element with several allowed values
+                        rtype_targets += [(entity, targets)]
+                    else:
+                        # different children
+                        rtype_targets += [(entity, t) for t in targets]
                 # if relation is not composite, that means it's a "value" relation, hence we should
                 # always emit a value (its associated XSD element must be defined)
                 elif not rdefschema.composite:
--- a/test/data/seda_02_export.rng	Thu Oct 12 09:39:54 2017 +0200
+++ b/test/data/seda_02_export.rng	Thu Oct 12 12:52:30 2017 +0200
@@ -384,7 +384,10 @@
               </rng:optional>
               <rng:element name="Attachment">
                 <rng:attribute name="format">
-                  <rng:value type="string">fmt/123</rng:value>
+                  <rng:choice>
+                    <rng:value type="string">fmt/123</rng:value>
+                    <rng:value type="string">fmt/987</rng:value>
+                  </rng:choice>
                 </rng:attribute>
                 <rng:attribute name="encodingCode">
                   <rng:value type="string">6</rng:value>
--- a/test/data/seda_02_export.xsd	Thu Oct 12 09:39:54 2017 +0200
+++ b/test/data/seda_02_export.xsd	Thu Oct 12 12:52:30 2017 +0200
@@ -284,7 +284,14 @@
                       <xsd:complexType>
                         <xsd:simpleContent>
                           <xsd:extension base="qdt:ArchivesBinaryObjectType">
-                            <xsd:attribute fixed="fmt/123" name="format" type="clmDAFFileTypeCode:FileTypeCodeType" use="required"/>
+                            <xsd:attribute name="format" use="required">
+                              <xsd:simpleType>
+                                <xsd:restriction base="xsd:token">
+			                <xsd:enumeration value="fmt/123"/>
+			                <xsd:enumeration value="fmt/987"/>
+                                </xsd:restriction>
+                              </xsd:simpleType>
+                            </xsd:attribute>
                             <xsd:attribute fixed="6" name="encodingCode" type="clm60133:CharacterSetEncodingCodeContentType" use="required"/>
                             <xsd:attribute name="mimeCode" type="clmIANAMIMEMediaType:MIMEMediaTypeContentType" use="optional"/>
                             <xsd:attribute fixed="this_is_the_filename.pdf" name="filename" type="xsd:string" use="required"/>
--- a/test/data/seda_1_export.rng	Thu Oct 12 09:39:54 2017 +0200
+++ b/test/data/seda_1_export.rng	Thu Oct 12 12:52:30 2017 +0200
@@ -531,7 +531,10 @@
               </rng:optional>
               <rng:element name="Attachment">
                 <rng:attribute name="format">
-                  <rng:value type="string">fmt/123</rng:value>
+                  <rng:choice>
+                    <rng:value type="string">fmt/123</rng:value>
+                    <rng:value type="string">fmt/987</rng:value>
+                  </rng:choice>
                 </rng:attribute>
                 <rng:attribute name="encodingCode">
                   <rng:value type="string">6</rng:value>
--- a/test/data/seda_1_export.xsd	Thu Oct 12 09:39:54 2017 +0200
+++ b/test/data/seda_1_export.xsd	Thu Oct 12 12:52:30 2017 +0200
@@ -429,7 +429,14 @@
                       <xsd:complexType>
                         <xsd:simpleContent>
                           <xsd:extension base="qdt:ArchivesBinaryObjectType">
-                            <xsd:attribute fixed="fmt/123" name="format" type="clmDAFFileTypeCode:FileTypeCodeType" use="required"/>
+                            <xsd:attribute name="format" use="required">
+                              <xsd:simpleType>
+                                <xsd:restriction base="xsd:token">
+			                <xsd:enumeration value="fmt/123"/>
+			                <xsd:enumeration value="fmt/987"/>
+                                </xsd:restriction>
+                              </xsd:simpleType>
+                            </xsd:attribute>
                             <xsd:attribute fixed="6" name="encodingCode" type="clm60133:CharacterSetEncodingCodeContentType" use="required"/>
                             <xsd:attribute name="mimeCode" type="clmIANAMIMEMediaType:MIMEMediaTypeContentType" use="optional"/>
                             <xsd:attribute fixed="this_is_the_filename.pdf" name="filename" type="xsd:string" use="required"/>
--- a/test/test_profile_generation.py	Thu Oct 12 09:39:54 2017 +0200
+++ b/test/test_profile_generation.py	Thu Oct 12 12:52:30 2017 +0200
@@ -533,6 +533,27 @@
                  {'name': 'listURI', 'use': 'optional', 'type': 'xsd:anyURI'},
                  {'name': 'listVersionID', 'use': 'optional', 'type': 'xsd:token'}])
 
+    def test_multiple_concepts(self):
+        with self.admin_access.cnx() as cnx:
+            transfer = cnx.create_entity('SEDAArchiveTransfer', title=u'test profile')
+            scheme = testutils.scheme_for_type(cnx, None, None,
+                                               u'application/msword', u'application/pdf')
+            cnx.create_entity('SEDAMimeTypeCodeListVersion',
+                              seda_mime_type_code_list_version_from=transfer,
+                              seda_mime_type_code_list_version_to=scheme)
+            bdo = testutils.create_data_object(transfer)
+            bdo.mime_type.cw_set(user_cardinality=u'1',
+                                 seda_mime_type_to=scheme.reverse_in_scheme)
+
+            profile = self.profile_etree(transfer)
+            mt = self.get_element(profile, 'MimeType')
+            self.assertEqual('\n'.join(etree.tostring(mt, pretty_print=True).splitlines()[1:-1]),
+                             '''\
+  <rng:choice>
+    <rng:value type="token">application/msword</rng:value>
+    <rng:value type="token">application/pdf</rng:value>
+  </rng:choice>''')
+
     def test_seda2_concept(self):
         with self.admin_access.cnx() as cnx:
             create = cnx.create_entity
@@ -710,18 +731,22 @@
             create = cnx.create_entity
 
             concepts = {}
-            for rtype, etype, value in [
-                    ('seda_format_id_to', None, u'fmt/123'),
-                    ('seda_encoding_to', None, u'6'),
-                    ('seda_type_to', None, u'CDO'),
-                    ('seda_description_level', None, u'file'),
-                    ('seda_algorithm', 'SEDABinaryDataObject', u'md5'),
-                    ('seda_rule', 'SEDASeqAppraisalRuleRule', u'P10Y'),
-                    ('seda_rule', 'SEDASeqAccessRuleRule', u'AR038'),
-                    ('seda_final_action', 'SEDAAppraisalRule', u'detruire'),
+            for rtype, etype, labels in [
+                    ('seda_format_id_to', None, [u'fmt/123', u'fmt/987']),
+                    ('seda_encoding_to', None, [u'6']),
+                    ('seda_type_to', None, [u'CDO']),
+                    ('seda_description_level', None, [u'file']),
+                    ('seda_algorithm', 'SEDABinaryDataObject', [u'md5']),
+                    ('seda_rule', 'SEDASeqAppraisalRuleRule', [u'P10Y']),
+                    ('seda_rule', 'SEDASeqAccessRuleRule', [u'AR038']),
+                    ('seda_final_action', 'SEDAAppraisalRule', [u'detruire']),
             ]:
-                scheme = testutils.scheme_for_type(cnx, rtype, etype, value)
-                concepts[value] = scheme.reverse_in_scheme[0]
+                scheme = testutils.scheme_for_type(cnx, rtype, etype, *labels)
+                if len(labels) == 1:
+                    concepts[labels[0]] = scheme.reverse_in_scheme[0]
+                else:
+                    for concept in scheme.reverse_in_scheme:
+                        concepts[concept.label()] = concept
 
             # ensure we're able to export concept with unexpected language code
             concepts['md5'].preferred_label[0].cw_set(language_code=u'de')
@@ -820,7 +845,7 @@
 
             bdo.format_id.cw_set(
                 user_cardinality=u'1',
-                seda_format_id_to=concepts['fmt/123'])
+                seda_format_id_to=[concepts['fmt/123'], concepts['fmt/987']])
             create('SEDAEncoding',
                    user_cardinality=u'1',
                    seda_encoding_from=bdo,