Refactor XSD export of old SEDA version to for later support of RNG export
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 07 Oct 2016 17:51:25 +0200
changeset 1625 f75c007abed2
parent 1624 3b80079a761d
child 1626 fd932a779c3b
Refactor XSD export of old SEDA version to for later support of RNG export This will allow to share attribute definition (XAttr) independantly of the export format (XSD or RNG). Related to #15045341
entities/profile_generation.py
--- a/entities/profile_generation.py	Fri Oct 07 17:48:55 2016 +0200
+++ b/entities/profile_generation.py	Fri Oct 07 17:51:25 2016 +0200
@@ -15,7 +15,7 @@
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 """cubicweb-seda adapter classes for profile (schema) generation"""
 
-from collections import defaultdict
+from collections import defaultdict, namedtuple
 
 from six import text_type, string_types
 
@@ -733,35 +733,30 @@
         return parent
 
 
-class XSDAttr(dict):
-    """Simple object to define an xsd:attribute element.
-
-    Params:
+class XAttr(namedtuple('_XAttr', ['name', 'qualified_type', 'cardinality', 'fixed_value'])):
+    """Simple representation of an attribute element in a schema (RNG or XSD).
 
-    * `name`, value for the 'name' attribute of the xsd:attribute
+    Parameters:
 
-    * `xsd_type`, value for the 'type' attribute of the xsd:attribute
+    * `name`, the attribute's name,
 
-    * `cardinality`, optional cardinality that will be used to compute
-      the 'use' attribute of the xsd:attribute (default to 'prohibited')
+    * `qualified_type`, its qualified type (e.g. 'xsd:string'),
 
