[profile gen] Ensure same name children are ordered accorded to their cardinality
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 01 Sep 2017 17:00:09 +0200
changeset 2680 4941eab6a289
parent 2679 39d1d971e058
child 2681 b247060eadc2
[profile gen] Ensure same name children are ordered accorded to their cardinality As explained in the previous commit, we want items with cardinality = 1 first then the one with cardinality != 1 if any. See previous commit for detailed explanation. Closes #17098404
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/profile_generation.py	Fri Sep 01 11:19:42 2017 +0200
+++ b/cubicweb_seda/entities/profile_generation.py	Fri Sep 01 17:00:09 2017 +0200
@@ -162,6 +162,10 @@
         return minmax_cardinality(cardinality, ('0..1', '1'))[0]
 
 
+# sort on user_cardinality != 1 to have all entities with cardinality == 1 first
+_sorted_children = partial(sorted, key=lambda x: getattr(x, 'user_cardinality', '1') != '1')
+
+
 def iter_path_children(xselement, entity):
     """Return an iterator on `entity` children entities according to `xselement` definition.
 
@@ -174,7 +178,7 @@
             if getattr(entity, rtype) is not None:
                 yield _path, getattr(entity, rtype)
         else:
-            related = entity.related(rtype, role, entities=True)
+            related = _sorted_children(entity.related(rtype, role, entities=True))
             if related:
                 yield _path, related
 
@@ -738,7 +742,7 @@
     def xsd_transfer(self, parent, archive_transfer):
         """Append XSD elements for the archive transfer to the given parent node."""
         transfer_node = self.xsd_transfer_base(parent, archive_transfer)
-        for archive_unit in archive_transfer.archive_units:
+        for archive_unit in _sorted_children(archive_transfer.archive_units):
             self.xsd_archive(transfer_node, archive_unit)
 
     def xsd_archive(self, parent, archive_unit):
@@ -822,7 +826,9 @@
         archive objects or documents, and append XSD elements for them to the given parent node.
         """
         for au_or_bdo in sorted(entity.cw_adapt_to('ITreeBase').iterchildren(),
-                                key=lambda x: (x.cw_etype == self.last_children_type, x.eid)):
+                                key=lambda x: (x.cw_etype == self.last_children_type,
+                                               x.user_cardinality != '1',
+                                               x.eid)):
             if au_or_bdo.cw_etype == 'SEDABinaryDataObject':
                 self.xsd_document(parent, au_or_bdo)
             else:
@@ -1014,14 +1020,14 @@
                                 xsd_attributes=[XAttr('languageID', 'xsd:language')])
 
     def xsd_keywords(self, parent, content):
-        for keyword in content.keywords:
+        for keyword in _sorted_children(content.keywords):
             self.xsd_keyword(parent, keyword)
 
     def xsd_custodial_history(self, parent, content):
         if content.custodial_history_items:
             ch_node = self.element_schema(parent, 'CustodialHistory',
                                           cardinality='0..1')