-    * `value`, optional value for the 'fixed' attribute of the xsd:attribute
+    * `cardinality`, optional cardinality as string (None, '1' or '0..1') - default to '1' if some
+      fixed value is provided, else to None (i.e. attribute is prohibited),
+
+    * `fixed_value`, optional fixed value for the attribute.
+
     """
-    def __init__(self, name, xsd_type, cardinality=None, fixed_value=None):
+    def __new__(cls, name, qualified_type, cardinality=None, fixed_value=None):
         assert cardinality in (None, '1', '0..1'), cardinality
-        if cardinality is None:
-            use = 'prohibited'
-        elif cardinality == '1':
-            use = 'required'
-        else:
-            use = 'optional'
-        super(XSDAttr, self).__init__(name=name, type=xsd_type, use=use)
-        if fixed_value is not None:
-            self['fixed'] = text_type(fixed_value)
+        if cardinality is None and fixed_value is not None:
+            cardinality = '1'
+        return super(XAttr, cls).__new__(cls, name, qualified_type, cardinality, fixed_value)
 
 
-LIST_VERSION_ID_2009 = XSDAttr('listVersionID', 'xsd:token', '1', 'edition 2009')
-LIST_VERSION_ID_2011 = XSDAttr('listVersionID', 'xsd:token', '1', 'edition 2011')
+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):
@@ -821,10 +816,22 @@
             ct = self.element('xsd:complexType', element)
             scontent = self.element('xsd:simpleContent', ct)
             attributes_parent = self.element('xsd:extension', scontent, {'base': xsd_type})
-        for xsd_attr in xsd_attributes:
-            self.element('xsd:attribute', attributes_parent, xsd_attr)
+        for xattr in xsd_attributes:
+            self.attribute_schema(attributes_parent, xattr)
         return children_parent
 
+    def attribute_schema(self, parent, xattr):
+        attrs = {'name': xattr.name, 'type': xattr.qualified_type}
+        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)
+
     # business visit methods #######################################################################
 
     def dump_etree(self):
@@ -841,13 +848,13 @@
         """Append XSD elements for the archive transfer to the given parent node."""
         transfer_node = self.xsd_element(parent, 'ArchiveTransfer',
                                          documentation=archive_transfer.title,
-                                         xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                         xsd_attributes=[XAttr('Id', 'xsd:ID')])
         for comment in archive_transfer.comments:
             self.xsd_element(transfer_node, 'Comment', 'udt:TextType',
                              fixed_value=comment.comment,
                              cardinality=comment.user_cardinality,
                              documentation=comment.user_annotation,
-                             xsd_attributes=[XSDAttr('languageID', 'xsd:language')])
+                             xsd_attributes=[XAttr('languageID', 'xsd:language')])
         self.xsd_element(transfer_node, 'Date', 'udt:DateTimeType')
         self.xsd_element(transfer_node, 'TransferIdentifier', 'qdt:ArchivesIDType',
                          xsd_attributes=self.xsd_attributes_scheme())
@@ -859,7 +866,7 @@
         archive_node = self.xsd_element(parent, 'Archive',
                                         cardinality=archive_unit.user_cardinality,
                                         documentation=archive_unit.user_annotation,
-                                        xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                        xsd_attributes=[XAttr('Id', 'xsd:ID')])
         # hard-coded description's language XXX fine, content language may be specified
         self.xsd_element(archive_node, 'DescriptionLanguage', 'qdt:CodeLanguageType',
                          fixed_value='fra',
@@ -868,7 +875,7 @@
         self.xsd_element(archive_node, 'Name', 'udt:TextType',
                          fixed_value=name_entity.title,
                          documentation=name_entity.user_annotation,
-                         xsd_attributes=[XSDAttr('languageID', 'xsd:language')])
+                         xsd_attributes=[XAttr('languageID', 'xsd:language')])
         appraisal_rule_entity = self.archive_unit_appraisal_rule(archive_unit)
         if appraisal_rule_entity:
             self.xsd_appraisal_rule(archive_node, appraisal_rule_entity)
@@ -893,7 +900,7 @@
         ar_node = self.xsd_element(parent, 'Appraisal',
                                    cardinality=appraisal_rule.user_cardinality,
                                    documentation=appraisal_rule.user_annotation,
-                                   xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                   xsd_attributes=[XAttr('Id', 'xsd:ID')])
         ar_code = appraisal_rule.final_action_concept
         ar_code_value = _concept_value(ar_code, self.concepts_language)
         self.xsd_element(ar_node, 'Code', 'qdt:CodeAppraisalType',
@@ -918,7 +925,7 @@
         ar_node = self.xsd_element(parent, self.access_restriction_tag_name,
                                    cardinality=access_rule.user_cardinality,
                                    documentation=access_rule.user_annotation,
-                                   xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                   xsd_attributes=[XAttr('Id', 'xsd:ID')])
         # XXX cardinality 1
         rule = access_rule.rules[0] if access_rule.rules else None
         value = _concept_value(rule.rule_concept, self.concepts_language) if rule else None
@@ -938,7 +945,7 @@
                                    # XXX should always be 1
                                    # cardinality=content.user_cardinality,
                                    documentation=content.user_annotation,
-                                   xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                   xsd_attributes=[XAttr('Id', 'xsd:ID')])
         self.xsd_description_level(cd_node, content.description_level_concept)
         for seda2_name, seda1_name in (('start', 'oldest'), ('end', 'latest')):
             date_entity = getattr(content, '%s_date' % seda2_name)
@@ -964,20 +971,20 @@
                          cardinality=description.user_cardinality,
                          documentation=description.user_annotation,
                          fixed_value=description.description,
-                         xsd_attributes=[XSDAttr('languageID', 'xsd:language')])
+                         xsd_attributes=[XAttr('languageID', 'xsd:language')])
 
     # extracted from xsd_keyword to allow parametrization for SEDA 1.0 vs 0.2 generation
     kw_tag_name = 'Keyword'
     kw_content_tag_type = 'qdt:KeywordContentType'
-    kw_content_tag_attributes = [XSDAttr('role', 'xsd:token'),
-                                 XSDAttr('languageID', 'xsd:language')]
+    kw_content_tag_attributes = [XAttr('role', 'xsd:token'),
+                                 XAttr('languageID', 'xsd:language')]
 
     def xsd_keyword(self, parent, keyword):
         """Append XSD elements for a keyword to the given parent node"""
         kw_node = self.xsd_element(parent, self.kw_tag_name,
                                    cardinality=keyword.user_cardinality,
                                    documentation=keyword.user_annotation,
-                                   xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                   xsd_attributes=[XAttr('Id', 'xsd:ID')])
         content = keyword.content_string
         url = None
         if keyword.reference:
@@ -1013,12 +1020,12 @@
         ao_node = self.xsd_element(parent, self.archive_object_tag_name,
                                    cardinality=archive_unit.user_cardinality,
                                    documentation=archive_unit.user_annotation,
-                                   xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                   xsd_attributes=[XAttr('Id', 'xsd:ID')])
         content_entity = self.archive_unit_content(archive_unit)
         self.xsd_element(ao_node, 'Name', 'udt:TextType',
                          fixed_value=content_entity.title.title,
                          documentation=content_entity.title.user_annotation,
-                         xsd_attributes=[XSDAttr('languageID', 'xsd:language')])
+                         xsd_attributes=[XAttr('languageID', 'xsd:language')])
         appraisal_rule_entity = self.archive_unit_appraisal_rule(archive_unit)
         if appraisal_rule_entity:
             self.xsd_appraisal_rule(ao_node, appraisal_rule_entity)
@@ -1035,7 +1042,7 @@
         document_node = self.xsd_element(parent, 'Document',
                                          cardinality=data_object.user_cardinality,
                                          documentation=data_object.user_annotation,
-                                         xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                         xsd_attributes=[XAttr('Id', 'xsd:ID')])
 
         def safe_cardinality(entity):
             if entity is None:
@@ -1051,19 +1058,19 @@
         encoding = data_object.encoding
         self.xsd_element(document_node, 'Attachment', 'qdt:ArchivesBinaryObjectType',
                          xsd_attributes=[
-                             XSDAttr('format', 'clmDAFFileTypeCode:FileTypeCodeType',
-                                     cardinality=safe_cardinality(format_id),
-                                     fixed_value=safe_concept_value(format_id)),
-                             XSDAttr('encodingCode',
-                                     'clm60133:CharacterSetEncodingCodeContentType',
-                                     cardinality=safe_cardinality(encoding),
-                                     fixed_value=safe_concept_value(encoding)),
+                             XAttr('format', 'clmDAFFileTypeCode:FileTypeCodeType',
+                                   cardinality=safe_cardinality(format_id),
+                                   fixed_value=safe_concept_value(format_id)),
+                             XAttr('encodingCode',
+                                   'clm60133:CharacterSetEncodingCodeContentType',
+                                   cardinality=safe_cardinality(encoding),
+                                   fixed_value=safe_concept_value(encoding)),
                              # hard-coded attributes
-                             XSDAttr('characterSetCode',
-                                     'clmIANACharacterSetCode:CharacterSetCodeContentType'),
-                             XSDAttr('mimeCode', 'clmIANAMIMEMediaType:MIMEMediaTypeContentType'),
-                             XSDAttr('uri', 'xsd:anyURI'),
-                             XSDAttr('filename', 'xsd:string'),
+                             XAttr('characterSetCode',
+                                   'clmIANACharacterSetCode:CharacterSetCodeContentType'),
+                             XAttr('mimeCode', 'clmIANAMIMEMediaType:MIMEMediaTypeContentType'),
+                             XAttr('uri', 'xsd:anyURI'),
+                             XAttr('filename', 'xsd:string'),
                          ])
         references = list(data_object.referenced_by)
         assert len(references) == 1
@@ -1076,28 +1083,23 @@
 
     @classmethod
     def xsd_attributes_scheme(cls, scheme=None):
-        """Return a list of :class:`XSDAttr` for a scheme definition, with some proper values
+        """Return a list of :class:`XAttr` for a scheme definition, with some proper values
         specified if a scheme is given.
         """
         attributes = [
-            XSDAttr('schemeID', 'xsd:token'),
-            XSDAttr('schemeName', 'xsd:string'),
-            XSDAttr('schemeAgencyName', 'xsd:string'),
-            XSDAttr('schemeVersionID', 'xsd:token'),
-            XSDAttr('schemeDataURI', 'xsd:anyURI'),
-            XSDAttr('schemeURI', 'xsd:anyURI'),
+            XAttr('schemeID', 'xsd:token'),
+            XAttr('schemeName', 'xsd:string',
+                  fixed_value=scheme.title if scheme and scheme.title else None),
+            XAttr('schemeAgencyName', 'xsd:string'),
+            XAttr('schemeVersionID', 'xsd:token'),
+            XAttr('schemeDataURI', 'xsd:anyURI'),
+            XAttr('schemeURI', 'xsd:anyURI',
+                  fixed_value=cls.cwuri_url(scheme) if scheme else None),
         ]