-            for item in content.custodial_history_items:
+            for item in _sorted_children(content.custodial_history_items):
                 when_card = item.when.user_cardinality if item.when else None
                 self.element_schema(ch_node, 'CustodialHistoryItem', 'qdt:CustodialHistoryItemType',
                                     cardinality=item.user_cardinality,
@@ -1140,10 +1146,10 @@
         """Append XSD elements for the archive transfer to the given parent node."""
         transfer_node = self.xsd_transfer_base(parent, archive_transfer)
 
-        for data_object in archive_transfer.binary_data_objects:
+        for data_object in _sorted_children(archive_transfer.binary_data_objects):
             self.xsd_integrity(transfer_node, data_object)
 
-        for archive_unit in archive_transfer.archive_units:
+        for archive_unit in _sorted_children(archive_transfer.archive_units):
             self.xsd_archive(transfer_node, archive_unit)
 
     def xsd_archive(self, parent, archive_unit):
@@ -1340,7 +1346,7 @@
         if rtype == 'id':
             return [(None, eid2xmlid(entity.eid))]
         return [(None, getattr(entity, rtype, None))]
-    targets = entity.related(rtype, role, entities=True)
+    targets = _sorted_children(entity.related(rtype, role, entities=True))
     rschema = entity._cw.vreg.schema.rschema
     rdefschema = next(iter(rschema(rtype).rdefs.values()))
     # data_object_reference_id is artificially composite to ease the case of simplified profile
@@ -1371,7 +1377,7 @@
                 rtype_targets.append((entity, getattr(entity, rtype, None)))
             else:
                 try:
-                    targets = entity.related(rtype, role, entities=True)
+                    targets = _sorted_children(entity.related(rtype, role, entities=True))
                 except KeyError:
                     # the relation is not defined in the schema: element is not modelized but should
                     # be added in the XSD
--- a/test/data/seda_02_export.rng	Fri Sep 01 11:19:42 2017 +0200
+++ b/test/data/seda_02_export.rng	Fri Sep 01 17:00:09 2017 +0200
@@ -402,72 +402,6 @@
               </rng:element>
             </rng:element>
           </rng:zeroOrMore>
-          <rng:oneOrMore>
-            <rng:element name="Contains">
-              <rng:element name="DescriptionLevel">
-                <rng:attribute name="listVersionID">
-                  <rng:value type="token">edition 2009</rng:value>
-                </rng:attribute>
-                <rng:value type="string">file</rng:value>
-              </rng:element>
-              <xsd:annotation>
-                <xsd:documentation>archive unit title</xsd:documentation>
-              </xsd:annotation>
-              <rng:optional>
-                <rng:attribute name="Id">
-                  <rng:data type="ID"/>
-                </rng:attribute>
-              </rng:optional>
-              <rng:element name="Name">
-                <rng:optional>
-                  <rng:attribute name="languageID">
-                    <rng:data type="language"/>
-                  </rng:attribute>
-                </rng:optional>
-                <rng:data type="string"/>
-              </rng:element>
-              <rng:optional>
-                <rng:element name="Appraisal">
-                  <rng:optional>
-                    <rng:attribute name="Id">
-                      <rng:data type="ID"/>
-                    </rng:attribute>
-                  </rng:optional>
-                  <rng:element name="Code">
-                    <rng:attribute name="listVersionID">
-                      <rng:value type="token">edition 2009</rng:value>
-                    </rng:attribute>
-                    <rng:data type="string"/>
-                  </rng:element>
-                  <rng:element name="Duration">
-                    <rng:data type="string"/>
-                  </rng:element>
-                  <rng:element name="StartDate">
-                    <rng:data type="string"/>
-                  </rng:element>
-                </rng:element>
-              </rng:optional>
-              <rng:element name="AccessRestriction">
-                <xsd:annotation>
-                  <xsd:documentation>restrict</xsd:documentation>
-                </xsd:annotation>
-                <rng:optional>
-                  <rng:attribute name="Id">
-                    <rng:data type="ID"/>
-                  </rng:attribute>
-                </rng:optional>
-                <rng:element name="Code">
-                  <rng:attribute name="listVersionID">
-                    <rng:value type="token">edition 2009</rng:value>
-                  </rng:attribute>
-                  <rng:value type="string">AR038</rng:value>
-                </rng:element>
-                <rng:element name="StartDate">
-                  <rng:data type="string"/>
-                </rng:element>
-              </rng:element>
-            </rng:element>
-          </rng:oneOrMore>
           <rng:element name="Contains">
             <rng:element name="DescriptionLevel">
               <rng:attribute name="listVersionID">
@@ -535,6 +469,72 @@
               </rng:element>
             </rng:element>
           </rng:element>
+          <rng:oneOrMore>
+            <rng:element name="Contains">
+              <rng:element name="DescriptionLevel">
+                <rng:attribute name="listVersionID">
+                  <rng:value type="token">edition 2009</rng:value>
+                </rng:attribute>
+                <rng:value type="string">file</rng:value>
+              </rng:element>
+              <xsd:annotation>
+                <xsd:documentation>archive unit title</xsd:documentation>
+              </xsd:annotation>
+              <rng:optional>
+                <rng:attribute name="Id">
+                  <rng:data type="ID"/>
+                </rng:attribute>
+              </rng:optional>
+              <rng:element name="Name">
+                <rng:optional>
+                  <rng:attribute name="languageID">
+                    <rng:data type="language"/>
+                  </rng:attribute>
+                </rng:optional>
+                <rng:data type="string"/>
+              </rng:element>
+              <rng:optional>
+                <rng:element name="Appraisal">
+                  <rng:optional>
+                    <rng:attribute name="Id">
+                      <rng:data type="ID"/>
+                    </rng:attribute>
+                  </rng:optional>
+                  <rng:element name="Code">
+                    <rng:attribute name="listVersionID">
+                      <rng:value type="token">edition 2009</rng:value>
+                    </rng:attribute>
+                    <rng:data type="string"/>
+                  </rng:element>
+                  <rng:element name="Duration">
+                    <rng:data type="string"/>
+                  </rng:element>
+                  <rng:element name="StartDate">
+                    <rng:data type="string"/>
+                  </rng:element>
+                </rng:element>
+              </rng:optional>
+              <rng:element name="AccessRestriction">
+                <xsd:annotation>
+                  <xsd:documentation>restrict</xsd:documentation>
+                </xsd:annotation>
+                <rng:optional>
+                  <rng:attribute name="Id">
+                    <rng:data type="ID"/>
+                  </rng:attribute>
+                </rng:optional>
+                <rng:element name="Code">
+                  <rng:attribute name="listVersionID">
+                    <rng:value type="token">edition 2009</rng:value>
+                  </rng:attribute>
+                  <rng:value type="string">AR038</rng:value>
+                </rng:element>
+                <rng:element name="StartDate">
+                  <rng:data type="string"/>
+                </rng:element>
+              </rng:element>
+            </rng:element>
+          </rng:oneOrMore>
         </rng:element>
       </rng:oneOrMore>
     </rng:element>
--- a/test/data/seda_02_export.xsd	Fri Sep 01 11:19:42 2017 +0200
+++ b/test/data/seda_02_export.xsd	Fri Sep 01 17:00:09 2017 +0200
@@ -307,72 +307,6 @@
                   <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
                 </xsd:complexType>
               </xsd:element>
-              <xsd:element maxOccurs="unbounded" name="Contains">
-                <xsd:annotation>
-                  <xsd:documentation>archive unit title</xsd:documentation>
-                </xsd:annotation>
-                <xsd:complexType>
-                  <xsd:sequence>
-                    <xsd:element fixed="file" name="DescriptionLevel">
-                      <xsd:complexType>
-                        <xsd:simpleContent>
-                          <xsd:extension base="qdt:CodeDescriptionLevelType">
-                            <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
-                          </xsd:extension>
-                        </xsd:simpleContent>
-                      </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element name="Name">
-                      <xsd:complexType>
-                        <xsd:simpleContent>
-                          <xsd:extension base="udt:TextType">
-                            <xsd:attribute name="languageID" type="xsd:language" use="optional"/>
-                          </xsd:extension>
-                        </xsd:simpleContent>
-                      </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element minOccurs="0" name="Appraisal">
-                      <xsd:complexType>
-                        <xsd:sequence>
-                          <xsd:element name="Code">
-                            <xsd:complexType>
-                              <xsd:simpleContent>
-                                <xsd:extension base="qdt:CodeAppraisalType">
-                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
-                                </xsd:extension>
-                              </xsd:simpleContent>
-                            </xsd:complexType>
-                          </xsd:element>
-                          <xsd:element name="Duration" type="qdt:ArchivesDurationType"/>
-                          <xsd:element name="StartDate" type="udt:DateType"/>
-                        </xsd:sequence>
-                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
-                      </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element name="AccessRestriction">
-                      <xsd:annotation>
-                        <xsd:documentation>restrict</xsd:documentation>
-                      </xsd:annotation>
-                      <xsd:complexType>
-                        <xsd:sequence>
-                          <xsd:element fixed="AR038" name="Code">
-                            <xsd:complexType>
-                              <xsd:simpleContent>
-                                <xsd:extension base="qdt:CodeAccessRestrictionType">
-                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
-                                </xsd:extension>
-                              </xsd:simpleContent>
-                            </xsd:complexType>
-                          </xsd:element>
-                          <xsd:element name="StartDate" type="udt:DateType"/>
-                        </xsd:sequence>
-                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
-                      </xsd:complexType>
-                    </xsd:element>
-                  </xsd:sequence>
-                  <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
-                </xsd:complexType>
-              </xsd:element>
               <xsd:element name="Contains">
                 <xsd:annotation>
                   <xsd:documentation>archive unit title</xsd:documentation>
@@ -443,6 +377,72 @@
                   <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
                 </xsd:complexType>
               </xsd:element>
+              <xsd:element maxOccurs="unbounded" name="Contains">
+                <xsd:annotation>
+                  <xsd:documentation>archive unit title</xsd:documentation>
+                </xsd:annotation>
+                <xsd:complexType>
+                  <xsd:sequence>
+                    <xsd:element fixed="file" name="DescriptionLevel">
+                      <xsd:complexType>
+                        <xsd:simpleContent>
+                          <xsd:extension base="qdt:CodeDescriptionLevelType">
+                            <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
+                          </xsd:extension>
+                        </xsd:simpleContent>
+                      </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="Name">
+                      <xsd:complexType>
+                        <xsd:simpleContent>
+                          <xsd:extension base="udt:TextType">
+                            <xsd:attribute name="languageID" type="xsd:language" use="optional"/>
+                          </xsd:extension>
+                        </xsd:simpleContent>
+                      </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element minOccurs="0" name="Appraisal">
+                      <xsd:complexType>
+                        <xsd:sequence>
+                          <xsd:element name="Code">
+                            <xsd:complexType>
+                              <xsd:simpleContent>
+                                <xsd:extension base="qdt:CodeAppraisalType">
+                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
+                                </xsd:extension>
+                              </xsd:simpleContent>
+                            </xsd:complexType>
+                          </xsd:element>
+                          <xsd:element name="Duration" type="qdt:ArchivesDurationType"/>
+                          <xsd:element name="StartDate" type="udt:DateType"/>
+                        </xsd:sequence>
+                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+                      </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="AccessRestriction">
+                      <xsd:annotation>
+                        <xsd:documentation>restrict</xsd:documentation>
+                      </xsd:annotation>
+                      <xsd:complexType>
+                        <xsd:sequence>
+                          <xsd:element fixed="AR038" name="Code">
+                            <xsd:complexType>
+                              <xsd:simpleContent>
+                                <xsd:extension base="qdt:CodeAccessRestrictionType">
+                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
+                                </xsd:extension>
+                              </xsd:simpleContent>
+                            </xsd:complexType>
+                          </xsd:element>
+                          <xsd:element name="StartDate" type="udt:DateType"/>
+                        </xsd:sequence>
+                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+                      </xsd:complexType>
+                    </xsd:element>
+                  </xsd:sequence>
+                  <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+                </xsd:complexType>
+              </xsd:element>
             </xsd:sequence>
             <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
           </xsd:complexType>
--- a/test/data/seda_1_export.rng	Fri Sep 01 11:19:42 2017 +0200
+++ b/test/data/seda_1_export.rng	Fri Sep 01 17:00:09 2017 +0200
@@ -360,85 +360,6 @@
               <rng:data type="string"/>
             </rng:element>
           </rng:element>
-          <rng:oneOrMore>
-            <rng:element name="ArchiveObject">
-              <xsd:annotation>
-                <xsd:documentation>archive unit title</xsd:documentation>
-              </xsd:annotation>
-              <rng:optional>
-                <rng:attribute name="Id">
-                  <rng:data type="ID"/>
-                </rng:attribute>
-              </rng:optional>
-              <rng:element name="Name">
-                <rng:optional>
-                  <rng:attribute name="languageID">
-                    <rng:data type="language"/>
-                  </rng:attribute>
-                </rng:optional>
-                <rng:data type="string"/>
-              </rng:element>
-              <rng:element name="ContentDescription">
-                <rng:optional>
-                  <rng:attribute name="Id">
-                    <rng:data type="ID"/>
-                  </rng:attribute>
-                </rng:optional>
-                <rng:element name="DescriptionLevel">
-                  <rng:attribute name="listVersionID">
-                    <rng:value type="token">edition 2009</rng:value>
-                  </rng:attribute>
-                  <rng:value type="string">file</rng:value>
-                </rng:element>
-                <rng:element name="Language">
-                  <rng:attribute name="listVersionID">
-                    <rng:value type="token">edition 2009</rng:value>
-                  </rng:attribute>
-                  <rng:data type="string"/>
-                </rng:element>
-              </rng:element>
-              <rng:optional>
-                <rng:element name="Appraisal">
-                  <rng:optional>
-                    <rng:attribute name="Id">
-                      <rng:data type="ID"/>
-                    </rng:attribute>
-                  </rng:optional>
-                  <rng:element name="Code">
-                    <rng:attribute name="listVersionID">
-                      <rng:value type="token">edition 2009</rng:value>
-                    </rng:attribute>
-                    <rng:data type="string"/>
-                  </rng:element>
-                  <rng:element name="Duration">
-                    <rng:data type="string"/>
-                  </rng:element>
-                  <rng:element name="StartDate">
-                    <rng:data type="string"/>
-                  </rng:element>
-                </rng:element>
-              </rng:optional>
-              <rng:element name="AccessRestrictionRule">
-                <xsd:annotation>
-                  <xsd:documentation>restrict</xsd:documentation>
-                </xsd:annotation>
-                <rng:optional>
-                  <rng:attribute name="Id">
-                    <rng:data type="ID"/>
-                  </rng:attribute>
-                </rng:optional>
-                <rng:element name="Code">
-                  <rng:attribute name="listVersionID">
-                    <rng:value type="token">edition 2009</rng:value>
-                  </rng:attribute>
-                  <rng:value type="string">AR038</rng:value>
-                </rng:element>
-                <rng:element name="StartDate">
-                  <rng:data type="string"/>
-                </rng:element>
-              </rng:element>
-            </rng:element>
-          </rng:oneOrMore>
           <rng:element name="ArchiveObject">
             <xsd:annotation>
               <xsd:documentation>archive unit title</xsd:documentation>
@@ -519,6 +440,85 @@
               </rng:element>
             </rng:element>
           </rng:element>
+          <rng:oneOrMore>
+            <rng:element name="ArchiveObject">
+              <xsd:annotation>
+                <xsd:documentation>archive unit title</xsd:documentation>
+              </xsd:annotation>
+              <rng:optional>
+                <rng:attribute name="Id">
+                  <rng:data type="ID"/>
+                </rng:attribute>
+              </rng:optional>
+              <rng:element name="Name">
+                <rng:optional>
+                  <rng:attribute name="languageID">
+                    <rng:data type="language"/>
+                  </rng:attribute>
+                </rng:optional>
+                <rng:data type="string"/>
+              </rng:element>
+              <rng:element name="ContentDescription">
+                <rng:optional>
+                  <rng:attribute name="Id">
+                    <rng:data type="ID"/>
+                  </rng:attribute>
+                </rng:optional>
+                <rng:element name="DescriptionLevel">
+                  <rng:attribute name="listVersionID">
+                    <rng:value type="token">edition 2009</rng:value>
+                  </rng:attribute>
+                  <rng:value type="string">file</rng:value>
+                </rng:element>
+                <rng:element name="Language">
+                  <rng:attribute name="listVersionID">
+                    <rng:value type="token">edition 2009</rng:value>
+                  </rng:attribute>
+                  <rng:data type="string"/>
+                </rng:element>
+              </rng:element>
+              <rng:optional>
+                <rng:element name="Appraisal">
+                  <rng:optional>
+                    <rng:attribute name="Id">
+                      <rng:data type="ID"/>
+                    </rng:attribute>
+                  </rng:optional>
+                  <rng:element name="Code">
+                    <rng:attribute name="listVersionID">
+                      <rng:value type="token">edition 2009</rng:value>
+                    </rng:attribute>
+                    <rng:data type="string"/>
+                  </rng:element>
+                  <rng:element name="Duration">
+                    <rng:data type="string"/>
+                  </rng:element>
+                  <rng:element name="StartDate">
+                    <rng:data type="string"/>
+                  </rng:element>
+                </rng:element>
+              </rng:optional>
+              <rng:element name="AccessRestrictionRule">
+                <xsd:annotation>
+                  <xsd:documentation>restrict</xsd:documentation>
+                </xsd:annotation>
+                <rng:optional>
+                  <rng:attribute name="Id">
+                    <rng:data type="ID"/>
+                  </rng:attribute>
+                </rng:optional>
+                <rng:element name="Code">
+                  <rng:attribute name="listVersionID">
+                    <rng:value type="token">edition 2009</rng:value>
+                  </rng:attribute>
+                  <rng:value type="string">AR038</rng:value>
+                </rng:element>
+                <rng:element name="StartDate">
+                  <rng:data type="string"/>
+                </rng:element>
+              </rng:element>
+            </rng:element>
+          </rng:oneOrMore>
           <rng:zeroOrMore>
             <rng:element name="Document">
               <xsd:annotation>
--- a/test/data/seda_1_export.xsd	Fri Sep 01 11:19:42 2017 +0200
+++ b/test/data/seda_1_export.xsd	Fri Sep 01 17:00:09 2017 +0200
@@ -251,88 +251,6 @@
                   <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
                 </xsd:complexType>
               </xsd:element>
-              <xsd:element maxOccurs="unbounded" name="ArchiveObject">
-                <xsd:annotation>
-                  <xsd:documentation>archive unit title</xsd:documentation>
-                </xsd:annotation>
-                <xsd:complexType>
-                  <xsd:sequence>
-                    <xsd:element name="Name">
-                      <xsd:complexType>
-                        <xsd:simpleContent>
-                          <xsd:extension base="udt:TextType">
-                            <xsd:attribute name="languageID" type="xsd:language" use="optional"/>
-                          </xsd:extension>
-                        </xsd:simpleContent>
-                      </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element name="ContentDescription">
-                      <xsd:complexType>
-                        <xsd:sequence>
-                          <xsd:element fixed="file" name="DescriptionLevel">
-                            <xsd:complexType>
-                              <xsd:simpleContent>
-                                <xsd:extension base="qdt:CodeDescriptionLevelType">
-                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
-                                </xsd:extension>
-                              </xsd:simpleContent>
-                            </xsd:complexType>
-                          </xsd:element>
-                          <xsd:element name="Language">
-                            <xsd:complexType>
-                              <xsd:simpleContent>
-                                <xsd:extension base="qdt:CodeLanguageType">
-                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
-                                </xsd:extension>
-                              </xsd:simpleContent>
-                            </xsd:complexType>
-                          </xsd:element>
-                        </xsd:sequence>
-                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
-                      </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element minOccurs="0" name="Appraisal">
-                      <xsd:complexType>
-                        <xsd:sequence>
-                          <xsd:element name="Code">
-                            <xsd:complexType>
-                              <xsd:simpleContent>
-                                <xsd:extension base="qdt:CodeAppraisalType">
-                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
-                                </xsd:extension>
-                              </xsd:simpleContent>
-                            </xsd:complexType>
-                          </xsd:element>
-                          <xsd:element name="Duration" type="qdt:ArchivesDurationType"/>
-                          <xsd:element name="StartDate" type="udt:DateType"/>
-                        </xsd:sequence>
-                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
-                      </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element name="AccessRestrictionRule">
-                      <xsd:annotation>
-                        <xsd:documentation>restrict</xsd:documentation>
-                      </xsd:annotation>
-                      <xsd:complexType>
-                        <xsd:sequence>
-                          <xsd:element fixed="AR038" name="Code">
-                            <xsd:complexType>
-                              <xsd:simpleContent>
-                                <xsd:extension base="qdt:CodeAccessRestrictionType">
-                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
-                                </xsd:extension>
-                              </xsd:simpleContent>
-                            </xsd:complexType>
-                          </xsd:element>
-                          <xsd:element name="StartDate" type="udt:DateType"/>
-                        </xsd:sequence>
-                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
-                      </xsd:complexType>
-                    </xsd:element>
-                  </xsd:sequence>
-                  <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
-                </xsd:complexType>
-              </xsd:element>
               <xsd:element name="ArchiveObject">
                 <xsd:annotation>
                   <xsd:documentation>archive unit title</xsd:documentation>
@@ -419,6 +337,88 @@
                   <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
                 </xsd:complexType>
               </xsd:element>
+              <xsd:element maxOccurs="unbounded" name="ArchiveObject">
+                <xsd:annotation>
+                  <xsd:documentation>archive unit title</xsd:documentation>
+                </xsd:annotation>
+                <xsd:complexType>
+                  <xsd:sequence>
+                    <xsd:element name="Name">
+                      <xsd:complexType>
+                        <xsd:simpleContent>
+                          <xsd:extension base="udt:TextType">
+                            <xsd:attribute name="languageID" type="xsd:language" use="optional"/>
+                          </xsd:extension>
+                        </xsd:simpleContent>
+                      </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="ContentDescription">
+                      <xsd:complexType>
+                        <xsd:sequence>
+                          <xsd:element fixed="file" name="DescriptionLevel">
+                            <xsd:complexType>
+                              <xsd:simpleContent>
+                                <xsd:extension base="qdt:CodeDescriptionLevelType">
+                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
+                                </xsd:extension>
+                              </xsd:simpleContent>
+                            </xsd:complexType>
+                          </xsd:element>
+                          <xsd:element name="Language">
+                            <xsd:complexType>
+                              <xsd:simpleContent>
+                                <xsd:extension base="qdt:CodeLanguageType">
+                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
+                                </xsd:extension>
+                              </xsd:simpleContent>
+                            </xsd:complexType>
+                          </xsd:element>
+                        </xsd:sequence>
+                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+                      </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element minOccurs="0" name="Appraisal">
+                      <xsd:complexType>
+                        <xsd:sequence>
+                          <xsd:element name="Code">
+                            <xsd:complexType>
+                              <xsd:simpleContent>
+                                <xsd:extension base="qdt:CodeAppraisalType">
+                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
+                                </xsd:extension>
+                              </xsd:simpleContent>
+                            </xsd:complexType>
+                          </xsd:element>
+                          <xsd:element name="Duration" type="qdt:ArchivesDurationType"/>
+                          <xsd:element name="StartDate" type="udt:DateType"/>
+                        </xsd:sequence>
+                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+                      </xsd:complexType>
+                    </xsd:element>
+                    <xsd:element name="AccessRestrictionRule">
+                      <xsd:annotation>
+                        <xsd:documentation>restrict</xsd:documentation>
+                      </xsd:annotation>
+                      <xsd:complexType>
+                        <xsd:sequence>
+                          <xsd:element fixed="AR038" name="Code">
+                            <xsd:complexType>
+                              <xsd:simpleContent>
+                                <xsd:extension base="qdt:CodeAccessRestrictionType">
+                                  <xsd:attribute fixed="edition 2009" name="listVersionID" type="xsd:token" use="required"/>
+                                </xsd:extension>
+                              </xsd:simpleContent>
+                            </xsd:complexType>
+                          </xsd:element>
+                          <xsd:element name="StartDate" type="udt:DateType"/>
+                        </xsd:sequence>
+                        <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+                      </xsd:complexType>
+                    </xsd:element>
+                  </xsd:sequence>
+                  <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+                </xsd:complexType>
+              </xsd:element>
               <xsd:element maxOccurs="unbounded" minOccurs="0" name="Document">
                 <xsd:annotation>
                   <xsd:documentation>data object title</xsd:documentation>
--- a/test/test_profile_generation.py	Fri Sep 01 11:19:42 2017 +0200
+++ b/test/test_profile_generation.py	Fri Sep 01 17:00:09 2017 +0200
@@ -448,6 +448,7 @@
             unit, unit_alt, unit_alt_seq = testutils.create_archive_unit(
                 transfer, user_cardinality=u'0..n')
 
+            create('SEDAKeyword', user_cardinality=u'0..n', seda_keyword=unit_alt_seq)
             kw = create('SEDAKeyword', seda_keyword=unit_alt_seq,
                         keyword_content=u'kwick')
             kwr_e = create('SEDAKeywordReference', seda_keyword_reference_from=kw)
@@ -456,10 +457,19 @@
 
             profile = self.profile_etree(transfer)
 
-            kwc = self.get_element(profile, 'KeywordContent')
-            self.assertElementDefinition(kwc, {'name': 'KeywordContent',
-                                               'type': 'xsd:string',
-                                               'fixed': 'kwick'})
+            keywords = self.get_elements(profile, 'Keyword')
+            self.assertElementDefinition(keywords[0], {'name': 'Keyword'})
+            self.assertElementDefinition(keywords[1], {'name': 'Keyword',
+                                                       'minOccurs': '0',
+                                                       'maxOccurs': 'unbounded'})
+
+            kwc = self.get_elements(profile, 'KeywordContent')
+            self.assertElementDefinition(kwc[0], {'name': 'KeywordContent',
+                                                  'type': 'xsd:string',
+                                                  'fixed': 'kwick'})
+            self.assertElementDefinition(kwc[1], {'name': 'KeywordContent',
+                                                  'type': 'xsd:string'})
+
             kwt = self.get_element(profile, 'KeywordType')
             self.assertElementDefinition(kwt, {'name': 'KeywordType',
                                                'type': 'xsd:token',
@@ -942,32 +952,102 @@
             root = self.profile_etree(transfer, 'SEDA-0.2.rng')
         self.check_xsd_profile(root, self.datapath('seda_02_bordereau_ref.xml'))
 
-    def test_children_order(self):
+    def test_xsd_children_order(self):
+        with self.admin_access.cnx() as cnx:
+            create = cnx.create_entity
+
+            transfer = create('SEDAArchiveTransfer', title=u'test profile',
+                              simplified_profile=True)
+            create('SEDAAccessRule',  # XXX mandatory for seda 1.0
+                   user_cardinality=u'1',
+                   seda_access_rule=transfer,
+                   seda_seq_access_rule_rule=create(
+                       'SEDASeqAccessRuleRule', reverse_seda_start_date=create('SEDAStartDate')))
+            unit, unit_alt, unit_alt_seq = testutils.create_archive_unit(
+                transfer, user_cardinality=u'0..1')
+            testutils.create_archive_unit(transfer)
+            testutils.create_archive_unit(unit_alt_seq, user_cardinality=u'0..1')
+            testutils.create_archive_unit(unit_alt_seq)
+            bdo = testutils.create_data_object(transfer, user_cardinality=u'0..1')
+            create('SEDADataObjectReference',
+                   seda_data_object_reference=unit_alt_seq,
+                   seda_data_object_reference_id=bdo)
+            bdo2 = testutils.create_data_object(transfer)
+            create('SEDADataObjectReference',
+                   seda_data_object_reference=unit_alt_seq,
+                   seda_data_object_reference_id=bdo2)
+            cnx.commit()
+
+            # ensure Document appears before Contains in SEDA 0.2 + cardinality based order
+            adapter = transfer.cw_adapt_to('SEDA-0.2.xsd')
+            root = etree.Element('test-root')
+            adapter.xsd_children(root, unit)
+            self.assertEqual([node.attrib['name'] for node in root],
+                             ['Document', 'Document', 'Contains', 'Contains'])
+            self.assertEqual([node.attrib.get('minOccurs') for node in root],
+                             [None, '0', None, '0'])
+            # test cardinality on transfer's children
+            root = etree.Element('test-root')
+            adapter.xsd_transfer(root, transfer)
+            # [0][-1][0][-2:] xs:element / xs:complexType / xs:sequence, picking
+            # -1 to avoid going into the annotation and -2: to only keep the two
+            # latest children
+            self.assertEqual([(node.attrib['name'], node.attrib.get('minOccurs'))
+                              for node in root[0][-1][0][-2:]],
+                             [('Contains', None), ('Contains', '0')])
+
+            # ensure Document appears after ArchiveObject in SEDA 1 + cardinality based order
+            adapter = transfer.cw_adapt_to('SEDA-1.0.xsd')
+            root = etree.Element('test-root')
+            adapter.xsd_children(root, unit)
+            self.assertEqual([node.attrib['name'] for node in root],
+                             ['ArchiveObject', 'ArchiveObject', 'Document', 'Document'])
+            self.assertEqual([node.attrib.get('minOccurs') for node in root],
+                             [None, '0', None, '0'])
+            # test cardinality on transfer's children
+            root = etree.Element('test-root')
+            adapter.xsd_transfer(root, transfer)
+            # xs:element(CustodialHistory) / xs:complexType / xs:sequence
+            self.assertEqual([(node.attrib['name'], node.attrib.get('minOccurs'))
+                              for node in root[0][-1][0][-2:]],
+                             [('Archive', None), ('Archive', '0')])
+
+    def test_keywords_order(self):
         with self.admin_access.cnx() as cnx:
             create = cnx.create_entity
 
             transfer = create('SEDAArchiveTransfer', title=u'test profile',
                               simplified_profile=True)
             unit, unit_alt, unit_alt_seq = testutils.create_archive_unit(transfer)
-            subunit, subunit_alt, subunit_alt_seq = testutils.create_archive_unit(
-                unit_alt_seq)
-            bdo = testutils.create_data_object(transfer)
-            create('SEDADataObjectReference',
-                   seda_data_object_reference=unit_alt_seq,
-                   seda_data_object_reference_id=bdo)
-            cnx.commit()
+            create('SEDAKeyword', user_cardinality=u'0..n', seda_keyword=unit_alt_seq)
+            create('SEDAKeyword', seda_keyword=unit_alt_seq)
 
-            # ensure Document appears before Contains in SEDA 0.2
-            adapter = transfer.cw_adapt_to('SEDA-0.2.xsd')
-            root = etree.Element('test-root')
-            adapter.xsd_children(root, unit)
-            self.assertEqual([node.attrib['name'] for node in root], ['Document', 'Contains'])
-
-            # ensure Document appears after ArchiveObject in SEDA 1
             adapter = transfer.cw_adapt_to('SEDA-1.0.xsd')
             root = etree.Element('test-root')
-            adapter.xsd_children(root, unit)
-            self.assertEqual([node.attrib['name'] for node in root], ['ArchiveObject', 'Document'])
+            adapter.xsd_keywords(root, unit_alt_seq)
+            self.assertEqual([(node.attrib['name'], node.attrib.get('minOccurs')) for node in root],
+                             [('Keyword', None), ('Keyword', '0')])
+
+    def test_custodialhistory_order(self):
+        with self.admin_access.cnx() as cnx:
+            create = cnx.create_entity
+
+            transfer = create('SEDAArchiveTransfer', title=u'test profile',
+                              simplified_profile=True)
+            unit, unit_alt, unit_alt_seq = testutils.create_archive_unit(transfer)
+            create('SEDACustodialHistoryItem',
+                   user_cardinality=u'0..1',
+                   seda_custodial_history_item=unit_alt_seq)
+            create('SEDACustodialHistoryItem',
+                   seda_custodial_history_item=unit_alt_seq)
+
+            adapter = transfer.cw_adapt_to('SEDA-1.0.xsd')
+            root = etree.Element('test-root')
+            adapter.xsd_custodial_history(root, unit_alt_seq)
+            history = root[0][0][0]  # xs:element(CustodialHistory) / xs:complexType / xs:sequence
+            self.assertEqual([(node.attrib['name'], node.attrib.get('minOccurs'))
+                              for node in history],
+                             [('CustodialHistoryItem', None), ('CustodialHistoryItem', '0')])
 
 
 class SEDAExportUnitTest(unittest.TestCase):