-        if scheme is not None:
-            # schemeURI
-            attributes[-1]['fixed'] = cls.cwuri_url(scheme)
-            attributes[-1]['use'] = 'required'
-            # # schemeID XXX move to saem
-            # attributes[0]['fixed'] = scheme.ark
-            # attributes[0]['use'] = 'required'
-            # schemeName
-            if scheme.title:
-                attributes[1]['fixed'] = scheme.title
-                attributes[1]['use'] = 'required'
+        # if scheme is not None:
+        #     # schemeID XXX move to saem
+        #     attributes[0]['fixed'] = scheme.ark
+        #     attributes[0]['use'] = 'required'
         return attributes
 
     @staticmethod
@@ -1127,7 +1129,7 @@
         archive_node = self.xsd_element(parent, 'Contains',
                                         cardinality=archive_unit.user_cardinality,
                                         documentation=archive_unit.user_annotation,
-                                        xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                        xsd_attributes=[XAttr('Id', 'xsd:ID')])
         # hard-coded description's language
         self.xsd_element(archive_node, 'DescriptionLanguage', 'qdt:CodeLanguageType',
                          fixed_value='fr',
@@ -1136,7 +1138,7 @@
         self.xsd_element(archive_node, 'Name', 'udt:TextType',
                          fixed_value=name_entity.title,
                          documentation=name_entity.user_annotation,
-                         xsd_attributes=[XSDAttr('languageID', 'xsd:language')])
+                         xsd_attributes=[XAttr('languageID', 'xsd:language')])
         # in SEDA 0.2, description level is on the archive element, not on its content description
         content_entity = self.archive_unit_content(archive_unit)
         self.xsd_description_level(archive_node, content_entity.description_level_concept)
@@ -1159,7 +1161,7 @@
                                    # XXX should always be 1
                                    # cardinality=content_description.user_cardinality,
                                    documentation=content.user_annotation,
-                                   xsd_attributes=[XSDAttr('Id', 'xsd:ID')])
+                                   xsd_attributes=[XAttr('Id', 'xsd:ID')])
         for seda2_name, seda1_name in (('start', 'oldest'), ('end', 'latest')):
             date_entity = getattr(content, '%s_date' % seda2_name)
             if date_entity:
@@ -1193,7 +1195,7 @@
     # is TextType and there is no 'role' attribute
     kw_tag_name = 'ContentDescriptive'
     kw_content_tag_type = 'udt:TextType'
-    kw_content_tag_attributes = [XSDAttr('languageID', 'xsd:language')]
+    kw_content_tag_attributes = [XAttr('languageID', 'xsd:language')]
 
 
 def xsd_element_cardinality(occ, card_entity):