[pkg] move to new "cube as packages" layout
authorPhilippe Pepiot <philippe.pepiot@logilab.fr>
Thu, 23 Feb 2017 16:17:34 +0100
changeset 2377 3315ecaad760
parent 2376 e60881f8798e
child 2387 53f6b75e2547
[pkg] move to new "cube as packages" layout Depend on cubicweb >= 3.24
MANIFEST.in
__init__.py
__pkginfo__.py
cubicweb-seda.spec
cubicweb_seda/__init__.py
cubicweb_seda/__pkginfo__.py
cubicweb_seda/data/cubes.editionext.js
cubicweb_seda/data/cubes.jqtree.css
cubicweb_seda/data/cubes.jqtree.js
cubicweb_seda/data/cubes.seda.css
cubicweb_seda/data/cubes.seda.form.js
cubicweb_seda/data/cubes.seda.js
cubicweb_seda/data/cubes.skoscomplete.js
cubicweb_seda/data/jqtree.css
cubicweb_seda/data/tree.jquery.js
cubicweb_seda/dataimport.py
cubicweb_seda/entities/__init__.py
cubicweb_seda/entities/custom.py
cubicweb_seda/entities/diag.py
cubicweb_seda/entities/generated.py
cubicweb_seda/entities/html_generation.py
cubicweb_seda/entities/itree.py
cubicweb_seda/entities/profile_generation.py
cubicweb_seda/hooks.py
cubicweb_seda/i18n/en.po
cubicweb_seda/i18n/fr.po
cubicweb_seda/i18n/static-messages.pot
cubicweb_seda/migration/0.5.1_Any.py
cubicweb_seda/migration/0.6.0_Any.py
cubicweb_seda/migration/data/access_control.csv
cubicweb_seda/migration/data/classification_levels.csv
cubicweb_seda/migration/data/code_keyword_type.csv
cubicweb_seda/migration/data/document_type_code.csv
cubicweb_seda/migration/data/dua.csv
cubicweb_seda/migration/data/encodings.csv
cubicweb_seda/migration/data/event_types.csv
cubicweb_seda/migration/data/file_formats.csv
cubicweb_seda/migration/data/final_action_appraisal_code_type.csv
cubicweb_seda/migration/data/final_action_storage_code_type.csv
cubicweb_seda/migration/data/languages.csv
cubicweb_seda/migration/data/level_type.csv
cubicweb_seda/migration/data/measurement_units_type.csv
cubicweb_seda/migration/data/measurement_weight_units_type.csv
cubicweb_seda/migration/data/mime_types.csv
cubicweb_seda/migration/postcreate.py
cubicweb_seda/schema/__init__.py
cubicweb_seda/schema/seda2.py
cubicweb_seda/site_cubicweb.py
cubicweb_seda/uiprops.py
cubicweb_seda/views/__init__.py
cubicweb_seda/views/archivetransfer.py
cubicweb_seda/views/archiveunit.py
cubicweb_seda/views/clone.py
cubicweb_seda/views/dataobject.py
cubicweb_seda/views/export.py
cubicweb_seda/views/facets.py
cubicweb_seda/views/jqtree.py
cubicweb_seda/views/mgmt_rules.py
cubicweb_seda/views/patches.py
cubicweb_seda/views/sedalib.py
cubicweb_seda/views/sedatree.py
cubicweb_seda/views/simplified.py
cubicweb_seda/views/uicfg.py
cubicweb_seda/views/viewlib.py
cubicweb_seda/views/widgets.py
cubicweb_seda/xsd.py
cubicweb_seda/xsd/seda-2.0-descriptive.xsd
cubicweb_seda/xsd/seda-2.0-main.xsd
cubicweb_seda/xsd/seda-2.0-management.xsd
cubicweb_seda/xsd/seda-2.0-ontology.xsd
cubicweb_seda/xsd/seda-2.0-technical.xsd
cubicweb_seda/xsd/seda-2.0-types.xsd
cubicweb_seda/xsd/xlink.xsd
cubicweb_seda/xsd/xml.xsd
cubicweb_seda/xsd2concepts.py
cubicweb_seda/xsd2entities.py
cubicweb_seda/xsd2uicfg.py
cubicweb_seda/xsd2yams.py
data/cubes.editionext.js
data/cubes.jqtree.css
data/cubes.jqtree.js
data/cubes.seda.css
data/cubes.seda.form.js
data/cubes.seda.js
data/cubes.skoscomplete.js
data/jqtree.css
data/tree.jquery.js
dataimport.py
debian/control
entities/__init__.py
entities/custom.py
entities/diag.py
entities/generated.py
entities/html_generation.py
entities/itree.py
entities/profile_generation.py
hooks.py
i18n/en.po
i18n/fr.po
i18n/static-messages.pot
migration/0.5.1_Any.py
migration/0.6.0_Any.py
migration/data/access_control.csv
migration/data/classification_levels.csv
migration/data/code_keyword_type.csv
migration/data/document_type_code.csv
migration/data/dua.csv
migration/data/encodings.csv
migration/data/event_types.csv
migration/data/file_formats.csv
migration/data/final_action_appraisal_code_type.csv
migration/data/final_action_storage_code_type.csv
migration/data/languages.csv
migration/data/level_type.csv
migration/data/measurement_units_type.csv
migration/data/measurement_weight_units_type.csv
migration/data/mime_types.csv
migration/postcreate.py
schema/__init__.py
schema/seda2.py
setup.py
site_cubicweb.py
test/test_dataimport.py
test/test_entities.py
test/test_profile_generation.py
test/test_views.py
test/test_xsd2yams.py
tox.ini
uiprops.py
views/__init__.py
views/archivetransfer.py
views/archiveunit.py
views/clone.py
views/dataobject.py
views/export.py
views/facets.py
views/jqtree.py
views/mgmt_rules.py
views/patches.py
views/sedalib.py
views/sedatree.py
views/simplified.py
views/uicfg.py
views/viewlib.py
views/widgets.py
xsd.py
xsd/seda-2.0-descriptive.xsd
xsd/seda-2.0-main.xsd
xsd/seda-2.0-management.xsd
xsd/seda-2.0-ontology.xsd
xsd/seda-2.0-technical.xsd
xsd/seda-2.0-types.xsd
xsd/xlink.xsd
xsd/xml.xsd
xsd2concepts.py
xsd2entities.py
xsd2uicfg.py
xsd2yams.py
--- a/MANIFEST.in	Thu Feb 23 11:50:39 2017 +0100
+++ b/MANIFEST.in	Thu Feb 23 16:17:34 2017 +0100
@@ -1,12 +1,13 @@
-include *.py
-include */*.py
+recursive-include cubicweb_seda *.py
+recursive-include cubicweb_seda/data *.gif *.png *.ico *.css *.js
+recursive-include cubicweb_seda/xsd *.xsd
+recursive-include doc *.rst *.py
+recursive-include test *.py
+include cubicweb_seda/migration/data/*.csv
+include cubicweb_seda/i18n/*.po cubicweb_seda/i18n/*.pot
+include test/data/bootstrap_cubes test/data/*.xml test/data/*.xsd test/data/*.rng
 include tox.ini dev-requirements.txt
-include doc/*.rst
-include i18n/*.po
-include test/data/bootstrap_cubes test/data/*.xml test/data/*.xsd test/data/*.rng
-recursive-include data *.gif *.png *.ico *.css *.js
-recursive-include xsd *.xsd
-recursive-include migration *.py *.csv
 
+prune __pkginfo__.py
 prune cubicweb-seda.spec
 prune debian
--- a/__init__.py	Thu Feb 23 11:50:39 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact@logilab.fr
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-"""cubicweb-seda application package
-
-Data Exchange Standard for Archival
-"""
-
-from cubicweb import neg_role
-
-from cubes.compound import structure_def, skip_rtypes_set
-
-
-def seda_profile_container_def(schema):
-    """Define container for SEDAProfile"""
-    return structure_def(schema, 'SEDAArchiveTransfer').items()
-
-
-def iter_external_rdefs(eschema, skip_rtypes=skip_rtypes_set(['container'])):
-    """Return an iterator on (rdef, role) of external relations from entity schema (i.e.
-    non-composite relations).
-    """
-    for rschema, targets, role in eschema.relation_definitions():
-        if rschema in skip_rtypes:
-            continue
-        for target_etype in targets:
-            rdef = eschema.rdef(rschema, role, target_etype)
-            if rdef.composite:
-                continue
-            yield rdef, role
-
-
-def iter_all_rdefs(schema, container_etype):
-    """Return an iterator on (rdef, role) of all relations of the compound graph starting from the
-    given entity type, both internal (composite) and external (non-composite).
-    """
-    for etype, parent_rdefs in structure_def(schema, container_etype).items():
-        for rtype, role in parent_rdefs:
-            for rdef in schema[rtype].rdefs.values():
-                yield rdef, neg_role(role)
-        for rdef, role in iter_external_rdefs(schema[etype]):
-                yield rdef, role
-    for rdef, role in iter_external_rdefs(schema[container_etype]):
-        yield rdef, role
--- a/__pkginfo__.py	Thu Feb 23 11:50:39 2017 +0100
+++ b/__pkginfo__.py	Thu Feb 23 16:17:34 2017 +0100
@@ -1,60 +1,1 @@
-# pylint: disable=W0622
-"""cubicweb-seda application packaging information"""
-
-from os import listdir as _listdir
-from os.path import join, isdir
-from glob import glob
-
-modname = 'seda'
-distname = 'cubicweb-seda'
-
-numversion = (0, 7, 0)
-version = '.'.join(str(num) for num in numversion)
-
-license = 'LGPL'
-author = 'LOGILAB S.A. (Paris, FRANCE)'
-author_email = 'contact@logilab.fr'
-description = 'Data Exchange Standard for Archival'
-web = 'http://www.cubicweb.org/project/%s' % distname
-
-__depends__ = {
-    'cubicweb': '>= 3.23', 'six': '>= 1.4.0',
-    'cubicweb-eac': None,
-    'cubicweb-skos': '>= 0.12.1',
-    'cubicweb-compound': '>= 0.4',
-    'cubicweb-relationwidget': '>= 0.4',
-    'cubicweb-squareui': None,
-    'pyxst': None,
-    'rdflib': '>= 4.1',
-}
-__recommends__ = {}
-
-classifiers = [
-    'Environment :: Web Environment',
-    'Framework :: CubicWeb',
-    'Programming Language :: Python',
-    'Programming Language :: JavaScript',
-]
-
-THIS_CUBE_DIR = join('share', 'cubicweb', 'cubes', modname)
-
-
-def listdir(dirpath):
-    return [join(dirpath, fname) for fname in _listdir(dirpath)
-            if fname[0] != '.' and not fname.endswith('.pyc') and
-            not fname.endswith('~') and
-            not isdir(join(dirpath, fname))]
-
-
-data_files = [
-    # common files
-    [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
-]
-# check for possible extended cube layout
-for dname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data',
-              'wdoc', 'i18n', 'migration', 'migration/data',
-              'xsd'):
-    if isdir(dname):
-        data_files.append([join(THIS_CUBE_DIR, dname), listdir(dname)])
-# Note: here, you'll need to add subdirectories if you want
-# them to be included in the debian package
+cubicweb_seda/__pkginfo__.py
\ No newline at end of file
--- a/cubicweb-seda.spec	Thu Feb 23 11:50:39 2017 +0100
+++ b/cubicweb-seda.spec	Thu Feb 23 16:17:34 2017 +0100
@@ -20,7 +20,7 @@
 BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-buildroot
 
 BuildRequires:  %{python} %{python}-setuptools
-Requires:       cubicweb >= 3.23
+Requires:       cubicweb >= 3.24
 Requires:       cubicweb-eac
 Requires:       cubicweb-skos >= 0.12.1
 Requires:       cubicweb-compound >= 0.4
@@ -41,14 +41,14 @@
 %endif
 
 %install
-NO_SETUPTOOLS=1 %{__python} setup.py --quiet install --no-compile --prefix=%{_prefix} --root="$RPM_BUILD_ROOT"
-# remove generated .egg-info file
-rm -rf $RPM_BUILD_ROOT/usr/lib/python*
+%{__python} setup.py build
 
+%install
+%{__python} setup.py install --no-compile --skip-build --root $RPM_BUILD_ROOT
 
 %clean
 rm -rf $RPM_BUILD_ROOT
 
 %files
 %defattr(-, root, root)
-%{_prefix}/share/cubicweb/cubes/*
+%{_python_sitelib}/*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/__init__.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,56 @@
+# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-seda application package
+
+Data Exchange Standard for Archival
+"""
+
+from cubicweb import neg_role
+
+from cubes.compound import structure_def, skip_rtypes_set
+
+
+def seda_profile_container_def(schema):
+    """Define container for SEDAProfile"""
+    return structure_def(schema, 'SEDAArchiveTransfer').items()
+
+
+def iter_external_rdefs(eschema, skip_rtypes=skip_rtypes_set(['container'])):
+    """Return an iterator on (rdef, role) of external relations from entity schema (i.e.
+    non-composite relations).
+    """
+    for rschema, targets, role in eschema.relation_definitions():
+        if rschema in skip_rtypes:
+            continue
+        for target_etype in targets:
+            rdef = eschema.rdef(rschema, role, target_etype)
+            if rdef.composite:
+                continue
+            yield rdef, role
+
+
+def iter_all_rdefs(schema, container_etype):
+    """Return an iterator on (rdef, role) of all relations of the compound graph starting from the
+    given entity type, both internal (composite) and external (non-composite).
+    """
+    for etype, parent_rdefs in structure_def(schema, container_etype).items():
+        for rtype, role in parent_rdefs:
+            for rdef in schema[rtype].rdefs.values():
+                yield rdef, neg_role(role)
+        for rdef, role in iter_external_rdefs(schema[etype]):
+                yield rdef, role
+    for rdef, role in iter_external_rdefs(schema[container_etype]):
+        yield rdef, role
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/__pkginfo__.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,34 @@
+# pylint: disable=W0622
+"""cubicweb-seda application packaging information"""
+
+modname = 'seda'
+distname = 'cubicweb-seda'
+
+numversion = (0, 7, 0)
+version = '.'.join(str(num) for num in numversion)
+
+license = 'LGPL'
+author = 'LOGILAB S.A. (Paris, FRANCE)'
+author_email = 'contact@logilab.fr'
+description = 'Data Exchange Standard for Archival'
+web = 'http://www.cubicweb.org/project/%s' % distname
+
+__depends__ = {
+    'cubicweb': '>= 3.24',
+    'six': '>= 1.4.0',
+    'cubicweb-eac': None,
+    'cubicweb-skos': '>= 0.12.1',
+    'cubicweb-compound': '>= 0.4',
+    'cubicweb-relationwidget': '>= 0.4',
+    'cubicweb-squareui': None,
+    'pyxst': None,
+    'rdflib': '>= 4.1',
+}
+__recommends__ = {}
+
+classifiers = [
+    'Environment :: Web Environment',
+    'Framework :: CubicWeb',
+    'Programming Language :: Python',
+    'Programming Language :: JavaScript',
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/data/cubes.editionext.js	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,35 @@
+// copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+// contact http://www.logilab.fr -- mailto:contact@logilab.fr
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation, either version 2.1 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+editext = {
+    relateWidget: function(domid, search_url, title, multiple, onValidate) {
+        options = {'dialogOptions': {'title': title},
+                   'editOptions': {'required': true,
+                                   'multiple': multiple,
+                                   'searchurl': search_url}
+                  };
+        options.onValidate = onValidate;
+        cw.jqNode(domid).relationwidget(options);
+    },
+
+    buildSedaImportValidate: function(eid) {
+        var validate = function(selected) {
+            const cloned = Object.keys(selected).join(',');
+            document.location = BASE_URL + 'seda.doimport?eid=' + eid + '&cloned=' + cloned;
+        };
+        return validate;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/data/cubes.jqtree.css	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,14 @@
+/* add span/a to override default rules */
+
+ul.jqtree-tree span.jqtree-title {
+  margin-left: 0;
+}
+
+ul.jqtree-tree i.glyphicon {
+  font-size: 80%;
+  color: #777;
+}
+
+ul.jqtree-tree a.jqtree-toggler.jqtree-toggler-left {
+  margin-right: 0.1em;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/data/cubes.jqtree.js	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,86 @@
+// copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+// contact http://www.logilab.fr -- mailto:contact@logilab.fr
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation, either version 2.1 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+jqtree = {
+    jqTree: function(domid, dragAndDrop) {
+        var $tree = cw.jqNode(domid);
+        // tree display and basic controls.
+        $tree.tree({
+            dragAndDrop: dragAndDrop,
+            autoOpen: 0,  // only open level-0
+            selectable: false,
+            autoEscape: false,
+            closedIcon: $('<i class="glyphicon glyphicon-expand"></i>'),
+            openedIcon: $('<i class="glyphicon glyphicon-collapse-down"></i>'),
+            onCanMove: function(node) {
+                return node.maybeMoved;
+            },
+            onCanMoveTo: function(moved_node, target_node, position) {
+                if ( target_node.id === undefined ) {
+                    return false;
+                } else if ( position != 'inside' ) {
+                    // moving before/after is not supported
+                    return false;
+                } else {
+                    // avoid moving into the same parent
+                    function isMovedNode(element, index, array) {
+                        return element.id == moved_node.id;
+                    }
+                    if ( target_node.children.some(isMovedNode) ) {
+                        return false;
+                    }
+                    // ensure the new parent target accept the moved node
+                    return target_node.maybeParentOf.indexOf(moved_node.type) !== -1;
+                }
+            },
+            onCreateLi: function(node, $li) {
+                $li.find('.jqtree-title').addClass(node.type);
+
+                var selectedId = $tree.tree('getTree').children[0].selected;
+
+                if ( selectedId !== null && node.id === selectedId ) {
+                    // add "jqtreeNodeSelected" CSS class so that the current
+                    // element in the tree gets highlighted.
+                    $li.find('.jqtree-title').addClass('jqtreeNodeSelected');
+                }
+            }
+        });
+        // tree events bindings.
+        $tree.bind(
+            'tree.init',
+            function() {
+                var selectedId = $tree.tree('getTree').children[0].selected;
+                var node = $tree.tree('getNodeById', selectedId);
+                var parentNode = node.parent;
+                while (parentNode !== null) {
+                    $tree.tree('openNode', parentNode);
+                    parentNode = parentNode.parent;
+                }
+            }
+        );
+        $tree.bind(
+            'tree.move',
+            function(event) {
+                event.preventDefault();
+                // do the move first, and then POST back.
+                event.move_info.do_move();
+                asyncRemoteExec('jqtree_reparent', event.move_info.moved_node.id,
+                                event.move_info.target_node.id);
+            }
+        );
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/data/cubes.seda.css	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,40 @@
+form.releditForm div.iformTitle {
+  background: transparent;
+  border: none;
+  margin-top: 1em;
+  margin-bottom: 0.5em;
+}
+
+div.iformTitle span {
+  font-weight: bold;
+}
+
+form.releditForm div.iformBody label {
+  font-weight: normal;
+  padding-left: 1em;
+}
+
+div.btn-group {
+  margin-bottom: 1em;
+}
+
+.metaField .control-label {
+  font-weight: normal;
+  width: 20%;
+}
+
+.jqtreeNodeSelected {
+  font-weight: bold;
+}
+
+.SEDAArchiveUnit > a {
+  color: #06A3A3;
+}
+
+.SEDABinaryDataObject > a, .SEDAPhysicalDataObject > a {
+  color: #AE8332;
+}
+
+.cw-table-primary-entity td {
+  width: 67%;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/data/cubes.seda.form.js	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,33 @@
+// overwrite CW's addInlineCreationForm function to propagate container eid information
+function addInlineCreationForm(peid, petype, ttype, rtype, role, i18nctx, insertBefore) {
+    insertBefore = insertBefore || cw.getNode('add' + rtype + ':' + peid + 'link').parentNode;
+    var ceid = $('#sedaContainerEID');
+    if (ceid.length == 1) {
+        form = {'sedaContainerEID': ceid.attr('value')}
+    } else {
+        form = null;
+    }
+    var args = ajaxFuncArgs('inline_creation_form', form, peid, petype, ttype, rtype, role, i18nctx);
+    var d = loadRemote(AJAX_BASE_URL, args);
+    d.addCallback(function(response) {
+        var dom = getDomFromResponse(response);
+        loadAjaxHtmlHead(dom);
+        var form = jQuery(dom);
+        form.css('display', 'none');
+        form.insertBefore(insertBefore).slideDown('fast');
+        updateInlinedEntitiesCounters(rtype, role);
+        reorderTabindex(null, $(insertBefore).closest('form')[0]);
+        jQuery(cw).trigger('inlinedform-added', form);
+        // if the inlined form contains a file input, we must force
+        // the form enctype to multipart/form-data
+        if (form.find('input:file').length) {
+            // NOTE: IE doesn't support dynamic enctype modification, we have
+            //       to set encoding too.
+            form.closest('form').attr('enctype', 'multipart/form-data').attr('encoding', 'multipart/form-data');
+        }
+        _postAjaxLoad(dom);
+    });
+    d.addErrback(function(xxx) {
+        cw.log('xxx =', xxx);
+    });
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/data/cubes.seda.js	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,32 @@
+// copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+// contact http://www.logilab.fr -- mailto:contact@logilab.fr
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation, either version 2.1 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+seda = {
+    toggleFormMetaVisibility: function(domid) {
+        var $node = cw.jqNode(domid);
+        $node.toggleClass('hidden');
+        var $a = $node.parent().children('a');
+        var classes = $a.attr('class').split(' ');
+        var icon = classes[0];
+        if (icon == 'icon-list-add') {
+            classes[0] = 'icon-up-open';
+        } else {
+            classes[0] = 'icon-list-add';
+        }
+        $a.attr('class', classes.join(' '));
+    },
+
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/data/cubes.skoscomplete.js	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,87 @@
+// copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+// contact http://www.logilab.fr -- mailto:contact@logilab.fr
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation, either version 2.1 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+// An autocompletion widget to select a concept from a vocabulary specified by another widget
+concept_autocomplete = {
+    initConceptAutoCompleteWidget: function(masterSelectId, dependentSelectId, ajaxFuncName) {
+        var masterSelect = cw.jqNode(masterSelectId);
+        // bind vocabulary select to update concept autocompletion input on value change
+        masterSelect.change(function() {
+            concept_autocomplete.updateCurrentSchemeEid(this);
+            concept_autocomplete.resetConceptFormField(dependentSelectId);
+        });
+        // initialize currentSchemeEid by looking the value of the master field
+        concept_autocomplete.updateCurrentSchemeEid(masterSelect);
+        // also bind the autocompletion widget
+        cw.jqNode(dependentSelectId+'Label')
+            .autocomplete({
+                source: function(request, response) {
+                    if (concept_autocomplete.currentSchemeEid) {
+                        var form = ajaxFuncArgs(ajaxFuncName,
+                                                {'q': request.term,
+                                                 'scheme': concept_autocomplete.currentSchemeEid});
+                        var d = loadRemote(AJAX_BASE_URL, form, 'POST');
+                        d.addCallback(function (suggestions) { response(suggestions); });
+                    }
+                },
+                focus: function( event, ui ) {
+                    cw.jqNode(dependentSelectId+'Label').val(ui.item.label);
+                    return false;
+                },
+                select: function(event, ui) {
+                    cw.jqNode(dependentSelectId+'Label').val(ui.item.label);
+                    cw.jqNode(dependentSelectId).val(ui.item.value);
+                    return false;
+                },
+                'mustMatch': true,
+                'limit': 100,
+                'delay': 300})
+            .tooltip({
+                tooltipClass: "ui-state-highlight"
+            });
+
+        // add key press and focusout event handlers so that value which isn't matching a vocabulary
+        // value will be erased
+        resetIfInvalidChoice = function() {
+            if (concept_autocomplete.currentSchemeEid) {
+                var validChoices = $.map($('ul.ui-autocomplete li'),
+                                         function(li) {return $(li).text();});
+                var value = cw.jqNode(dependentSelectId + 'Label').val();
+                if ($.inArray(value, validChoices) == -1) {
+                    concept_autocomplete.resetConceptFormField(dependentSelectId);
+                }
+            }
+        };
+        cw.jqNode(dependentSelectId+'Label').keypress(function(evt) {
+            if (evt.keyCode == $.ui.keyCode.ENTER || evt.keyCode == $.ui.keyCode.TAB) {
+                resetIfInvalidChoice();
+            }
+        });
+        cw.jqNode(dependentSelectId+'Label').focusout(function(evt) {
+            resetIfInvalidChoice();
+        });
+    },
+    updateCurrentSchemeEid: function(masterSelect) {
+        concept_autocomplete.currentSchemeEid = $(masterSelect).val();
+        if (concept_autocomplete.currentSchemeEid == '__cubicweb_internal_field__') {
+            concept_autocomplete.currentSchemeEid = null;
+        }
+    },
+    resetConceptFormField: function(dependentSelectId) {
+        cw.jqNode(dependentSelectId+'Label').val('');
+        cw.jqNode(dependentSelectId).val('');
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/data/jqtree.css	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,132 @@
+ul.jqtree-tree {
+  list-style: none outside;
+  margin-left: 0;
+  margin-bottom: 0;
+  padding: 0; }
+  ul.jqtree-tree ul.jqtree_common {
+    list-style: none outside;
+    margin-left: 12px;
+    margin-right: 0;
+    margin-bottom: 0;
+    padding: 0;
+    display: block; }
+  ul.jqtree-tree li.jqtree-closed > ul.jqtree_common {
+    display: none; }
+  ul.jqtree-tree li.jqtree_common {
+    clear: both;
+    list-style-type: none; }
+  ul.jqtree-tree .jqtree-toggler {
+    border-bottom: none;
+    color: #333;
+    text-decoration: none;
+    vertical-align: middle; }
+    ul.jqtree-tree .jqtree-toggler:hover {
+      color: #000;
+      text-decoration: none; }
+    ul.jqtree-tree .jqtree-toggler.jqtree-closed {
+      background-position: 0 0; }
+    ul.jqtree-tree .jqtree-toggler.jqtree-toggler-left {
+      margin-right: 0.5em; }
+    ul.jqtree-tree .jqtree-toggler.jqtree-toggler-right {
+      margin-left: 0.5em; }
+  ul.jqtree-tree .jqtree-element {
+    cursor: pointer;
+    position: relative; }
+  ul.jqtree-tree .jqtree-title {
+    color: #1C4257;
+    vertical-align: middle;
+    margin-left: 1.5em; }
+    ul.jqtree-tree .jqtree-title.jqtree-title-folder {
+      margin-left: 0; }
+  ul.jqtree-tree li.jqtree-folder {
+    margin-bottom: 4px; }
+    ul.jqtree-tree li.jqtree-folder.jqtree-closed {
+      margin-bottom: 1px; }
+  ul.jqtree-tree li.jqtree-ghost {
+    position: relative;
+    z-index: 10;
+    margin-right: 10px;
+    /* todo: add classes to span? */ }
+    ul.jqtree-tree li.jqtree-ghost span {
+      display: block; }
+    ul.jqtree-tree li.jqtree-ghost span.jqtree-circle {
+      border: solid 2px #0000ff;
+      -webkit-border-radius: 100px;
+      -moz-border-radius: 100px;
+      border-radius: 100px;
+      height: 8px;
+      width: 8px;
+      position: absolute;
+      top: -4px;
+      left: -6px;
+      -webkit-box-sizing: border-box;
+      -moz-box-sizing: border-box;
+      box-sizing: border-box; }
+    ul.jqtree-tree li.jqtree-ghost span.jqtree-line {
+      background-color: #0000ff;
+      height: 2px;
+      padding: 0;
+      position: absolute;
+      top: -1px;
+      left: 2px;
+      width: 100%; }
+    ul.jqtree-tree li.jqtree-ghost.jqtree-inside {
+      margin-left: 48px; }
+  ul.jqtree-tree span.jqtree-border {
+    position: absolute;
+    display: block;
+    left: -2px;
+    top: 0;
+    border: solid 2px #0000ff;
+    border-radius: 6px;
+    margin: 0;
+    box-sizing: content-box; }
+  ul.jqtree-tree li.jqtree-selected > .jqtree-element,
+  ul.jqtree-tree li.jqtree-selected > .jqtree-element:hover {
+    background-color: #97BDD6;
+    background: -webkit-gradient(linear, left top, left bottom, from(#BEE0F5), to(#89AFCA));
+    background: -moz-linear-gradient(top, #BEE0F5, #89AFCA);
+    background: -ms-linear-gradient(top, #BEE0F5, #89AFCA);
+    background: -o-linear-gradient(top, #BEE0F5, #89AFCA);
+    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); }
+  ul.jqtree-tree .jqtree-moving > .jqtree-element .jqtree-title {
+    outline: dashed 1px #0000ff; }
+
+ul.jqtree-tree.jqtree-rtl {
+  direction: rtl; }
+  ul.jqtree-tree.jqtree-rtl ul.jqtree_common {
+    margin-left: 0;
+    margin-right: 12px; }
+  ul.jqtree-tree.jqtree-rtl .jqtree-toggler {
+    margin-left: 0.5em;
+    margin-right: 0; }
+  ul.jqtree-tree.jqtree-rtl .jqtree-title {
+    margin-left: 0;
+    margin-right: 1.5em; }
+    ul.jqtree-tree.jqtree-rtl .jqtree-title.jqtree-title-folder {
+      margin-right: 0; }
+  ul.jqtree-tree.jqtree-rtl li.jqtree-ghost {
+    margin-right: 0;
+    margin-left: 10px; }
+    ul.jqtree-tree.jqtree-rtl li.jqtree-ghost span.jqtree-circle {
+      right: -6px; }
+    ul.jqtree-tree.jqtree-rtl li.jqtree-ghost span.jqtree-line {
+      right: 2px; }
+    ul.jqtree-tree.jqtree-rtl li.jqtree-ghost.jqtree-inside {
+      margin-left: 0;
+      margin-right: 48px; }
+  ul.jqtree-tree.jqtree-rtl span.jqtree-border {
+    right: -2px; }
+
+span.jqtree-dragging {
+  color: #fff;
+  background: #000;
+  opacity: 0.6;
+  cursor: pointer;
+  padding: 2px 8px; }
+
+/* IE 6, 7, 8 */
+@media \0screen\,screen\9  {
+  ul.jqtree-tree li.jqtree-ghost span.jqtree-circle {
+    background: url(jqtree-circle.png) no-repeat;
+    border: 0 none; } }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/data/tree.jquery.js	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,3575 @@
+/*
+JqTree 1.3.3
+
+Copyright 2015 Marco Braak
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+var $, DragAndDropHandler, DragElement, HitAreasGenerator, Position, VisibleNodeIterator, node_module,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+node_module = require('./node');
+
+Position = node_module.Position;
+
+$ = jQuery;
+
+DragAndDropHandler = (function() {
+  function DragAndDropHandler(tree_widget) {
+    this.tree_widget = tree_widget;
+    this.hovered_area = null;
+    this.$ghost = null;
+    this.hit_areas = [];
+    this.is_dragging = false;
+    this.current_item = null;
+  }
+
+  DragAndDropHandler.prototype.mouseCapture = function(position_info) {
+    var $element, node_element;
+    $element = $(position_info.target);
+    if (!this.mustCaptureElement($element)) {
+      return null;
+    }
+    if (this.tree_widget.options.onIsMoveHandle && !this.tree_widget.options.onIsMoveHandle($element)) {
+      return null;
+    }
+    node_element = this.tree_widget._getNodeElement($element);
+    if (node_element && this.tree_widget.options.onCanMove) {
+      if (!this.tree_widget.options.onCanMove(node_element.node)) {
+        node_element = null;
+      }
+    }
+    this.current_item = node_element;
+    return this.current_item !== null;
+  };
+
+  DragAndDropHandler.prototype.mouseStart = function(position_info) {
+    var offset;
+    this.refresh();
+    offset = $(position_info.target).offset();
+    this.drag_element = new DragElement(this.current_item.node, position_info.page_x - offset.left, position_info.page_y - offset.top, this.tree_widget.element);
+    this.is_dragging = true;
+    this.current_item.$element.addClass('jqtree-moving');
+    return true;
+  };
+
+  DragAndDropHandler.prototype.mouseDrag = function(position_info) {
+    var area, can_move_to;
+    this.drag_element.move(position_info.page_x, position_info.page_y);
+    area = this.findHoveredArea(position_info.page_x, position_info.page_y);
+    can_move_to = this.canMoveToArea(area);
+    if (can_move_to && area) {
+      if (!area.node.isFolder()) {
+        this.stopOpenFolderTimer();
+      }
+      if (this.hovered_area !== area) {
+        this.hovered_area = area;
+        if (this.mustOpenFolderTimer(area)) {
+          this.startOpenFolderTimer(area.node);
+        } else {
+          this.stopOpenFolderTimer();
+        }
+        this.updateDropHint();
+      }
+    } else {
+      this.removeHover();
+      this.removeDropHint();
+      this.stopOpenFolderTimer();
+    }
+    if (!area) {
+      if (this.tree_widget.options.onDragMove != null) {
+        this.tree_widget.options.onDragMove(this.current_item.node, position_info.original_event);
+      }
+    }
+    return true;
+  };
+
+  DragAndDropHandler.prototype.mustCaptureElement = function($element) {
+    return !$element.is('input,select');
+  };
+
+  DragAndDropHandler.prototype.canMoveToArea = function(area) {
+    var position_name;
+    if (!area) {
+      return false;
+    } else if (this.tree_widget.options.onCanMoveTo) {
+      position_name = Position.getName(area.position);
+      return this.tree_widget.options.onCanMoveTo(this.current_item.node, area.node, position_name);
+    } else {
+      return true;
+    }
+  };
+
+  DragAndDropHandler.prototype.mouseStop = function(position_info) {
+    var current_item;
+    this.moveItem(position_info);
+    this.clear();
+    this.removeHover();
+    this.removeDropHint();
+    this.removeHitAreas();
+    current_item = this.current_item;
+    if (this.current_item) {
+      this.current_item.$element.removeClass('jqtree-moving');
+      this.current_item = null;
+    }
+    this.is_dragging = false;
+    if (!this.hovered_area && current_item) {
+      if (this.tree_widget.options.onDragStop != null) {
+        this.tree_widget.options.onDragStop(current_item.node, position_info.original_event);
+      }
+    }
+    return false;
+  };
+
+  DragAndDropHandler.prototype.refresh = function() {
+    this.removeHitAreas();
+    if (this.current_item) {
+      this.generateHitAreas();
+      this.current_item = this.tree_widget._getNodeElementForNode(this.current_item.node);
+      if (this.is_dragging) {
+        return this.current_item.$element.addClass('jqtree-moving');
+      }
+    }
+  };
+
+  DragAndDropHandler.prototype.removeHitAreas = function() {
+    return this.hit_areas = [];
+  };
+
+  DragAndDropHandler.prototype.clear = function() {
+    this.drag_element.remove();
+    return this.drag_element = null;
+  };
+
+  DragAndDropHandler.prototype.removeDropHint = function() {
+    if (this.previous_ghost) {
+      return this.previous_ghost.remove();
+    }
+  };
+
+  DragAndDropHandler.prototype.removeHover = function() {
+    return this.hovered_area = null;
+  };
+
+  DragAndDropHandler.prototype.generateHitAreas = function() {
+    var hit_areas_generator;
+    hit_areas_generator = new HitAreasGenerator(this.tree_widget.tree, this.current_item.node, this.getTreeDimensions().bottom);
+    return this.hit_areas = hit_areas_generator.generate();
+  };
+
+  DragAndDropHandler.prototype.findHoveredArea = function(x, y) {
+    var area, dimensions, high, low, mid;
+    dimensions = this.getTreeDimensions();
+    if (x < dimensions.left || y < dimensions.top || x > dimensions.right || y > dimensions.bottom) {
+      return null;
+    }
+    low = 0;
+    high = this.hit_areas.length;
+    while (low < high) {
+      mid = (low + high) >> 1;
+      area = this.hit_areas[mid];
+      if (y < area.top) {
+        high = mid;
+      } else if (y > area.bottom) {
+        low = mid + 1;
+      } else {
+        return area;
+      }
+    }
+    return null;
+  };
+
+  DragAndDropHandler.prototype.mustOpenFolderTimer = function(area) {
+    var node;
+    node = area.node;
+    return node.isFolder() && !node.is_open && area.position === Position.INSIDE;
+  };
+
+  DragAndDropHandler.prototype.updateDropHint = function() {
+    var node_element;
+    if (!this.hovered_area) {
+      return;
+    }
+    this.removeDropHint();
+    node_element = this.tree_widget._getNodeElementForNode(this.hovered_area.node);
+    return this.previous_ghost = node_element.addDropHint(this.hovered_area.position);
+  };
+
+  DragAndDropHandler.prototype.startOpenFolderTimer = function(folder) {
+    var openFolder;
+    openFolder = (function(_this) {
+      return function() {
+        return _this.tree_widget._openNode(folder, _this.tree_widget.options.slide, function() {
+          _this.refresh();
+          return _this.updateDropHint();
+        });
+      };
+    })(this);
+    this.stopOpenFolderTimer();
+    return this.open_folder_timer = setTimeout(openFolder, this.tree_widget.options.openFolderDelay);
+  };
+
+  DragAndDropHandler.prototype.stopOpenFolderTimer = function() {
+    if (this.open_folder_timer) {
+      clearTimeout(this.open_folder_timer);
+      return this.open_folder_timer = null;
+    }
+  };
+
+  DragAndDropHandler.prototype.moveItem = function(position_info) {
+    var doMove, event, moved_node, position, previous_parent, target_node;
+    if (this.hovered_area && this.hovered_area.position !== Position.NONE && this.canMoveToArea(this.hovered_area)) {
+      moved_node = this.current_item.node;
+      target_node = this.hovered_area.node;
+      position = this.hovered_area.position;
+      previous_parent = moved_node.parent;
+      if (position === Position.INSIDE) {
+        this.hovered_area.node.is_open = true;
+      }
+      doMove = (function(_this) {
+        return function() {
+          _this.tree_widget.tree.moveNode(moved_node, target_node, position);
+          _this.tree_widget.element.empty();
+          return _this.tree_widget._refreshElements();
+        };
+      })(this);
+      event = this.tree_widget._triggerEvent('tree.move', {
+        move_info: {
+          moved_node: moved_node,
+          target_node: target_node,
+          position: Position.getName(position),
+          previous_parent: previous_parent,
+          do_move: doMove,
+          original_event: position_info.original_event
+        }
+      });
+      if (!event.isDefaultPrevented()) {
+        return doMove();
+      }
+    }
+  };
+
+  DragAndDropHandler.prototype.getTreeDimensions = function() {
+    var offset;
+    offset = this.tree_widget.element.offset();
+    return {
+      left: offset.left,
+      top: offset.top,
+      right: offset.left + this.tree_widget.element.width(),
+      bottom: offset.top + this.tree_widget.element.height() + 16
+    };
+  };
+
+  return DragAndDropHandler;
+
+})();
+
+VisibleNodeIterator = (function() {
+  function VisibleNodeIterator(tree) {
+    this.tree = tree;
+  }
+
+  VisibleNodeIterator.prototype.iterate = function() {
+    var _iterateNode, is_first_node;
+    is_first_node = true;
+    _iterateNode = (function(_this) {
+      return function(node, next_node) {
+        var $element, child, children_length, i, j, len, must_iterate_inside, ref;
+        must_iterate_inside = (node.is_open || !node.element) && node.hasChildren();
+        if (node.element) {
+          $element = $(node.element);
+          if (!$element.is(':visible')) {
+            return;
+          }
+          if (is_first_node) {
+            _this.handleFirstNode(node, $element);
+            is_first_node = false;
+          }
+          if (!node.hasChildren()) {
+            _this.handleNode(node, next_node, $element);
+          } else if (node.is_open) {
+            if (!_this.handleOpenFolder(node, $element)) {
+              must_iterate_inside = false;
+            }
+          } else {
+            _this.handleClosedFolder(node, next_node, $element);
+          }
+        }
+        if (must_iterate_inside) {
+          children_length = node.children.length;
+          ref = node.children;
+          for (i = j = 0, len = ref.length; j < len; i = ++j) {
+            child = ref[i];
+            if (i === (children_length - 1)) {
+              _iterateNode(node.children[i], null);
+            } else {
+              _iterateNode(node.children[i], node.children[i + 1]);
+            }
+          }
+          if (node.is_open) {
+            return _this.handleAfterOpenFolder(node, next_node, $element);
+          }
+        }
+      };
+    })(this);
+    return _iterateNode(this.tree, null);
+  };
+
+  VisibleNodeIterator.prototype.handleNode = function(node, next_node, $element) {};
+
+  VisibleNodeIterator.prototype.handleOpenFolder = function(node, $element) {};
+
+  VisibleNodeIterator.prototype.handleClosedFolder = function(node, next_node, $element) {};
+
+  VisibleNodeIterator.prototype.handleAfterOpenFolder = function(node, next_node, $element) {};
+
+  VisibleNodeIterator.prototype.handleFirstNode = function(node, $element) {};
+
+  return VisibleNodeIterator;
+
+})();
+
+HitAreasGenerator = (function(superClass) {
+  extend(HitAreasGenerator, superClass);
+
+  function HitAreasGenerator(tree, current_node, tree_bottom) {
+    HitAreasGenerator.__super__.constructor.call(this, tree);
+    this.current_node = current_node;
+    this.tree_bottom = tree_bottom;
+  }
+
+  HitAreasGenerator.prototype.generate = function() {
+    this.positions = [];
+    this.last_top = 0;
+    this.iterate();
+    return this.generateHitAreas(this.positions);
+  };
+
+  HitAreasGenerator.prototype.getTop = function($element) {
+    return $element.offset().top;
+  };
+
+  HitAreasGenerator.prototype.addPosition = function(node, position, top) {
+    var area;
+    area = {
+      top: top,
+      node: node,
+      position: position
+    };
+    this.positions.push(area);
+    return this.last_top = top;
+  };
+
+  HitAreasGenerator.prototype.handleNode = function(node, next_node, $element) {
+    var top;
+    top = this.getTop($element);
+    if (node === this.current_node) {
+      this.addPosition(node, Position.NONE, top);
+    } else {
+      this.addPosition(node, Position.INSIDE, top);
+    }
+    if (next_node === this.current_node || node === this.current_node) {
+      return this.addPosition(node, Position.NONE, top);
+    } else {
+      return this.addPosition(node, Position.AFTER, top);
+    }
+  };
+
+  HitAreasGenerator.prototype.handleOpenFolder = function(node, $element) {
+    if (node === this.current_node) {
+      return false;
+    }
+    if (node.children[0] !== this.current_node) {
+      this.addPosition(node, Position.INSIDE, this.getTop($element));
+    }
+    return true;
+  };
+
+  HitAreasGenerator.prototype.handleClosedFolder = function(node, next_node, $element) {
+    var top;
+    top = this.getTop($element);
+    if (node === this.current_node) {
+      return this.addPosition(node, Position.NONE, top);
+    } else {
+      this.addPosition(node, Position.INSIDE, top);
+      if (next_node !== this.current_node) {
+        return this.addPosition(node, Position.AFTER, top);
+      }
+    }
+  };
+
+  HitAreasGenerator.prototype.handleFirstNode = function(node, $element) {
+    if (node !== this.current_node) {
+      return this.addPosition(node, Position.BEFORE, this.getTop($(node.element)));
+    }
+  };
+
+  HitAreasGenerator.prototype.handleAfterOpenFolder = function(node, next_node, $element) {
+    if (node === this.current_node.node || next_node === this.current_node.node) {
+      return this.addPosition(node, Position.NONE, this.last_top);
+    } else {
+      return this.addPosition(node, Position.AFTER, this.last_top);
+    }
+  };
+
+  HitAreasGenerator.prototype.generateHitAreas = function(positions) {
+    var group, hit_areas, j, len, position, previous_top;
+    previous_top = -1;
+    group = [];
+    hit_areas = [];
+    for (j = 0, len = positions.length; j < len; j++) {
+      position = positions[j];
+      if (position.top !== previous_top && group.length) {
+        if (group.length) {
+          this.generateHitAreasForGroup(hit_areas, group, previous_top, position.top);
+        }
+        previous_top = position.top;
+        group = [];
+      }
+      group.push(position);
+    }
+    this.generateHitAreasForGroup(hit_areas, group, previous_top, this.tree_bottom);
+    return hit_areas;
+  };
+
+  HitAreasGenerator.prototype.generateHitAreasForGroup = function(hit_areas, positions_in_group, top, bottom) {
+    var area_height, area_top, i, position, position_count;
+    position_count = Math.min(positions_in_group.length, 4);
+    area_height = Math.round((bottom - top) / position_count);
+    area_top = top;
+    i = 0;
+    while (i < position_count) {
+      position = positions_in_group[i];
+      hit_areas.push({
+        top: area_top,
+        bottom: area_top + area_height,
+        node: position.node,
+        position: position.position
+      });
+      area_top += area_height;
+      i += 1;
+    }
+    return null;
+  };
+
+  return HitAreasGenerator;
+
+})(VisibleNodeIterator);
+
+DragElement = (function() {
+  function DragElement(node, offset_x, offset_y, $tree) {
+    this.offset_x = offset_x;
+    this.offset_y = offset_y;
+    this.$element = $("<span class=\"jqtree-title jqtree-dragging\">" + node.name + "</span>");
+    this.$element.css("position", "absolute");
+    $tree.append(this.$element);
+  }
+
+  DragElement.prototype.move = function(page_x, page_y) {
+    return this.$element.offset({
+      left: page_x - this.offset_x,
+      top: page_y - this.offset_y
+    });
+  };
+
+  DragElement.prototype.remove = function() {
+    return this.$element.remove();
+  };
+
+  return DragElement;
+
+})();
+
+module.exports = {
+  DragAndDropHandler: DragAndDropHandler,
+  DragElement: DragElement,
+  HitAreasGenerator: HitAreasGenerator
+};
+
+},{"./node":5}],2:[function(require,module,exports){
+var $, ElementsRenderer, NodeElement, html_escape, node_element, util;
+
+node_element = require('./node_element');
+
+NodeElement = node_element.NodeElement;
+
+util = require('./util');
+
+html_escape = util.html_escape;
+
+$ = jQuery;
+
+ElementsRenderer = (function() {
+  function ElementsRenderer(tree_widget) {
+    this.tree_widget = tree_widget;
+    this.opened_icon_element = this.createButtonElement(tree_widget.options.openedIcon);
+    this.closed_icon_element = this.createButtonElement(tree_widget.options.closedIcon);
+  }
+
+  ElementsRenderer.prototype.render = function(from_node) {
+    if (from_node && from_node.parent) {
+      return this.renderFromNode(from_node);
+    } else {
+      return this.renderFromRoot();
+    }
+  };
+
+  ElementsRenderer.prototype.renderFromRoot = function() {
+    var $element;
+    $element = this.tree_widget.element;
+    $element.empty();
+    return this.createDomElements($element[0], this.tree_widget.tree.children, true, true, 1);
+  };
+
+  ElementsRenderer.prototype.renderFromNode = function(node) {
+    var $previous_li, li;
+    $previous_li = $(node.element);
+    li = this.createLi(node, node.getLevel());
+    this.attachNodeData(node, li);
+    $previous_li.after(li);
+    $previous_li.remove();
+    if (node.children) {
+      return this.createDomElements(li, node.children, false, false, node.getLevel() + 1);
+    }
+  };
+
+  ElementsRenderer.prototype.createDomElements = function(element, children, is_root_node, is_open, level) {
+    var child, i, len, li, ul;
+    ul = this.createUl(is_root_node);
+    element.appendChild(ul);
+    for (i = 0, len = children.length; i < len; i++) {
+      child = children[i];
+      li = this.createLi(child, level);
+      ul.appendChild(li);
+      this.attachNodeData(child, li);
+      if (child.hasChildren()) {
+        this.createDomElements(li, child.children, false, child.is_open, level + 1);
+      }
+    }
+    return null;
+  };
+
+  ElementsRenderer.prototype.attachNodeData = function(node, li) {
+    node.element = li;
+    return $(li).data('node', node);
+  };
+
+  ElementsRenderer.prototype.createUl = function(is_root_node) {
+    var class_string, role, ul;
+    if (!is_root_node) {
+      class_string = '';
+      role = 'group';
+    } else {
+      class_string = 'jqtree-tree';
+      role = 'tree';
+      if (this.tree_widget.options.rtl) {
+        class_string += ' jqtree-rtl';
+      }
+    }
+    ul = document.createElement('ul');
+    ul.className = "jqtree_common " + class_string;
+    ul.setAttribute('role', role);
+    return ul;
+  };
+
+  ElementsRenderer.prototype.createLi = function(node, level) {
+    var is_selected, li;
+    is_selected = this.tree_widget.select_node_handler && this.tree_widget.select_node_handler.isNodeSelected(node);
+    if (node.isFolder()) {
+      li = this.createFolderLi(node, level, is_selected);
+    } else {
+      li = this.createNodeLi(node, level, is_selected);
+    }
+    if (this.tree_widget.options.onCreateLi) {
+      this.tree_widget.options.onCreateLi(node, $(li));
+    }
+    return li;
+  };
+
+  ElementsRenderer.prototype.createFolderLi = function(node, level, is_selected) {
+    var button_classes, button_link, div, folder_classes, icon_element, is_folder, li;
+    button_classes = this.getButtonClasses(node);
+    folder_classes = this.getFolderClasses(node, is_selected);
+    if (node.is_open) {
+      icon_element = this.opened_icon_element;
+    } else {
+      icon_element = this.closed_icon_element;
+    }
+    li = document.createElement('li');
+    li.className = "jqtree_common " + folder_classes;
+    li.setAttribute('role', 'presentation');
+    div = document.createElement('div');
+    div.className = "jqtree-element jqtree_common";
+    div.setAttribute('role', 'presentation');
+    li.appendChild(div);
+    button_link = document.createElement('a');
+    button_link.className = button_classes;
+    button_link.appendChild(icon_element.cloneNode(false));
+    button_link.setAttribute('role', 'presentation');
+    button_link.setAttribute('aria-hidden', 'true');
+    if (this.tree_widget.options.buttonLeft) {
+      div.appendChild(button_link);
+    }
+    div.appendChild(this.createTitleSpan(node.name, level, is_selected, node.is_open, is_folder = true));
+    if (!this.tree_widget.options.buttonLeft) {
+      div.appendChild(button_link);
+    }
+    return li;
+  };
+
+  ElementsRenderer.prototype.createNodeLi = function(node, level, is_selected) {
+    var class_string, div, is_folder, li, li_classes;
+    li_classes = ['jqtree_common'];
+    if (is_selected) {
+      li_classes.push('jqtree-selected');
+    }
+    class_string = li_classes.join(' ');
+    li = document.createElement('li');
+    li.className = class_string;
+    li.setAttribute('role', 'presentation');
+    div = document.createElement('div');
+    div.className = "jqtree-element jqtree_common";
+    div.setAttribute('role', 'presentation');
+    li.appendChild(div);
+    div.appendChild(this.createTitleSpan(node.name, level, is_selected, node.is_open, is_folder = false));
+    return li;
+  };
+
+  ElementsRenderer.prototype.createTitleSpan = function(node_name, level, is_selected, is_open, is_folder) {
+    var classes, title_span;
+    title_span = document.createElement('span');
+    classes = "jqtree-title jqtree_common";
+    if (is_folder) {
+      classes += " jqtree-title-folder";
+    }
+    title_span.className = classes;
+    title_span.setAttribute('role', 'treeitem');
+    title_span.setAttribute('aria-level', level);
+    title_span.setAttribute('aria-selected', util.getBoolString(is_selected));
+    title_span.setAttribute('aria-expanded', util.getBoolString(is_open));
+    if (is_selected) {
+      title_span.setAttribute('tabindex', 0);
+    }
+    title_span.innerHTML = this.escapeIfNecessary(node_name);
+    return title_span;
+  };
+
+  ElementsRenderer.prototype.getButtonClasses = function(node) {
+    var classes;
+    classes = ['jqtree-toggler', 'jqtree_common'];
+    if (!node.is_open) {
+      classes.push('jqtree-closed');
+    }
+    if (this.tree_widget.options.buttonLeft) {
+      classes.push('jqtree-toggler-left');
+    } else {
+      classes.push('jqtree-toggler-right');
+    }
+    return classes.join(' ');
+  };
+
+  ElementsRenderer.prototype.getFolderClasses = function(node, is_selected) {
+    var classes;
+    classes = ['jqtree-folder'];
+    if (!node.is_open) {
+      classes.push('jqtree-closed');
+    }
+    if (is_selected) {
+      classes.push('jqtree-selected');
+    }
+    if (node.is_loading) {
+      classes.push('jqtree-loading');
+    }
+    return classes.join(' ');
+  };
+
+  ElementsRenderer.prototype.escapeIfNecessary = function(value) {
+    if (this.tree_widget.options.autoEscape) {
+      return html_escape(value);
+    } else {
+      return value;
+    }
+  };
+
+  ElementsRenderer.prototype.createButtonElement = function(value) {
+    var div;
+    if (typeof value === 'string') {
+      div = document.createElement('div');
+      div.innerHTML = value;
+      return document.createTextNode(div.innerHTML);
+    } else {
+      return $(value)[0];
+    }
+  };
+
+  return ElementsRenderer;
+
+})();
+
+module.exports = ElementsRenderer;
+
+},{"./node_element":6,"./util":12}],3:[function(require,module,exports){
+var $, KeyHandler,
+  bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+$ = jQuery;
+
+KeyHandler = (function() {
+  var DOWN, LEFT, RIGHT, UP;
+
+  LEFT = 37;
+
+  UP = 38;
+
+  RIGHT = 39;
+
+  DOWN = 40;
+
+  function KeyHandler(tree_widget) {
+    this.selectNode = bind(this.selectNode, this);
+    this.tree_widget = tree_widget;
+    if (tree_widget.options.keyboardSupport) {
+      $(document).bind('keydown.jqtree', $.proxy(this.handleKeyDown, this));
+    }
+  }
+
+  KeyHandler.prototype.deinit = function() {
+    return $(document).unbind('keydown.jqtree');
+  };
+
+  KeyHandler.prototype.moveDown = function() {
+    var node;
+    node = this.tree_widget.getSelectedNode();
+    if (node) {
+      return this.selectNode(node.getNextNode());
+    } else {
+      return false;
+    }
+  };
+
+  KeyHandler.prototype.moveUp = function() {
+    var node;
+    node = this.tree_widget.getSelectedNode();
+    if (node) {
+      return this.selectNode(node.getPreviousNode());
+    } else {
+      return false;
+    }
+  };
+
+  KeyHandler.prototype.moveRight = function() {
+    var node;
+    node = this.tree_widget.getSelectedNode();
+    if (!node) {
+      return true;
+    } else if (!node.isFolder()) {
+      return true;
+    } else {
+      if (node.is_open) {
+        return this.selectNode(node.getNextNode());
+      } else {
+        this.tree_widget.openNode(node);
+        return false;
+      }
+    }
+  };
+
+  KeyHandler.prototype.moveLeft = function() {
+    var node;
+    node = this.tree_widget.getSelectedNode();
+    if (!node) {
+      return true;
+    } else if (node.isFolder() && node.is_open) {
+      this.tree_widget.closeNode(node);
+      return false;
+    } else {
+      return this.selectNode(node.getParent());
+    }
+  };
+
+  KeyHandler.prototype.handleKeyDown = function(e) {
+    var key;
+    if (!this.tree_widget.options.keyboardSupport) {
+      return true;
+    }
+    if ($(document.activeElement).is('textarea,input,select')) {
+      return true;
+    }
+    if (!this.tree_widget.getSelectedNode()) {
+      return true;
+    }
+    key = e.which;
+    switch (key) {
+      case DOWN:
+        return this.moveDown();
+      case UP:
+        return this.moveUp();
+      case RIGHT:
+        return this.moveRight();
+      case LEFT:
+        return this.moveLeft();
+    }
+    return true;
+  };
+
+  KeyHandler.prototype.selectNode = function(node) {
+    if (!node) {
+      return true;
+    } else {
+      this.tree_widget.selectNode(node);
+      if (this.tree_widget.scroll_handler && (!this.tree_widget.scroll_handler.isScrolledIntoView($(node.element).find('.jqtree-element')))) {
+        this.tree_widget.scrollToNode(node);
+      }
+      return false;
+    }
+  };
+
+  return KeyHandler;
+
+})();
+
+module.exports = KeyHandler;
+
+},{}],4:[function(require,module,exports){
+
+/*
+This widget does the same a the mouse widget in jqueryui.
+ */
+var $, MouseWidget, SimpleWidget,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+SimpleWidget = require('./simple.widget');
+
+$ = jQuery;
+
+MouseWidget = (function(superClass) {
+  extend(MouseWidget, superClass);
+
+  function MouseWidget() {
+    return MouseWidget.__super__.constructor.apply(this, arguments);
+  }
+
+  MouseWidget.is_mouse_handled = false;
+
+  MouseWidget.prototype._init = function() {
+    this.$el.bind('mousedown.mousewidget', $.proxy(this._mouseDown, this));
+    this.$el.bind('touchstart.mousewidget', $.proxy(this._touchStart, this));
+    this.is_mouse_started = false;
+    this.mouse_delay = 0;
+    this._mouse_delay_timer = null;
+    this._is_mouse_delay_met = true;
+    return this.mouse_down_info = null;
+  };
+
+  MouseWidget.prototype._deinit = function() {
+    var $document;
+    this.$el.unbind('mousedown.mousewidget');
+    this.$el.unbind('touchstart.mousewidget');
+    $document = $(document);
+    $document.unbind('mousemove.mousewidget');
+    return $document.unbind('mouseup.mousewidget');
+  };
+
+  MouseWidget.prototype._mouseDown = function(e) {
+    var result;
+    if (e.which !== 1) {
+      return;
+    }
+    result = this._handleMouseDown(e, this._getPositionInfo(e));
+    if (result) {
+      e.preventDefault();
+    }
+    return result;
+  };
+
+  MouseWidget.prototype._handleMouseDown = function(e, position_info) {
+    if (MouseWidget.is_mouse_handled) {
+      return;
+    }
+    if (this.is_mouse_started) {
+      this._handleMouseUp(position_info);
+    }
+    this.mouse_down_info = position_info;
+    if (!this._mouseCapture(position_info)) {
+      return;
+    }
+    this._handleStartMouse();
+    this.is_mouse_handled = true;
+    return true;
+  };
+
+  MouseWidget.prototype._handleStartMouse = function() {
+    var $document;
+    $document = $(document);
+    $document.bind('mousemove.mousewidget', $.proxy(this._mouseMove, this));
+    $document.bind('touchmove.mousewidget', $.proxy(this._touchMove, this));
+    $document.bind('mouseup.mousewidget', $.proxy(this._mouseUp, this));
+    $document.bind('touchend.mousewidget', $.proxy(this._touchEnd, this));
+    if (this.mouse_delay) {
+      return this._startMouseDelayTimer();
+    }
+  };
+
+  MouseWidget.prototype._startMouseDelayTimer = function() {
+    if (this._mouse_delay_timer) {
+      clearTimeout(this._mouse_delay_timer);
+    }
+    this._mouse_delay_timer = setTimeout((function(_this) {
+      return function() {
+        return _this._is_mouse_delay_met = true;
+      };
+    })(this), this.mouse_delay);
+    return this._is_mouse_delay_met = false;
+  };
+
+  MouseWidget.prototype._mouseMove = function(e) {
+    return this._handleMouseMove(e, this._getPositionInfo(e));
+  };
+
+  MouseWidget.prototype._handleMouseMove = function(e, position_info) {
+    if (this.is_mouse_started) {
+      this._mouseDrag(position_info);
+      return e.preventDefault();
+    }
+    if (this.mouse_delay && !this._is_mouse_delay_met) {
+      return true;
+    }
+    this.is_mouse_started = this._mouseStart(this.mouse_down_info) !== false;
+    if (this.is_mouse_started) {
+      this._mouseDrag(position_info);
+    } else {
+      this._handleMouseUp(position_info);
+    }
+    return !this.is_mouse_started;
+  };
+
+  MouseWidget.prototype._getPositionInfo = function(e) {
+    return {
+      page_x: e.pageX,
+      page_y: e.pageY,
+      target: e.target,
+      original_event: e
+    };
+  };
+
+  MouseWidget.prototype._mouseUp = function(e) {
+    return this._handleMouseUp(this._getPositionInfo(e));
+  };
+
+  MouseWidget.prototype._handleMouseUp = function(position_info) {
+    var $document;
+    $document = $(document);
+    $document.unbind('mousemove.mousewidget');
+    $document.unbind('touchmove.mousewidget');
+    $document.unbind('mouseup.mousewidget');
+    $document.unbind('touchend.mousewidget');
+    if (this.is_mouse_started) {
+      this.is_mouse_started = false;
+      this._mouseStop(position_info);
+    }
+  };
+
+  MouseWidget.prototype._mouseCapture = function(position_info) {
+    return true;
+  };
+
+  MouseWidget.prototype._mouseStart = function(position_info) {
+    return null;
+  };
+
+  MouseWidget.prototype._mouseDrag = function(position_info) {
+    return null;
+  };
+
+  MouseWidget.prototype._mouseStop = function(position_info) {
+    return null;
+  };
+
+  MouseWidget.prototype.setMouseDelay = function(mouse_delay) {
+    return this.mouse_delay = mouse_delay;
+  };
+
+  MouseWidget.prototype._touchStart = function(e) {
+    var touch;
+    if (e.originalEvent.touches.length > 1) {
+      return;
+    }
+    touch = e.originalEvent.changedTouches[0];
+    return this._handleMouseDown(e, this._getPositionInfo(touch));
+  };
+
+  MouseWidget.prototype._touchMove = function(e) {
+    var touch;
+    if (e.originalEvent.touches.length > 1) {
+      return;
+    }
+    touch = e.originalEvent.changedTouches[0];
+    return this._handleMouseMove(e, this._getPositionInfo(touch));
+  };
+
+  MouseWidget.prototype._touchEnd = function(e) {
+    var touch;
+    if (e.originalEvent.touches.length > 1) {
+      return;
+    }
+    touch = e.originalEvent.changedTouches[0];
+    return this._handleMouseUp(this._getPositionInfo(touch));
+  };
+
+  return MouseWidget;
+
+})(SimpleWidget);
+
+module.exports = MouseWidget;
+
+},{"./simple.widget":10}],5:[function(require,module,exports){
+var $, Node, Position;
+
+$ = jQuery;
+
+Position = {
+  getName: function(position) {
+    return Position.strings[position - 1];
+  },
+  nameToIndex: function(name) {
+    var i, j, ref;
+    for (i = j = 1, ref = Position.strings.length; 1 <= ref ? j <= ref : j >= ref; i = 1 <= ref ? ++j : --j) {
+      if (Position.strings[i - 1] === name) {
+        return i;
+      }
+    }
+    return 0;
+  }
+};
+
+Position.BEFORE = 1;
+
+Position.AFTER = 2;
+
+Position.INSIDE = 3;
+
+Position.NONE = 4;
+
+Position.strings = ['before', 'after', 'inside', 'none'];
+
+Node = (function() {
+  function Node(o, is_root, node_class) {
+    if (is_root == null) {
+      is_root = false;
+    }
+    if (node_class == null) {
+      node_class = Node;
+    }
+    this.name = '';
+    this.setData(o);
+    this.children = [];
+    this.parent = null;
+    if (is_root) {
+      this.id_mapping = {};
+      this.tree = this;
+      this.node_class = node_class;
+    }
+  }
+
+  Node.prototype.setData = function(o) {
+
+    /*
+    Set the data of this node.
+    
+    setData(string): set the name of the node
+    setdata(object): set attributes of the node
+    
+    Examples:
+        setdata('node1')
+    
+        setData({ name: 'node1', id: 1});
+    
+        setData({ name: 'node2', id: 2, color: 'green'});
+    
+    * This is an internal function; it is not in the docs
+    * Does not remove existing node values
+     */
+    var key, setName, value;
+    setName = (function(_this) {
+      return function(name) {
+        if (name !== null) {
+          return _this.name = name;
+        }
+      };
+    })(this);
+    if (typeof o !== 'object') {
+      setName(o);
+    } else {
+      for (key in o) {
+        value = o[key];
+        if (key === 'label') {
+          setName(value);
+        } else if (key !== 'children') {
+          this[key] = value;
+        }
+      }
+    }
+    return null;
+  };
+
+  Node.prototype.initFromData = function(data) {
+    var addChildren, addNode;
+    addNode = (function(_this) {
+      return function(node_data) {
+        _this.setData(node_data);
+        if (node_data.children) {
+          return addChildren(node_data.children);
+        }
+      };
+    })(this);
+    addChildren = (function(_this) {
+      return function(children_data) {
+        var child, j, len, node;
+        for (j = 0, len = children_data.length; j < len; j++) {
+          child = children_data[j];
+          node = new _this.tree.node_class('');
+          node.initFromData(child);
+          _this.addChild(node);
+        }
+        return null;
+      };
+    })(this);
+    addNode(data);
+    return null;
+  };
+
+
+  /*
+  Create tree from data.
+  
+  Structure of data is:
+  [
+      {
+          label: 'node1',
+          children: [
+              { label: 'child1' },
+              { label: 'child2' }
+          ]
+      },
+      {
+          label: 'node2'
+      }
+  ]
+   */
+
+  Node.prototype.loadFromData = function(data) {
+    var j, len, node, o;
+    this.removeChildren();
+    for (j = 0, len = data.length; j < len; j++) {
+      o = data[j];
+      node = new this.tree.node_class(o);
+      this.addChild(node);
+      if (typeof o === 'object' && o.children) {
+        node.loadFromData(o.children);
+      }
+    }
+    return null;
+  };
+
+
+  /*
+  Add child.
+  
+  tree.addChild(
+      new Node('child1')
+  );
+   */
+
+  Node.prototype.addChild = function(node) {
+    this.children.push(node);
+    return node._setParent(this);
+  };
+
+
+  /*
+  Add child at position. Index starts at 0.
+  
+  tree.addChildAtPosition(
+      new Node('abc'),
+      1
+  );
+   */
+
+  Node.prototype.addChildAtPosition = function(node, index) {
+    this.children.splice(index, 0, node);
+    return node._setParent(this);
+  };
+
+  Node.prototype._setParent = function(parent) {
+    this.parent = parent;
+    this.tree = parent.tree;
+    return this.tree.addNodeToIndex(this);
+  };
+
+
+  /*
+  Remove child. This also removes the children of the node.
+  
+  tree.removeChild(tree.children[0]);
+   */
+
+  Node.prototype.removeChild = function(node) {
+    node.removeChildren();
+    return this._removeChild(node);
+  };
+
+  Node.prototype._removeChild = function(node) {
+    this.children.splice(this.getChildIndex(node), 1);
+    return this.tree.removeNodeFromIndex(node);
+  };
+
+
+  /*
+  Get child index.
+  
+  var index = getChildIndex(node);
+   */
+
+  Node.prototype.getChildIndex = function(node) {
+    return $.inArray(node, this.children);
+  };
+
+
+  /*
+  Does the tree have children?
+  
+  if (tree.hasChildren()) {
+      //
+  }
+   */
+
+  Node.prototype.hasChildren = function() {
+    return this.children.length !== 0;
+  };
+
+  Node.prototype.isFolder = function() {
+    return this.hasChildren() || this.load_on_demand;
+  };
+
+
+  /*
+  Iterate over all the nodes in the tree.
+  
+  Calls callback with (node, level).
+  
+  The callback must return true to continue the iteration on current node.
+  
+  tree.iterate(
+      function(node, level) {
+         console.log(node.name);
+  
+         // stop iteration after level 2
+         return (level <= 2);
+      }
+  );
+   */
+
+  Node.prototype.iterate = function(callback) {
+    var _iterate;
+    _iterate = function(node, level) {
+      var child, j, len, ref, result;
+      if (node.children) {
+        ref = node.children;
+        for (j = 0, len = ref.length; j < len; j++) {
+          child = ref[j];
+          result = callback(child, level);
+          if (result && child.hasChildren()) {
+            _iterate(child, level + 1);
+          }
+        }
+        return null;
+      }
+    };
+    _iterate(this, 0);
+    return null;
+  };
+
+
+  /*
+  Move node relative to another node.
+  
+  Argument position: Position.BEFORE, Position.AFTER or Position.Inside
+  
+  // move node1 after node2
+  tree.moveNode(node1, node2, Position.AFTER);
+   */
+
+  Node.prototype.moveNode = function(moved_node, target_node, position) {
+    if (moved_node.isParentOf(target_node)) {
+      return;
+    }
+    moved_node.parent._removeChild(moved_node);
+    if (position === Position.AFTER) {
+      return target_node.parent.addChildAtPosition(moved_node, target_node.parent.getChildIndex(target_node) + 1);
+    } else if (position === Position.BEFORE) {
+      return target_node.parent.addChildAtPosition(moved_node, target_node.parent.getChildIndex(target_node));
+    } else if (position === Position.INSIDE) {
+      return target_node.addChildAtPosition(moved_node, 0);
+    }
+  };
+
+
+  /*
+  Get the tree as data.
+   */
+
+  Node.prototype.getData = function(include_parent) {
+    var getDataFromNodes;
+    if (include_parent == null) {
+      include_parent = false;
+    }
+    getDataFromNodes = function(nodes) {
+      var data, j, k, len, node, tmp_node, v;
+      data = [];
+      for (j = 0, len = nodes.length; j < len; j++) {
+        node = nodes[j];
+        tmp_node = {};
+        for (k in node) {
+          v = node[k];
+          if ((k !== 'parent' && k !== 'children' && k !== 'element' && k !== 'tree') && Object.prototype.hasOwnProperty.call(node, k)) {
+            tmp_node[k] = v;
+          }
+        }
+        if (node.hasChildren()) {
+          tmp_node.children = getDataFromNodes(node.children);
+        }
+        data.push(tmp_node);
+      }
+      return data;
+    };
+    if (include_parent) {
+      return getDataFromNodes([this]);
+    } else {
+      return getDataFromNodes(this.children);
+    }
+  };
+
+  Node.prototype.getNodeByName = function(name) {
+    var result;
+    result = null;
+    this.iterate(function(node) {
+      if (node.name === name) {
+        result = node;
+        return false;
+      } else {
+        return true;
+      }
+    });
+    return result;
+  };
+
+  Node.prototype.addAfter = function(node_info) {
+    var child_index, node;
+    if (!this.parent) {
+      return null;
+    } else {
+      node = new this.tree.node_class(node_info);
+      child_index = this.parent.getChildIndex(this);
+      this.parent.addChildAtPosition(node, child_index + 1);
+      if (typeof node_info === 'object' && node_info.children && node_info.children.length) {
+        node.loadFromData(node_info.children);
+      }
+      return node;
+    }
+  };
+
+  Node.prototype.addBefore = function(node_info) {
+    var child_index, node;
+    if (!this.parent) {
+      return null;
+    } else {
+      node = new this.tree.node_class(node_info);
+      child_index = this.parent.getChildIndex(this);
+      this.parent.addChildAtPosition(node, child_index);
+      if (typeof node_info === 'object' && node_info.children && node_info.children.length) {
+        node.loadFromData(node_info.children);
+      }
+      return node;
+    }
+  };
+
+  Node.prototype.addParent = function(node_info) {
+    var child, j, len, new_parent, original_parent, ref;
+    if (!this.parent) {
+      return null;
+    } else {
+      new_parent = new this.tree.node_class(node_info);
+      new_parent._setParent(this.tree);
+      original_parent = this.parent;
+      ref = original_parent.children;
+      for (j = 0, len = ref.length; j < len; j++) {
+        child = ref[j];
+        new_parent.addChild(child);
+      }
+      original_parent.children = [];
+      original_parent.addChild(new_parent);
+      return new_parent;
+    }
+  };
+
+  Node.prototype.remove = function() {
+    if (this.parent) {
+      this.parent.removeChild(this);
+      return this.parent = null;
+    }
+  };
+
+  Node.prototype.append = function(node_info) {
+    var node;
+    node = new this.tree.node_class(node_info);
+    this.addChild(node);
+    if (typeof node_info === 'object' && node_info.children && node_info.children.length) {
+      node.loadFromData(node_info.children);
+    }
+    return node;
+  };
+
+  Node.prototype.prepend = function(node_info) {
+    var node;
+    node = new this.tree.node_class(node_info);
+    this.addChildAtPosition(node, 0);
+    if (typeof node_info === 'object' && node_info.children && node_info.children.length) {
+      node.loadFromData(node_info.children);
+    }
+    return node;
+  };
+
+  Node.prototype.isParentOf = function(node) {
+    var parent;
+    parent = node.parent;
+    while (parent) {
+      if (parent === this) {
+        return true;
+      }
+      parent = parent.parent;
+    }
+    return false;
+  };
+
+  Node.prototype.getLevel = function() {
+    var level, node;
+    level = 0;
+    node = this;
+    while (node.parent) {
+      level += 1;
+      node = node.parent;
+    }
+    return level;
+  };
+
+  Node.prototype.getNodeById = function(node_id) {
+    return this.id_mapping[node_id];
+  };
+
+  Node.prototype.addNodeToIndex = function(node) {
+    if (node.id != null) {
+      return this.id_mapping[node.id] = node;
+    }
+  };
+
+  Node.prototype.removeNodeFromIndex = function(node) {
+    if (node.id != null) {
+      return delete this.id_mapping[node.id];
+    }
+  };
+
+  Node.prototype.removeChildren = function() {
+    this.iterate((function(_this) {
+      return function(child) {
+        _this.tree.removeNodeFromIndex(child);
+        return true;
+      };
+    })(this));
+    return this.children = [];
+  };
+
+  Node.prototype.getPreviousSibling = function() {
+    var previous_index;
+    if (!this.parent) {
+      return null;
+    } else {
+      previous_index = this.parent.getChildIndex(this) - 1;
+      if (previous_index >= 0) {
+        return this.parent.children[previous_index];
+      } else {
+        return null;
+      }
+    }
+  };
+
+  Node.prototype.getNextSibling = function() {
+    var next_index;
+    if (!this.parent) {
+      return null;
+    } else {
+      next_index = this.parent.getChildIndex(this) + 1;
+      if (next_index < this.parent.children.length) {
+        return this.parent.children[next_index];
+      } else {
+        return null;
+      }
+    }
+  };
+
+  Node.prototype.getNodesByProperty = function(key, value) {
+    return this.filter(function(node) {
+      return node[key] === value;
+    });
+  };
+
+  Node.prototype.filter = function(f) {
+    var result;
+    result = [];
+    this.iterate(function(node) {
+      if (f(node)) {
+        result.push(node);
+      }
+      return true;
+    });
+    return result;
+  };
+
+  Node.prototype.getNextNode = function(include_children) {
+    var next_sibling;
+    if (include_children == null) {
+      include_children = true;
+    }
+    if (include_children && this.hasChildren() && this.is_open) {
+      return this.children[0];
+    } else {
+      if (!this.parent) {
+        return null;
+      } else {
+        next_sibling = this.getNextSibling();
+        if (next_sibling) {
+          return next_sibling;
+        } else {
+          return this.parent.getNextNode(false);
+        }
+      }
+    }
+  };
+
+  Node.prototype.getPreviousNode = function() {
+    var previous_sibling;
+    if (!this.parent) {
+      return null;
+    } else {
+      previous_sibling = this.getPreviousSibling();
+      if (previous_sibling) {
+        if (!previous_sibling.hasChildren() || !previous_sibling.is_open) {
+          return previous_sibling;
+        } else {
+          return previous_sibling.getLastChild();
+        }
+      } else {
+        return this.getParent();
+      }
+    }
+  };
+
+  Node.prototype.getParent = function() {
+    if (!this.parent) {
+      return null;
+    } else if (!this.parent.parent) {
+      return null;
+    } else {
+      return this.parent;
+    }
+  };
+
+  Node.prototype.getLastChild = function() {
+    var last_child;
+    if (!this.hasChildren()) {
+      return null;
+    } else {
+      last_child = this.children[this.children.length - 1];
+      if (!last_child.hasChildren() || !last_child.is_open) {
+        return last_child;
+      } else {
+        return last_child.getLastChild();
+      }
+    }
+  };
+
+  return Node;
+
+})();
+
+module.exports = {
+  Node: Node,
+  Position: Position
+};
+
+},{}],6:[function(require,module,exports){
+var $, BorderDropHint, FolderElement, GhostDropHint, NodeElement, Position, node,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+node = require('./node');
+
+Position = node.Position;
+
+$ = jQuery;
+
+NodeElement = (function() {
+  function NodeElement(node, tree_widget) {
+    this.init(node, tree_widget);
+  }
+
+  NodeElement.prototype.init = function(node, tree_widget) {
+    this.node = node;
+    this.tree_widget = tree_widget;
+    if (!node.element) {
+      node.element = this.tree_widget.element;
+    }
+    return this.$element = $(node.element);
+  };
+
+  NodeElement.prototype.getUl = function() {
+    return this.$element.children('ul:first');
+  };
+
+  NodeElement.prototype.getSpan = function() {
+    return this.$element.children('.jqtree-element').find('span.jqtree-title');
+  };
+
+  NodeElement.prototype.getLi = function() {
+    return this.$element;
+  };
+
+  NodeElement.prototype.addDropHint = function(position) {
+    if (position === Position.INSIDE) {
+      return new BorderDropHint(this.$element);
+    } else {
+      return new GhostDropHint(this.node, this.$element, position);
+    }
+  };
+
+  NodeElement.prototype.select = function() {
+    var $li, $span;
+    $li = this.getLi();
+    $li.addClass('jqtree-selected');
+    $li.attr('aria-selected', 'true');
+    $span = this.getSpan();
+    return $span.attr('tabindex', 0);
+  };
+
+  NodeElement.prototype.deselect = function() {
+    var $li, $span;
+    $li = this.getLi();
+    $li.removeClass('jqtree-selected');
+    $li.attr('aria-selected', 'false');
+    $span = this.getSpan();
+    return $span.attr('tabindex', -1);
+  };
+
+  return NodeElement;
+
+})();
+
+FolderElement = (function(superClass) {
+  extend(FolderElement, superClass);
+
+  function FolderElement() {
+    return FolderElement.__super__.constructor.apply(this, arguments);
+  }
+
+  FolderElement.prototype.open = function(on_finished, slide) {
+    var $button, doOpen;
+    if (slide == null) {
+      slide = true;
+    }
+    if (!this.node.is_open) {
+      this.node.is_open = true;
+      $button = this.getButton();
+      $button.removeClass('jqtree-closed');
+      $button.html('');
+      $button.append(this.tree_widget.renderer.opened_icon_element.cloneNode(false));
+      doOpen = (function(_this) {
+        return function() {
+          var $li, $span;
+          $li = _this.getLi();
+          $li.removeClass('jqtree-closed');
+          $span = _this.getSpan();
+          $span.attr('aria-expanded', 'true');
+          if (on_finished) {
+            on_finished();
+          }
+          return _this.tree_widget._triggerEvent('tree.open', {
+            node: _this.node
+          });
+        };
+      })(this);
+      if (slide) {
+        return this.getUl().slideDown('fast', doOpen);
+      } else {
+        this.getUl().show();
+        return doOpen();
+      }
+    }
+  };
+
+  FolderElement.prototype.close = function(slide) {
+    var $button, doClose;
+    if (slide == null) {
+      slide = true;
+    }
+    if (this.node.is_open) {
+      this.node.is_open = false;
+      $button = this.getButton();
+      $button.addClass('jqtree-closed');
+      $button.html('');
+      $button.append(this.tree_widget.renderer.closed_icon_element.cloneNode(false));
+      doClose = (function(_this) {
+        return function() {
+          var $li, $span;
+          $li = _this.getLi();
+          $li.addClass('jqtree-closed');
+          $span = _this.getSpan();
+          $span.attr('aria-expanded', 'false');
+          return _this.tree_widget._triggerEvent('tree.close', {
+            node: _this.node
+          });
+        };
+      })(this);
+      if (slide) {
+        return this.getUl().slideUp('fast', doClose);
+      } else {
+        this.getUl().hide();
+        return doClose();
+      }
+    }
+  };
+
+  FolderElement.prototype.getButton = function() {
+    return this.$element.children('.jqtree-element').find('a.jqtree-toggler');
+  };
+
+  FolderElement.prototype.addDropHint = function(position) {
+    if (!this.node.is_open && position === Position.INSIDE) {
+      return new BorderDropHint(this.$element);
+    } else {
+      return new GhostDropHint(this.node, this.$element, position);
+    }
+  };
+
+  return FolderElement;
+
+})(NodeElement);
+
+BorderDropHint = (function() {
+  function BorderDropHint($element) {
+    var $div, width;
+    $div = $element.children('.jqtree-element');
+    width = $element.width() - 4;
+    this.$hint = $('<span class="jqtree-border"></span>');
+    $div.append(this.$hint);
+    this.$hint.css({
+      width: width,
+      height: $div.outerHeight() - 4
+    });
+  }
+
+  BorderDropHint.prototype.remove = function() {
+    return this.$hint.remove();
+  };
+
+  return BorderDropHint;
+
+})();
+
+GhostDropHint = (function() {
+  function GhostDropHint(node, $element, position) {
+    this.$element = $element;
+    this.node = node;
+    this.$ghost = $('<li class="jqtree_common jqtree-ghost"><span class="jqtree_common jqtree-circle"></span><span class="jqtree_common jqtree-line"></span></li>');
+    if (position === Position.AFTER) {
+      this.moveAfter();
+    } else if (position === Position.BEFORE) {
+      this.moveBefore();
+    } else if (position === Position.INSIDE) {
+      if (node.isFolder() && node.is_open) {
+        this.moveInsideOpenFolder();
+      } else {
+        this.moveInside();
+      }
+    }
+  }
+
+  GhostDropHint.prototype.remove = function() {
+    return this.$ghost.remove();
+  };
+
+  GhostDropHint.prototype.moveAfter = function() {
+    return this.$element.after(this.$ghost);
+  };
+
+  GhostDropHint.prototype.moveBefore = function() {
+    return this.$element.before(this.$ghost);
+  };
+
+  GhostDropHint.prototype.moveInsideOpenFolder = function() {
+    return $(this.node.children[0].element).before(this.$ghost);
+  };
+
+  GhostDropHint.prototype.moveInside = function() {
+    this.$element.after(this.$ghost);
+    return this.$ghost.addClass('jqtree-inside');
+  };
+
+  return GhostDropHint;
+
+})();
+
+module.exports = {
+  BorderDropHint: BorderDropHint,
+  FolderElement: FolderElement,
+  GhostDropHint: GhostDropHint,
+  NodeElement: NodeElement
+};
+
+},{"./node":5}],7:[function(require,module,exports){
+var $, SaveStateHandler, indexOf, isInt, util;
+
+util = require('./util');
+
+indexOf = util.indexOf;
+
+isInt = util.isInt;
+
+$ = jQuery;
+
+SaveStateHandler = (function() {
+  function SaveStateHandler(tree_widget) {
+    this.tree_widget = tree_widget;
+  }
+
+  SaveStateHandler.prototype.saveState = function() {
+    var state;
+    state = JSON.stringify(this.getState());
+    if (this.tree_widget.options.onSetStateFromStorage) {
+      return this.tree_widget.options.onSetStateFromStorage(state);
+    } else if (this.supportsLocalStorage()) {
+      return localStorage.setItem(this.getCookieName(), state);
+    } else if ($.cookie) {
+      $.cookie.raw = true;
+      return $.cookie(this.getCookieName(), state, {
+        path: '/'
+      });
+    }
+  };
+
+  SaveStateHandler.prototype.getStateFromStorage = function() {
+    var json_data;
+    json_data = this._loadFromStorage();
+    if (json_data) {
+      return this._parseState(json_data);
+    } else {
+      return null;
+    }
+  };
+
+  SaveStateHandler.prototype._parseState = function(json_data) {
+    var state;
+    state = $.parseJSON(json_data);
+    if (state && state.selected_node && isInt(state.selected_node)) {
+      state.selected_node = [state.selected_node];
+    }
+    return state;
+  };
+
+  SaveStateHandler.prototype._loadFromStorage = function() {
+    if (this.tree_widget.options.onGetStateFromStorage) {
+      return this.tree_widget.options.onGetStateFromStorage();
+    } else if (this.supportsLocalStorage()) {
+      return localStorage.getItem(this.getCookieName());
+    } else if ($.cookie) {
+      $.cookie.raw = true;
+      return $.cookie(this.getCookieName());
+    } else {
+      return null;
+    }
+  };
+
+  SaveStateHandler.prototype.getState = function() {
+    var getOpenNodeIds, getSelectedNodeIds;
+    getOpenNodeIds = (function(_this) {
+      return function() {
+        var open_nodes;
+        open_nodes = [];
+        _this.tree_widget.tree.iterate(function(node) {
+          if (node.is_open && node.id && node.hasChildren()) {
+            open_nodes.push(node.id);
+          }
+          return true;
+        });
+        return open_nodes;
+      };
+    })(this);
+    getSelectedNodeIds = (function(_this) {
+      return function() {
+        var n;
+        return (function() {
+          var i, len, ref, results;
+          ref = this.tree_widget.getSelectedNodes();
+          results = [];
+          for (i = 0, len = ref.length; i < len; i++) {
+            n = ref[i];
+            results.push(n.id);
+          }
+          return results;
+        }).call(_this);
+      };
+    })(this);
+    return {
+      open_nodes: getOpenNodeIds(),
+      selected_node: getSelectedNodeIds()
+    };
+  };
+
+  SaveStateHandler.prototype.setInitialState = function(state) {
+    var must_load_on_demand;
+    if (!state) {
+      return false;
+    } else {
+      must_load_on_demand = this._openInitialNodes(state.open_nodes);
+      this._selectInitialNodes(state.selected_node);
+      return must_load_on_demand;
+    }
+  };
+
+  SaveStateHandler.prototype._openInitialNodes = function(node_ids) {
+    var i, len, must_load_on_demand, node, node_id;
+    must_load_on_demand = false;
+    for (i = 0, len = node_ids.length; i < len; i++) {
+      node_id = node_ids[i];
+      node = this.tree_widget.getNodeById(node_id);
+      if (node) {
+        if (!node.load_on_demand) {
+          node.is_open = true;
+        } else {
+          must_load_on_demand = true;
+        }
+      }
+    }
+    return must_load_on_demand;
+  };
+
+  SaveStateHandler.prototype._selectInitialNodes = function(node_ids) {
+    var i, len, node, node_id, select_count;
+    select_count = 0;
+    for (i = 0, len = node_ids.length; i < len; i++) {
+      node_id = node_ids[i];
+      node = this.tree_widget.getNodeById(node_id);
+      if (node) {
+        select_count += 1;
+        this.tree_widget.select_node_handler.addToSelection(node);
+      }
+    }
+    return select_count !== 0;
+  };
+
+  SaveStateHandler.prototype.setInitialStateOnDemand = function(state, cb_finished) {
+    if (state) {
+      return this._setInitialStateOnDemand(state.open_nodes, state.selected_node, cb_finished);
+    } else {
+      return cb_finished();
+    }
+  };
+
+  SaveStateHandler.prototype._setInitialStateOnDemand = function(node_ids, selected_nodes, cb_finished) {
+    var loadAndOpenNode, loading_count, openNodes;
+    loading_count = 0;
+    openNodes = (function(_this) {
+      return function() {
+        var i, len, new_nodes_ids, node, node_id;
+        new_nodes_ids = [];
+        for (i = 0, len = node_ids.length; i < len; i++) {
+          node_id = node_ids[i];
+          node = _this.tree_widget.getNodeById(node_id);
+          if (!node) {
+            new_nodes_ids.push(node_id);
+          } else {
+            if (!node.is_loading) {
+              if (node.load_on_demand) {
+                loadAndOpenNode(node);
+              } else {
+                _this.tree_widget._openNode(node, false);
+              }
+            }
+          }
+        }
+        node_ids = new_nodes_ids;
+        if (_this._selectInitialNodes(selected_nodes)) {
+          _this.tree_widget._refreshElements();
+        }
+        if (loading_count === 0) {
+          return cb_finished();
+        }
+      };
+    })(this);
+    loadAndOpenNode = (function(_this) {
+      return function(node) {
+        loading_count += 1;
+        return _this.tree_widget._openNode(node, false, function() {
+          loading_count -= 1;
+          return openNodes();
+        });
+      };
+    })(this);
+    return openNodes();
+  };
+
+  SaveStateHandler.prototype.getCookieName = function() {
+    if (typeof this.tree_widget.options.saveState === 'string') {
+      return this.tree_widget.options.saveState;
+    } else {
+      return 'tree';
+    }
+  };
+
+  SaveStateHandler.prototype.supportsLocalStorage = function() {
+    var testSupport;
+    testSupport = function() {
+      var error, error1, key;
+      if (typeof localStorage === "undefined" || localStorage === null) {
+        return false;
+      } else {
+        try {
+          key = '_storage_test';
+          sessionStorage.setItem(key, true);
+          sessionStorage.removeItem(key);
+        } catch (error1) {
+          error = error1;
+          return false;
+        }
+        return true;
+      }
+    };
+    if (this._supportsLocalStorage == null) {
+      this._supportsLocalStorage = testSupport();
+    }
+    return this._supportsLocalStorage;
+  };
+
+  SaveStateHandler.prototype.getNodeIdToBeSelected = function() {
+    var state;
+    state = this.getStateFromStorage();
+    if (state && state.selected_node) {
+      return state.selected_node[0];
+    } else {
+      return null;
+    }
+  };
+
+  return SaveStateHandler;
+
+})();
+
+module.exports = SaveStateHandler;
+
+},{"./util":12}],8:[function(require,module,exports){
+var $, ScrollHandler;
+
+$ = jQuery;
+
+ScrollHandler = (function() {
+  function ScrollHandler(tree_widget) {
+    this.tree_widget = tree_widget;
+    this.previous_top = -1;
+    this.is_initialized = false;
+    this._initScrollParent();
+  }
+
+  ScrollHandler.prototype._initScrollParent = function() {
+    var $scroll_parent, getParentWithOverflow, setDocumentAsScrollParent;
+    getParentWithOverflow = (function(_this) {
+      return function() {
+        var css_values, el, hasOverFlow, i, len, ref;
+        css_values = ['overflow', 'overflow-y'];
+        hasOverFlow = function(el) {
+          var css_value, i, len, ref;
+          for (i = 0, len = css_values.length; i < len; i++) {
+            css_value = css_values[i];
+            if ((ref = $.css(el, css_value)) === 'auto' || ref === 'scroll') {
+              return true;
+            }
+          }
+          return false;
+        };
+        if (hasOverFlow(_this.tree_widget.$el[0])) {
+          return _this.tree_widget.$el;
+        }
+        ref = _this.tree_widget.$el.parents();
+        for (i = 0, len = ref.length; i < len; i++) {
+          el = ref[i];
+          if (hasOverFlow(el)) {
+            return $(el);
+          }
+        }
+        return null;
+      };
+    })(this);
+    setDocumentAsScrollParent = (function(_this) {
+      return function() {
+        _this.scroll_parent_top = 0;
+        return _this.$scroll_parent = null;
+      };
+    })(this);
+    if (this.tree_widget.$el.css('position') === 'fixed') {
+      setDocumentAsScrollParent();
+    }
+    $scroll_parent = getParentWithOverflow();
+    if ($scroll_parent && $scroll_parent.length && $scroll_parent[0].tagName !== 'HTML') {
+      this.$scroll_parent = $scroll_parent;
+      this.scroll_parent_top = this.$scroll_parent.offset().top;
+    } else {
+      setDocumentAsScrollParent();
+    }
+    return this.is_initialized = true;
+  };
+
+  ScrollHandler.prototype._ensureInit = function() {
+    if (!this.is_initialized) {
+      return this._initScrollParent();
+    }
+  };
+
+  ScrollHandler.prototype.checkScrolling = function() {
+    var hovered_area;
+    this._ensureInit();
+    hovered_area = this.tree_widget.dnd_handler.hovered_area;
+    if (hovered_area && hovered_area.top !== this.previous_top) {
+      this.previous_top = hovered_area.top;
+      if (this.$scroll_parent) {
+        return this._handleScrollingWithScrollParent(hovered_area);
+      } else {
+        return this._handleScrollingWithDocument(hovered_area);
+      }
+    }
+  };
+
+  ScrollHandler.prototype._handleScrollingWithScrollParent = function(area) {
+    var distance_bottom;
+    distance_bottom = this.scroll_parent_top + this.$scroll_parent[0].offsetHeight - area.bottom;
+    if (distance_bottom < 20) {
+      this.$scroll_parent[0].scrollTop += 20;
+      this.tree_widget.refreshHitAreas();
+      return this.previous_top = -1;
+    } else if ((area.top - this.scroll_parent_top) < 20) {
+      this.$scroll_parent[0].scrollTop -= 20;
+      this.tree_widget.refreshHitAreas();
+      return this.previous_top = -1;
+    }
+  };
+
+  ScrollHandler.prototype._handleScrollingWithDocument = function(area) {
+    var distance_top;
+    distance_top = area.top - $(document).scrollTop();
+    if (distance_top < 20) {
+      return $(document).scrollTop($(document).scrollTop() - 20);
+    } else if ($(window).height() - (area.bottom - $(document).scrollTop()) < 20) {
+      return $(document).scrollTop($(document).scrollTop() + 20);
+    }
+  };
+
+  ScrollHandler.prototype.scrollTo = function(top) {
+    var tree_top;
+    this._ensureInit();
+    if (this.$scroll_parent) {
+      return this.$scroll_parent[0].scrollTop = top;
+    } else {
+      tree_top = this.tree_widget.$el.offset().top;
+      return $(document).scrollTop(top + tree_top);
+    }
+  };
+
+  ScrollHandler.prototype.isScrolledIntoView = function(element) {
+    var $element, element_bottom, element_top, view_bottom, view_top;
+    this._ensureInit();
+    $element = $(element);
+    if (this.$scroll_parent) {
+      view_top = 0;
+      view_bottom = this.$scroll_parent.height();
+      element_top = $element.offset().top - this.scroll_parent_top;
+      element_bottom = element_top + $element.height();
+    } else {
+      view_top = $(window).scrollTop();
+      view_bottom = view_top + $(window).height();
+      element_top = $element.offset().top;
+      element_bottom = element_top + $element.height();
+    }
+    return (element_bottom <= view_bottom) && (element_top >= view_top);
+  };
+
+  return ScrollHandler;
+
+})();
+
+module.exports = ScrollHandler;
+
+},{}],9:[function(require,module,exports){
+var $, SelectNodeHandler;
+
+$ = jQuery;
+
+SelectNodeHandler = (function() {
+  function SelectNodeHandler(tree_widget) {
+    this.tree_widget = tree_widget;
+    this.clear();
+  }
+
+  SelectNodeHandler.prototype.getSelectedNode = function() {
+    var selected_nodes;
+    selected_nodes = this.getSelectedNodes();
+    if (selected_nodes.length) {
+      return selected_nodes[0];
+    } else {
+      return false;
+    }
+  };
+
+  SelectNodeHandler.prototype.getSelectedNodes = function() {
+    var id, node, selected_nodes;
+    if (this.selected_single_node) {
+      return [this.selected_single_node];
+    } else {
+      selected_nodes = [];
+      for (id in this.selected_nodes) {
+        node = this.tree_widget.getNodeById(id);
+        if (node) {
+          selected_nodes.push(node);
+        }
+      }
+      return selected_nodes;
+    }
+  };
+
+  SelectNodeHandler.prototype.getSelectedNodesUnder = function(parent) {
+    var id, node, selected_nodes;
+    if (this.selected_single_node) {
+      if (parent.isParentOf(this.selected_single_node)) {
+        return [this.selected_single_node];
+      } else {
+        return [];
+      }
+    } else {
+      selected_nodes = [];
+      for (id in this.selected_nodes) {
+        node = this.tree_widget.getNodeById(id);
+        if (node && parent.isParentOf(node)) {
+          selected_nodes.push(node);
+        }
+      }
+      return selected_nodes;
+    }
+  };
+
+  SelectNodeHandler.prototype.isNodeSelected = function(node) {
+    if (node.id) {
+      return this.selected_nodes[node.id];
+    } else if (this.selected_single_node) {
+      return this.selected_single_node.element === node.element;
+    } else {
+      return false;
+    }
+  };
+
+  SelectNodeHandler.prototype.clear = function() {
+    this.selected_nodes = {};
+    return this.selected_single_node = null;
+  };
+
+  SelectNodeHandler.prototype.removeFromSelection = function(node, include_children) {
+    if (include_children == null) {
+      include_children = false;
+    }
+    if (!node.id) {
+      if (this.selected_single_node && node.element === this.selected_single_node.element) {
+        return this.selected_single_node = null;
+      }
+    } else {
+      delete this.selected_nodes[node.id];
+      if (include_children) {
+        return node.iterate((function(_this) {
+          return function(n) {
+            delete _this.selected_nodes[node.id];
+            return true;
+          };
+        })(this));
+      }
+    }
+  };
+
+  SelectNodeHandler.prototype.addToSelection = function(node) {
+    if (node.id) {
+      return this.selected_nodes[node.id] = true;
+    } else {
+      return this.selected_single_node = node;
+    }
+  };
+
+  return SelectNodeHandler;
+
+})();
+
+module.exports = SelectNodeHandler;
+
+},{}],10:[function(require,module,exports){
+
+/*
+Copyright 2013 Marco Braak
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ */
+var $, SimpleWidget,
+  slice = [].slice;
+
+$ = jQuery;
+
+SimpleWidget = (function() {
+  SimpleWidget.prototype.defaults = {};
+
+  function SimpleWidget(el, options) {
+    this.$el = $(el);
+    this.options = $.extend({}, this.defaults, options);
+  }
+
+  SimpleWidget.prototype.destroy = function() {
+    return this._deinit();
+  };
+
+  SimpleWidget.prototype._init = function() {
+    return null;
+  };
+
+  SimpleWidget.prototype._deinit = function() {
+    return null;
+  };
+
+  SimpleWidget.register = function(widget_class, widget_name) {
+    var callFunction, createWidget, destroyWidget, getDataKey, getWidgetData;
+    getDataKey = function() {
+      return "simple_widget_" + widget_name;
+    };
+    getWidgetData = function(el, data_key) {
+      var widget;
+      widget = $.data(el, data_key);
+      if (widget && (widget instanceof SimpleWidget)) {
+        return widget;
+      } else {
+        return null;
+      }
+    };
+    createWidget = function($el, options) {
+      var data_key, el, existing_widget, i, len, widget;
+      data_key = getDataKey();
+      for (i = 0, len = $el.length; i < len; i++) {
+        el = $el[i];
+        existing_widget = getWidgetData(el, data_key);
+        if (!existing_widget) {
+          widget = new widget_class(el, options);
+          if (!$.data(el, data_key)) {
+            $.data(el, data_key, widget);
+          }
+          widget._init();
+        }
+      }
+      return $el;
+    };
+    destroyWidget = function($el) {
+      var data_key, el, i, len, results, widget;
+      data_key = getDataKey();
+      results = [];
+      for (i = 0, len = $el.length; i < len; i++) {
+        el = $el[i];
+        widget = getWidgetData(el, data_key);
+        if (widget) {
+          widget.destroy();
+        }
+        results.push($.removeData(el, data_key));
+      }
+      return results;
+    };
+    callFunction = function($el, function_name, args) {
+      var el, i, len, result, widget, widget_function;
+      result = null;
+      for (i = 0, len = $el.length; i < len; i++) {
+        el = $el[i];
+        widget = $.data(el, getDataKey());
+        if (widget && (widget instanceof SimpleWidget)) {
+          widget_function = widget[function_name];
+          if (widget_function && (typeof widget_function === 'function')) {
+            result = widget_function.apply(widget, args);
+          }
+        }
+      }
+      return result;
+    };
+    return $.fn[widget_name] = function() {
+      var $el, args, argument1, function_name, options;
+      argument1 = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
+      $el = this;
+      if (argument1 === void 0 || typeof argument1 === 'object') {
+        options = argument1;
+        return createWidget($el, options);
+      } else if (typeof argument1 === 'string' && argument1[0] !== '_') {
+        function_name = argument1;
+        if (function_name === 'destroy') {
+          return destroyWidget($el);
+        } else if (function_name === 'get_widget_class') {
+          return widget_class;
+        } else {
+          return callFunction($el, function_name, args);
+        }
+      }
+    };
+  };
+
+  return SimpleWidget;
+
+})();
+
+module.exports = SimpleWidget;
+
+},{}],11:[function(require,module,exports){
+var $, BorderDropHint, DragAndDropHandler, DragElement, ElementsRenderer, FolderElement, GhostDropHint, HitAreasGenerator, JqTreeWidget, KeyHandler, MouseWidget, Node, NodeElement, Position, SaveStateHandler, ScrollHandler, SelectNodeHandler, SimpleWidget, __version__, node_module, ref, ref1, util_module,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+__version__ = require('./version');
+
+ref = require('./drag_and_drop_handler'), DragAndDropHandler = ref.DragAndDropHandler, DragElement = ref.DragElement, HitAreasGenerator = ref.HitAreasGenerator;
+
+ElementsRenderer = require('./elements_renderer');
+
+KeyHandler = require('./key_handler');
+
+MouseWidget = require('./mouse.widget');
+
+SaveStateHandler = require('./save_state_handler');
+
+ScrollHandler = require('./scroll_handler');
+
+SelectNodeHandler = require('./select_node_handler');
+
+SimpleWidget = require('./simple.widget');
+
+node_module = require('./node');
+
+Node = node_module.Node;
+
+Position = node_module.Position;
+
+util_module = require('./util');
+
+ref1 = require('./node_element'), BorderDropHint = ref1.BorderDropHint, FolderElement = ref1.FolderElement, GhostDropHint = ref1.GhostDropHint, NodeElement = ref1.NodeElement;
+
+$ = jQuery;
+
+JqTreeWidget = (function(superClass) {
+  extend(JqTreeWidget, superClass);
+
+  function JqTreeWidget() {
+    return JqTreeWidget.__super__.constructor.apply(this, arguments);
+  }
+
+  JqTreeWidget.prototype.BorderDropHint = BorderDropHint;
+
+  JqTreeWidget.prototype.DragElement = DragElement;
+
+  JqTreeWidget.prototype.DragAndDropHandler = DragAndDropHandler;
+
+  JqTreeWidget.prototype.ElementsRenderer = ElementsRenderer;
+
+  JqTreeWidget.prototype.GhostDropHint = GhostDropHint;
+
+  JqTreeWidget.prototype.HitAreasGenerator = HitAreasGenerator;
+
+  JqTreeWidget.prototype.Node = Node;
+
+  JqTreeWidget.prototype.SaveStateHandler = SaveStateHandler;
+
+  JqTreeWidget.prototype.ScrollHandler = ScrollHandler;
+
+  JqTreeWidget.prototype.SelectNodeHandler = SelectNodeHandler;
+
+  JqTreeWidget.prototype.defaults = {
+    autoOpen: false,
+    saveState: false,
+    dragAndDrop: false,
+    selectable: true,
+    useContextMenu: true,
+    onCanSelectNode: null,
+    onSetStateFromStorage: null,
+    onGetStateFromStorage: null,
+    onCreateLi: null,
+    onIsMoveHandle: null,
+    onCanMove: null,
+    onCanMoveTo: null,
+    onLoadFailed: null,
+    autoEscape: true,
+    dataUrl: null,
+    closedIcon: null,
+    openedIcon: '&#x25bc;',
+    slide: true,
+    nodeClass: Node,
+    dataFilter: null,
+    keyboardSupport: true,
+    openFolderDelay: 500,
+    rtl: null,
+    onDragMove: null,
+    onDragStop: null,
+    buttonLeft: true,
+    onLoading: null
+  };
+
+  JqTreeWidget.prototype.toggle = function(node, slide) {
+    if (slide == null) {
+      slide = null;
+    }
+    if (slide === null) {
+      slide = this.options.slide;
+    }
+    if (node.is_open) {
+      this.closeNode(node, slide);
+    } else {
+      this.openNode(node, slide);
+    }
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.getTree = function() {
+    return this.tree;
+  };
+
+  JqTreeWidget.prototype.selectNode = function(node) {
+    this._selectNode(node, false);
+    return this.element;
+  };
+
+  JqTreeWidget.prototype._selectNode = function(node, must_toggle) {
+    var canSelect, deselected_node, openParents, saveState;
+    if (must_toggle == null) {
+      must_toggle = false;
+    }
+    if (!this.select_node_handler) {
+      return;
+    }
+    canSelect = (function(_this) {
+      return function() {
+        if (_this.options.onCanSelectNode) {
+          return _this.options.selectable && _this.options.onCanSelectNode(node);
+        } else {
+          return _this.options.selectable;
+        }
+      };
+    })(this);
+    openParents = (function(_this) {
+      return function() {
+        var parent;
+        parent = node.parent;
+        if (parent && parent.parent && !parent.is_open) {
+          return _this.openNode(parent, false);
+        }
+      };
+    })(this);
+    saveState = (function(_this) {
+      return function() {
+        if (_this.options.saveState) {
+          return _this.save_state_handler.saveState();
+        }
+      };
+    })(this);
+    if (!node) {
+      this._deselectCurrentNode();
+      saveState();
+      return;
+    }
+    if (!canSelect()) {
+      return;
+    }
+    if (this.select_node_handler.isNodeSelected(node)) {
+      if (must_toggle) {
+        this._deselectCurrentNode();
+        this._triggerEvent('tree.select', {
+          node: null,
+          previous_node: node
+        });
+      }
+    } else {
+      deselected_node = this.getSelectedNode();
+      this._deselectCurrentNode();
+      this.addToSelection(node);
+      this._triggerEvent('tree.select', {
+        node: node,
+        deselected_node: deselected_node
+      });
+      openParents();
+    }
+    return saveState();
+  };
+
+  JqTreeWidget.prototype.getSelectedNode = function() {
+    if (this.select_node_handler) {
+      return this.select_node_handler.getSelectedNode();
+    } else {
+      return null;
+    }
+  };
+
+  JqTreeWidget.prototype.toJson = function() {
+    return JSON.stringify(this.tree.getData());
+  };
+
+  JqTreeWidget.prototype.loadData = function(data, parent_node) {
+    this._loadData(data, parent_node);
+    return this.element;
+  };
+
+
+  /*
+  signatures:
+  - loadDataFromUrl(url, parent_node=null, on_finished=null)
+      loadDataFromUrl('/my_data');
+      loadDataFromUrl('/my_data', node1);
+      loadDataFromUrl('/my_data', node1, function() { console.log('finished'); });
+      loadDataFromUrl('/my_data', null, function() { console.log('finished'); });
+  
+  - loadDataFromUrl(parent_node=null, on_finished=null)
+      loadDataFromUrl();
+      loadDataFromUrl(node1);
+      loadDataFromUrl(null, function() { console.log('finished'); });
+      loadDataFromUrl(node1, function() { console.log('finished'); });
+   */
+
+  JqTreeWidget.prototype.loadDataFromUrl = function(param1, param2, param3) {
+    if ($.type(param1) === 'string') {
+      this._loadDataFromUrl(param1, param2, param3);
+    } else {
+      this._loadDataFromUrl(null, param1, param2);
+    }
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.reload = function(on_finished) {
+    this._loadDataFromUrl(null, null, on_finished);
+    return this.element;
+  };
+
+  JqTreeWidget.prototype._loadDataFromUrl = function(url_info, parent_node, on_finished) {
+    var $el, addLoadingClass, handeLoadData, handleError, handleSuccess, loadDataFromUrlInfo, parseUrlInfo, removeLoadingClass;
+    $el = null;
+    addLoadingClass = (function(_this) {
+      return function() {
+        if (parent_node) {
+          $el = $(parent_node.element);
+        } else {
+          $el = _this.element;
+        }
+        $el.addClass('jqtree-loading');
+        return _this._notifyLoading(true, parent_node, $el);
+      };
+    })(this);
+    removeLoadingClass = (function(_this) {
+      return function() {
+        if ($el) {
+          $el.removeClass('jqtree-loading');
+          return _this._notifyLoading(false, parent_node, $el);
+        }
+      };
+    })(this);
+    parseUrlInfo = function() {
+      if ($.type(url_info) === 'string') {
+        return {
+          url: url_info
+        };
+      }
+      if (!url_info.method) {
+        url_info.method = 'get';
+      }
+      return url_info;
+    };
+    handeLoadData = (function(_this) {
+      return function(data) {
+        removeLoadingClass();
+        _this._loadData(data, parent_node);
+        if (on_finished && $.isFunction(on_finished)) {
+          return on_finished();
+        }
+      };
+    })(this);
+    handleSuccess = (function(_this) {
+      return function(response) {
+        var data;
+        if ($.isArray(response) || typeof response === 'object') {
+          data = response;
+        } else if (data != null) {
+          data = $.parseJSON(response);
+        } else {
+          data = [];
+        }
+        if (_this.options.dataFilter) {
+          data = _this.options.dataFilter(data);
+        }
+        return handeLoadData(data);
+      };
+    })(this);
+    handleError = (function(_this) {
+      return function(response) {
+        removeLoadingClass();
+        if (_this.options.onLoadFailed) {
+          return _this.options.onLoadFailed(response);
+        }
+      };
+    })(this);
+    loadDataFromUrlInfo = function() {
+      url_info = parseUrlInfo();
+      return $.ajax($.extend({}, url_info, {
+        method: url_info.method != null ? url_info.method.toUpperCase() : 'GET',
+        cache: false,
+        dataType: 'json',
+        success: handleSuccess,
+        error: handleError
+      }));
+    };
+    if (!url_info) {
+      url_info = this._getDataUrlInfo(parent_node);
+    }
+    addLoadingClass();
+    if (!url_info) {
+      removeLoadingClass();
+    } else if ($.isArray(url_info)) {
+      handeLoadData(url_info);
+    } else {
+      loadDataFromUrlInfo();
+    }
+  };
+
+  JqTreeWidget.prototype._loadData = function(data, parent_node) {
+    var deselectNodes, loadSubtree;
+    if (parent_node == null) {
+      parent_node = null;
+    }
+    deselectNodes = (function(_this) {
+      return function() {
+        var i, len, n, selected_nodes_under_parent;
+        if (_this.select_node_handler) {
+          selected_nodes_under_parent = _this.select_node_handler.getSelectedNodesUnder(parent_node);
+          for (i = 0, len = selected_nodes_under_parent.length; i < len; i++) {
+            n = selected_nodes_under_parent[i];
+            _this.select_node_handler.removeFromSelection(n);
+          }
+        }
+        return null;
+      };
+    })(this);
+    loadSubtree = (function(_this) {
+      return function() {
+        parent_node.loadFromData(data);
+        parent_node.load_on_demand = false;
+        parent_node.is_loading = false;
+        return _this._refreshElements(parent_node);
+      };
+    })(this);
+    if (!data) {
+      return;
+    }
+    this._triggerEvent('tree.load_data', {
+      tree_data: data
+    });
+    if (!parent_node) {
+      this._initTree(data);
+    } else {
+      deselectNodes();
+      loadSubtree();
+    }
+    if (this.isDragging()) {
+      return this.dnd_handler.refresh();
+    }
+  };
+
+  JqTreeWidget.prototype.getNodeById = function(node_id) {
+    return this.tree.getNodeById(node_id);
+  };
+
+  JqTreeWidget.prototype.getNodeByName = function(name) {
+    return this.tree.getNodeByName(name);
+  };
+
+  JqTreeWidget.prototype.getNodesByProperty = function(key, value) {
+    return this.tree.getNodesByProperty(key, value);
+  };
+
+  JqTreeWidget.prototype.openNode = function(node, slide) {
+    if (slide == null) {
+      slide = null;
+    }
+    if (slide === null) {
+      slide = this.options.slide;
+    }
+    this._openNode(node, slide);
+    return this.element;
+  };
+
+  JqTreeWidget.prototype._openNode = function(node, slide, on_finished) {
+    var doOpenNode, parent;
+    if (slide == null) {
+      slide = true;
+    }
+    doOpenNode = (function(_this) {
+      return function(_node, _slide, _on_finished) {
+        var folder_element;
+        folder_element = new FolderElement(_node, _this);
+        return folder_element.open(_on_finished, _slide);
+      };
+    })(this);
+    if (node.isFolder()) {
+      if (node.load_on_demand) {
+        return this._loadFolderOnDemand(node, slide, on_finished);
+      } else {
+        parent = node.parent;
+        while (parent) {
+          if (parent.parent) {
+            doOpenNode(parent, false, null);
+          }
+          parent = parent.parent;
+        }
+        doOpenNode(node, slide, on_finished);
+        return this._saveState();
+      }
+    }
+  };
+
+  JqTreeWidget.prototype._loadFolderOnDemand = function(node, slide, on_finished) {
+    if (slide == null) {
+      slide = true;
+    }
+    node.is_loading = true;
+    return this._loadDataFromUrl(null, node, (function(_this) {
+      return function() {
+        return _this._openNode(node, slide, on_finished);
+      };
+    })(this));
+  };
+
+  JqTreeWidget.prototype.closeNode = function(node, slide) {
+    if (slide == null) {
+      slide = null;
+    }
+    if (slide === null) {
+      slide = this.options.slide;
+    }
+    if (node.isFolder()) {
+      new FolderElement(node, this).close(slide);
+      this._saveState();
+    }
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.isDragging = function() {
+    if (this.dnd_handler) {
+      return this.dnd_handler.is_dragging;
+    } else {
+      return false;
+    }
+  };
+
+  JqTreeWidget.prototype.refreshHitAreas = function() {
+    this.dnd_handler.refresh();
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.addNodeAfter = function(new_node_info, existing_node) {
+    var new_node;
+    new_node = existing_node.addAfter(new_node_info);
+    this._refreshElements(existing_node.parent);
+    return new_node;
+  };
+
+  JqTreeWidget.prototype.addNodeBefore = function(new_node_info, existing_node) {
+    var new_node;
+    new_node = existing_node.addBefore(new_node_info);
+    this._refreshElements(existing_node.parent);
+    return new_node;
+  };
+
+  JqTreeWidget.prototype.addParentNode = function(new_node_info, existing_node) {
+    var new_node;
+    new_node = existing_node.addParent(new_node_info);
+    this._refreshElements(new_node.parent);
+    return new_node;
+  };
+
+  JqTreeWidget.prototype.removeNode = function(node) {
+    var parent;
+    parent = node.parent;
+    if (parent) {
+      this.select_node_handler.removeFromSelection(node, true);
+      node.remove();
+      this._refreshElements(parent);
+    }
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.appendNode = function(new_node_info, parent_node) {
+    var node;
+    parent_node = parent_node || this.tree;
+    node = parent_node.append(new_node_info);
+    this._refreshElements(parent_node);
+    return node;
+  };
+
+  JqTreeWidget.prototype.prependNode = function(new_node_info, parent_node) {
+    var node;
+    if (!parent_node) {
+      parent_node = this.tree;
+    }
+    node = parent_node.prepend(new_node_info);
+    this._refreshElements(parent_node);
+    return node;
+  };
+
+  JqTreeWidget.prototype.updateNode = function(node, data) {
+    var id_is_changed;
+    id_is_changed = data.id && data.id !== node.id;
+    if (id_is_changed) {
+      this.tree.removeNodeFromIndex(node);
+    }
+    node.setData(data);
+    if (id_is_changed) {
+      this.tree.addNodeToIndex(node);
+    }
+    if (typeof data === 'object' && data.children && data.children.length) {
+      node.removeChildren();
+      node.loadFromData(data.children);
+    }
+    this.renderer.renderFromNode(node);
+    this._selectCurrentNode();
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.moveNode = function(node, target_node, position) {
+    var position_index;
+    position_index = Position.nameToIndex(position);
+    this.tree.moveNode(node, target_node, position_index);
+    this._refreshElements();
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.getStateFromStorage = function() {
+    return this.save_state_handler.getStateFromStorage();
+  };
+
+  JqTreeWidget.prototype.addToSelection = function(node) {
+    if (node) {
+      this.select_node_handler.addToSelection(node);
+      this._getNodeElementForNode(node).select();
+      this._saveState();
+    }
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.getSelectedNodes = function() {
+    return this.select_node_handler.getSelectedNodes();
+  };
+
+  JqTreeWidget.prototype.isNodeSelected = function(node) {
+    return this.select_node_handler.isNodeSelected(node);
+  };
+
+  JqTreeWidget.prototype.removeFromSelection = function(node) {
+    this.select_node_handler.removeFromSelection(node);
+    this._getNodeElementForNode(node).deselect();
+    this._saveState();
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.scrollToNode = function(node) {
+    var $element, top;
+    $element = $(node.element);
+    top = $element.offset().top - this.$el.offset().top;
+    this.scroll_handler.scrollTo(top);
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.getState = function() {
+    return this.save_state_handler.getState();
+  };
+
+  JqTreeWidget.prototype.setState = function(state) {
+    this.save_state_handler.setInitialState(state);
+    this._refreshElements();
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.setOption = function(option, value) {
+    this.options[option] = value;
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.moveDown = function() {
+    if (this.key_handler) {
+      this.key_handler.moveDown();
+    }
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.moveUp = function() {
+    if (this.key_handler) {
+      this.key_handler.moveUp();
+    }
+    return this.element;
+  };
+
+  JqTreeWidget.prototype.getVersion = function() {
+    return __version__;
+  };
+
+  JqTreeWidget.prototype._init = function() {
+    JqTreeWidget.__super__._init.call(this);
+    this.element = this.$el;
+    this.mouse_delay = 300;
+    this.is_initialized = false;
+    this.options.rtl = this._getRtlOption();
+    if (!this.options.closedIcon) {
+      this.options.closedIcon = this._getDefaultClosedIcon();
+    }
+    this.renderer = new ElementsRenderer(this);
+    if (SaveStateHandler != null) {
+      this.save_state_handler = new SaveStateHandler(this);
+    } else {
+      this.options.saveState = false;
+    }
+    if (SelectNodeHandler != null) {
+      this.select_node_handler = new SelectNodeHandler(this);
+    }
+    if (DragAndDropHandler != null) {
+      this.dnd_handler = new DragAndDropHandler(this);
+    } else {
+      this.options.dragAndDrop = false;
+    }
+    if (ScrollHandler != null) {
+      this.scroll_handler = new ScrollHandler(this);
+    }
+    if ((KeyHandler != null) && (SelectNodeHandler != null)) {
+      this.key_handler = new KeyHandler(this);
+    }
+    this._initData();
+    this.element.click($.proxy(this._click, this));
+    this.element.dblclick($.proxy(this._dblclick, this));
+    if (this.options.useContextMenu) {
+      return this.element.bind('contextmenu', $.proxy(this._contextmenu, this));
+    }
+  };
+
+  JqTreeWidget.prototype._deinit = function() {
+    this.element.empty();
+    this.element.unbind();
+    if (this.key_handler) {
+      this.key_handler.deinit();
+    }
+    this.tree = null;
+    return JqTreeWidget.__super__._deinit.call(this);
+  };
+
+  JqTreeWidget.prototype._initData = function() {
+    if (this.options.data) {
+      return this._loadData(this.options.data);
+    } else {
+      return this._loadDataFromUrl(this._getDataUrlInfo());
+    }
+  };
+
+  JqTreeWidget.prototype._getDataUrlInfo = function(node) {
+    var data_url, getUrlFromString;
+    data_url = this.options.dataUrl || this.element.data('url');
+    getUrlFromString = (function(_this) {
+      return function() {
+        var data, selected_node_id, url_info;
+        url_info = {
+          url: data_url
+        };
+        if (node && node.id) {
+          data = {
+            node: node.id
+          };
+          url_info['data'] = data;
+        } else {
+          selected_node_id = _this._getNodeIdToBeSelected();
+          if (selected_node_id) {
+            data = {
+              selected_node: selected_node_id
+            };
+            url_info['data'] = data;
+          }
+        }
+        return url_info;
+      };
+    })(this);
+    if ($.isFunction(data_url)) {
+      return data_url(node);
+    } else if ($.type(data_url) === 'string') {
+      return getUrlFromString();
+    } else {
+      return data_url;
+    }
+  };
+
+  JqTreeWidget.prototype._getNodeIdToBeSelected = function() {
+    if (this.options.saveState) {
+      return this.save_state_handler.getNodeIdToBeSelected();
+    } else {
+      return null;
+    }
+  };
+
+  JqTreeWidget.prototype._initTree = function(data) {
+    var doInit, must_load_on_demand;
+    doInit = (function(_this) {
+      return function() {
+        if (!_this.is_initialized) {
+          _this.is_initialized = true;
+          return _this._triggerEvent('tree.init');
+        }
+      };
+    })(this);
+    this.tree = new this.options.nodeClass(null, true, this.options.nodeClass);
+    if (this.select_node_handler) {
+      this.select_node_handler.clear();
+    }
+    this.tree.loadFromData(data);
+    must_load_on_demand = this._setInitialState();
+    this._refreshElements();
+    if (!must_load_on_demand) {
+      return doInit();
+    } else {
+      return this._setInitialStateOnDemand(doInit);
+    }
+  };
+
+  JqTreeWidget.prototype._setInitialState = function() {
+    var autoOpenNodes, is_restored, must_load_on_demand, ref2, restoreState;
+    restoreState = (function(_this) {
+      return function() {
+        var must_load_on_demand, state;
+        if (!(_this.options.saveState && _this.save_state_handler)) {
+          return [false, false];
+        } else {
+          state = _this.save_state_handler.getStateFromStorage();
+          if (!state) {
+            return [false, false];
+          } else {
+            must_load_on_demand = _this.save_state_handler.setInitialState(state);
+            return [true, must_load_on_demand];
+          }
+        }
+      };
+    })(this);
+    autoOpenNodes = (function(_this) {
+      return function() {
+        var max_level, must_load_on_demand;
+        if (_this.options.autoOpen === false) {
+          return false;
+        }
+        max_level = _this._getAutoOpenMaxLevel();
+        must_load_on_demand = false;
+        _this.tree.iterate(function(node, level) {
+          if (node.load_on_demand) {
+            must_load_on_demand = true;
+            return false;
+          } else if (!node.hasChildren()) {
+            return false;
+          } else {
+            node.is_open = true;
+            return level !== max_level;
+          }
+        });
+        return must_load_on_demand;
+      };
+    })(this);
+    ref2 = restoreState(), is_restored = ref2[0], must_load_on_demand = ref2[1];
+    if (!is_restored) {
+      must_load_on_demand = autoOpenNodes();
+    }
+    return must_load_on_demand;
+  };
+
+  JqTreeWidget.prototype._setInitialStateOnDemand = function(cb_finished) {
+    var autoOpenNodes, restoreState;
+    restoreState = (function(_this) {
+      return function() {
+        var state;
+        if (!(_this.options.saveState && _this.save_state_handler)) {
+          return false;
+        } else {
+          state = _this.save_state_handler.getStateFromStorage();
+          if (!state) {
+            return false;
+          } else {
+            _this.save_state_handler.setInitialStateOnDemand(state, cb_finished);
+            return true;
+          }
+        }
+      };
+    })(this);
+    autoOpenNodes = (function(_this) {
+      return function() {
+        var loadAndOpenNode, loading_count, max_level, openNodes;
+        max_level = _this._getAutoOpenMaxLevel();
+        loading_count = 0;
+        loadAndOpenNode = function(node) {
+          loading_count += 1;
+          return _this._openNode(node, false, function() {
+            loading_count -= 1;
+            return openNodes();
+          });
+        };
+        openNodes = function() {
+          _this.tree.iterate(function(node, level) {
+            if (node.load_on_demand) {
+              if (!node.is_loading) {
+                loadAndOpenNode(node);
+              }
+              return false;
+            } else {
+              _this._openNode(node, false);
+              return level !== max_level;
+            }
+          });
+          if (loading_count === 0) {
+            return cb_finished();
+          }
+        };
+        return openNodes();
+      };
+    })(this);
+    if (!restoreState()) {
+      return autoOpenNodes();
+    }
+  };
+
+  JqTreeWidget.prototype._getAutoOpenMaxLevel = function() {
+    if (this.options.autoOpen === true) {
+      return -1;
+    } else {
+      return parseInt(this.options.autoOpen);
+    }
+  };
+
+
+  /*
+  Redraw the tree or part of the tree.
+   * from_node: redraw this subtree
+   */
+
+  JqTreeWidget.prototype._refreshElements = function(from_node) {
+    if (from_node == null) {
+      from_node = null;
+    }
+    this.renderer.render(from_node);
+    return this._triggerEvent('tree.refresh');
+  };
+
+  JqTreeWidget.prototype._click = function(e) {
+    var click_target, event, node;
+    click_target = this._getClickTarget(e.target);
+    if (click_target) {
+      if (click_target.type === 'button') {
+        this.toggle(click_target.node, this.options.slide);
+        e.preventDefault();
+        return e.stopPropagation();
+      } else if (click_target.type === 'label') {
+        node = click_target.node;
+        event = this._triggerEvent('tree.click', {
+          node: node,
+          click_event: e
+        });
+        if (!event.isDefaultPrevented()) {
+          return this._selectNode(node, true);
+        }
+      }
+    }
+  };
+
+  JqTreeWidget.prototype._dblclick = function(e) {
+    var click_target;
+    click_target = this._getClickTarget(e.target);
+    if (click_target && click_target.type === 'label') {
+      return this._triggerEvent('tree.dblclick', {
+        node: click_target.node,
+        click_event: e
+      });
+    }
+  };
+
+  JqTreeWidget.prototype._getClickTarget = function(element) {
+    var $button, $el, $target, node;
+    $target = $(element);
+    $button = $target.closest('.jqtree-toggler');
+    if ($button.length) {
+      node = this._getNode($button);
+      if (node) {
+        return {
+          type: 'button',
+          node: node
+        };
+      }
+    } else {
+      $el = $target.closest('.jqtree-element');
+      if ($el.length) {
+        node = this._getNode($el);
+        if (node) {
+          return {
+            type: 'label',
+            node: node
+          };
+        }
+      }
+    }
+    return null;
+  };
+
+  JqTreeWidget.prototype._getNode = function($element) {
+    var $li;
+    $li = $element.closest('li.jqtree_common');
+    if ($li.length === 0) {
+      return null;
+    } else {
+      return $li.data('node');
+    }
+  };
+
+  JqTreeWidget.prototype._getNodeElementForNode = function(node) {
+    if (node.isFolder()) {
+      return new FolderElement(node, this);
+    } else {
+      return new NodeElement(node, this);
+    }
+  };
+
+  JqTreeWidget.prototype._getNodeElement = function($element) {
+    var node;
+    node = this._getNode($element);
+    if (node) {
+      return this._getNodeElementForNode(node);
+    } else {
+      return null;
+    }
+  };
+
+  JqTreeWidget.prototype._contextmenu = function(e) {
+    var $div, node;
+    $div = $(e.target).closest('ul.jqtree-tree .jqtree-element');
+    if ($div.length) {
+      node = this._getNode($div);
+      if (node) {
+        e.preventDefault();
+        e.stopPropagation();
+        this._triggerEvent('tree.contextmenu', {
+          node: node,
+          click_event: e
+        });
+        return false;
+      }
+    }
+  };
+
+  JqTreeWidget.prototype._saveState = function() {
+    if (this.options.saveState) {
+      return this.save_state_handler.saveState();
+    }
+  };
+
+  JqTreeWidget.prototype._mouseCapture = function(position_info) {
+    if (this.options.dragAndDrop) {
+      return this.dnd_handler.mouseCapture(position_info);
+    } else {
+      return false;
+    }
+  };
+
+  JqTreeWidget.prototype._mouseStart = function(position_info) {
+    if (this.options.dragAndDrop) {
+      return this.dnd_handler.mouseStart(position_info);
+    } else {
+      return false;
+    }
+  };
+
+  JqTreeWidget.prototype._mouseDrag = function(position_info) {
+    var result;
+    if (this.options.dragAndDrop) {
+      result = this.dnd_handler.mouseDrag(position_info);
+      if (this.scroll_handler) {
+        this.scroll_handler.checkScrolling();
+      }
+      return result;
+    } else {
+      return false;
+    }
+  };
+
+  JqTreeWidget.prototype._mouseStop = function(position_info) {
+    if (this.options.dragAndDrop) {
+      return this.dnd_handler.mouseStop(position_info);
+    } else {
+      return false;
+    }
+  };
+
+  JqTreeWidget.prototype._triggerEvent = function(event_name, values) {
+    var event;
+    event = $.Event(event_name);
+    $.extend(event, values);
+    this.element.trigger(event);
+    return event;
+  };
+
+  JqTreeWidget.prototype.testGenerateHitAreas = function(moving_node) {
+    this.dnd_handler.current_item = this._getNodeElementForNode(moving_node);
+    this.dnd_handler.generateHitAreas();
+    return this.dnd_handler.hit_areas;
+  };
+
+  JqTreeWidget.prototype._selectCurrentNode = function() {
+    var node, node_element;
+    node = this.getSelectedNode();
+    if (node) {
+      node_element = this._getNodeElementForNode(node);
+      if (node_element) {
+        return node_element.select();
+      }
+    }
+  };
+
+  JqTreeWidget.prototype._deselectCurrentNode = function() {
+    var node;
+    node = this.getSelectedNode();
+    if (node) {
+      return this.removeFromSelection(node);
+    }
+  };
+
+  JqTreeWidget.prototype._getDefaultClosedIcon = function() {
+    if (this.options.rtl) {
+      return '&#x25c0;';
+    } else {
+      return '&#x25ba;';
+    }
+  };
+
+  JqTreeWidget.prototype._getRtlOption = function() {
+    var data_rtl;
+    if (this.options.rtl !== null) {
+      return this.options.rtl;
+    } else {
+      data_rtl = this.element.data('rtl');
+      if ((data_rtl != null) && data_rtl !== false) {
+        return true;
+      } else {
+        return false;
+      }
+    }
+  };
+
+  JqTreeWidget.prototype._notifyLoading = function(is_loading, node, $el) {
+    if (this.options.onLoading) {
+      return this.options.onLoading(is_loading, node, $el);
+    }
+  };
+
+  return JqTreeWidget;
+
+})(MouseWidget);
+
+JqTreeWidget.getModule = function(name) {
+  var modules;
+  modules = {
+    'node': node_module,
+    'util': util_module
+  };
+  return modules[name];
+};
+
+SimpleWidget.register(JqTreeWidget, 'tree');
+
+},{"./drag_and_drop_handler":1,"./elements_renderer":2,"./key_handler":3,"./mouse.widget":4,"./node":5,"./node_element":6,"./save_state_handler":7,"./scroll_handler":8,"./select_node_handler":9,"./simple.widget":10,"./util":12,"./version":13}],12:[function(require,module,exports){
+var _indexOf, getBoolString, html_escape, indexOf, isInt;
+
+_indexOf = function(array, item) {
+  var i, j, len, value;
+  for (i = j = 0, len = array.length; j < len; i = ++j) {
+    value = array[i];
+    if (value === item) {
+      return i;
+    }
+  }
+  return -1;
+};
+
+indexOf = function(array, item) {
+  if (array.indexOf) {
+    return array.indexOf(item);
+  } else {
+    return _indexOf(array, item);
+  }
+};
+
+isInt = function(n) {
+  return typeof n === 'number' && n % 1 === 0;
+};
+
+html_escape = function(string) {
+  return ('' + string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g, '&#x2F;');
+};
+
+getBoolString = function(value) {
+  if (value) {
+    return 'true';
+  } else {
+    return 'false';
+  }
+};
+
+module.exports = {
+  _indexOf: _indexOf,
+  getBoolString: getBoolString,
+  html_escape: html_escape,
+  indexOf: indexOf,
+  isInt: isInt
+};
+
+},{}],13:[function(require,module,exports){
+module.exports = '1.3.3';
+
+},{}]},{},[11]);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/dataimport.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,184 @@
+# coding: utf-8
+# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-seda data import tools"""
+
+from __future__ import print_function
+
+from itertools import count
+from os.path import join, dirname
+
+from six import text_type
+
+from cubicweb.server.checkintegrity import reindex_entities
+from cubicweb.dataimport.stores import NoHookRQLObjectStore
+from cubicweb.dataimport.importer import SimpleImportLog
+
+from cubes.skos import lcsv, sobjects as skos
+
+
+LCSV_FILES = (
+    # schemes extracted from SEDA 2 XSD
+    (u'SEDA 2 : Actions',
+     'seda_final_action', 'SEDAStorageRule',
+     'final_action_storage_code_type.csv'),
+    (u'SEDA 2 : Unités de mesure',
+     'seda_unit', ('SEDAWidth', 'SEDAHeight', 'SEDADepth',
+                   'SEDADiameter', 'SEDALength', 'SEDAThickness'),
+     'measurement_units_type.csv'),
+    (u'SEDA 2 : Unités de poids',
+     'seda_unit', 'SEDAWeight',
+     'measurement_weight_units_type.csv'),
+    (u'SEDA 2 : Types de mot-clé',
+     'seda_keyword_type_to', (),
+     'code_keyword_type.csv'),
+    (u'SEDA : Niveaux de description',
+     'seda_description_level', (),
+     'level_type.csv'),
+    # schemes extracted from SEDA 2 XSD, completed to support earlier SEDA versions
+    (u'SEDA : Sort final',
+     'seda_final_action', 'SEDAAppraisalRule',
+     'final_action_appraisal_code_type.csv'),
+    # schemes extracted from earlier SEDA versions
+    (u"SEDA : Durée d'utilité administrative",
+     'seda_rule', 'SEDASeqAppraisalRuleRule',
+     'dua.csv'),
+    (u"SEDA : Codes de restriction d'accès",
+     'seda_rule', 'SEDASeqAccessRuleRule',
+     'access_control.csv'),
+    (u"SEDA : Types d'objets-données",
+     'seda_type_to', (),
+     'document_type_code.csv'),
+    # other schemes
+    (u'Types MIME',
+     'seda_mime_type_to', (),
+     'mime_types.csv'),
+    (u"Types d'évènement",
+     'seda_event_type_to', (),
+     'event_types.csv'),
+    (u'Encodages (extraits du schéma UN/CEFACT)',
+     'seda_encoding_to', (),
+     'encodings.csv'),
+    (u'Formats de fichier (PRONOM)',
+     'seda_format_id_to', (),
+     'file_formats.csv'),
+    (u'Niveau de classification (IGI 1300)',
+     'seda_classification_level', (),
+     'classification_levels.csv'),
+    (u'Langues (ISO-639-3)',
+     ('seda_language_to', 'seda_description_language_to'), (),
+     'languages.csv'),
+)
+
+
+def lcsv_import(cnx, store, fname, scheme_uri):
+    """Actually import LCSV data file."""
+    with open(join(dirname(__file__), 'migration', 'data', fname)) as stream:
+        extentities = skos.lcsv_extentities(stream, scheme_uri, ';', 'utf-8')
+        import_log = SimpleImportLog(fname)
+        skos.store_skos_extentities(cnx, store, extentities, import_log,
+                                    raise_on_error=True, extid_as_cwuri=False)
+
+
+def lcsv_check(cnx, store, fname, scheme_uri):
+    """Simply check data file consistency."""
+    counter = count()
+
+    def uri_generator(val):
+        return text_type(next(counter)) + val
+
+    with open(join(dirname(__file__), 'migration', 'data', fname)) as stream:
+        lcsv2rdf = lcsv.LCSV2RDF(stream, ';', 'utf-8',
+                                 # XXX drop once skos is released
+                                 uri_generator=uri_generator, uri_cls=text_type)
+        list(lcsv2rdf.triples())
+
+
+def init_seda_scheme(cnx, title):
+    """Create a scheme to hold SEDA concepts with the given title.
+
+    Separated function to be monkey-patched if one need to customize the store (eg saem).
+    """
+    description = u'edition 2009' if title.startswith('SEDA :') else None
+    return cnx.create_entity('ConceptScheme', title=title, description=description)
+
+
+def get_store(cnx):
+    """Return the store to be used to import LCSV data files.
+
+    Separated function to be monkey-patched if one needs to customize the store (eg saem).
+    """
+    if cnx.repo.system_source.dbdriver == 'postgres':
+        from cubicweb.dataimport.massive_store import MassiveObjectStore
+        return MassiveObjectStore(cnx, eids_seq_range=1000)
+    else:
+        return NoHookRQLObjectStore(cnx)
+
+
+def import_seda_schemes(cnx, lcsv_import=lcsv_import):
+    """Import all LCSV data files defined in LCSV_FILES."""
+    orig_cwuri2eid = post321_import.cwuri2eid
+    try:
+        _import_seda_schemes(cnx, lcsv_import)
+    finally:
+        post321_import.cwuri2eid = orig_cwuri2eid
+
+
+def _import_seda_schemes(cnx, lcsv_import=lcsv_import):
+    """Import all LCSV data files defined in LCSV_FILES."""
+    feed_extid2eid_cache(cnx)
+    store = get_store(cnx)
+    for title, rtypes, etypes, fname in LCSV_FILES:
+        if not cnx.find('ConceptScheme', title=title):
+            print('importing', title.encode('utf-8'))
+            scheme = init_seda_scheme(cnx, title)
+            lcsv_import(cnx, store, fname, scheme.cwuri)
+            if not isinstance(rtypes, tuple):
+                rtypes = (rtypes,)
+            for rtype in rtypes:
+                rtype_e = cnx.find('CWRType', name=rtype).one()
+                scheme.cw_set(scheme_relation_type=rtype_e)
+            if not isinstance(etypes, tuple):
+                etypes = (etypes,)
+            for etype in etypes:
+                etype_e = cnx.find('CWEType', name=etype).one()
+                scheme.cw_set(scheme_entity_type=etype_e)
+            store.flush()
+    store.commit()
+    store.finish()
+    if not isinstance(store, NoHookRQLObjectStore):
+        # when using the massive store, we need explicit reindexation
+        reindex_entities(cnx.repo.schema, cnx, etypes=['Concept', 'ConceptScheme'])
+
+
+# hack to avoid recomputing extid2eid mapping for each lcsv file, this is costly with massive store
+# since index may have been removed
+from logilab.common.decorators import monkeypatch  # noqa
+from cubicweb.dataimport.importer import cwuri2eid as orig_cwuri2eid  # noqa
+from cubes.skos import post321_import  # noqa
+
+EXTID2EID_CACHE = None
+
+
+def feed_extid2eid_cache(cnx):
+    global EXTID2EID_CACHE
+    EXTID2EID_CACHE = orig_cwuri2eid(cnx, ('ConceptScheme', 'Label'))
+    # though concepts and external URIs may come from any source
+    EXTID2EID_CACHE.update(patched_cwuri2eid(cnx, ('Concept', 'ExternalUri')))
+
+
+def patched_cwuri2eid(cnx, etypes, source_eid=None):
+    return EXTID2EID_CACHE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/entities/__init__.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,194 @@
+# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+"""Custom logic (eg entities and adapters) for cubicweb-seda."""
+
+import json
+
+from logilab.common.registry import objectify_predicate
+
+from cubes.compound.entities import IContainer, IContained, IClonableAdapter
+
+from .. import seda_profile_container_def
+from ..xsd import XSDMMapping
+from ..xsd2yams import RULE_TYPES
+
+
+XSDM_MAPPING = XSDMMapping('ArchiveTransfer')
+
+
+def parent_and_container(entity):
+    """Attempt to return the direct parent and container from the entity, handling case where entity
+    is being created and container information will be found through linkto or parent form
+    (partially supported)
+    """
+    if entity.has_eid():
+        # entity is expected to be a contained entity, not the container itself
+        container = entity.cw_adapt_to('IContained').container
+        parent = entity.cw_adapt_to('IContained').parent
+        if container is None:
+            # entity may be both container and contained, and in this case is a container
+            assert entity.cw_adapt_to('IContainer'), entity
+            container = entity
+    else:
+        req = entity._cw
+        # but parent entity, retrieved through linkto, may be the container itself or a
+        # contained entity
+        try:
+            parent_eid = int(req.form['__linkto'].split(':')[1])
+        except KeyError:
+            # ajax created form
+            try:
+                parent_eid = int(json.loads(req.form['arg'][0]))
+            except (KeyError, ValueError):
+                if 'sedaContainerEID' in req.form:
+                    container = req.entity_from_eid(int(req.form['sedaContainerEID']))
+                    return None, container
+                if 'referenced_by' in req.form:
+                    entity = req.entity_from_eid(int(req.form['referenced_by']))
+                    container = entity.cw_adapt_to('IContained').container
+                    return None, container
+                # unable to get parent eid for now :(
+                return None, None
+        parent = req.entity_from_eid(parent_eid)
+        # handle IContained first: in case entity support both interface we want to go to the
+        # uppermost parent
+        icontained = parent.cw_adapt_to('IContained')
+        if icontained is not None and icontained.container:
+            container = icontained.container
+        elif parent.cw_adapt_to('IContainer'):
+            container = parent
+    return parent, container
+
+
+def _seda_container_from_context(rset, entity):
+    if entity is None:
+        entity = rset.one()
+    # protect against unrelated entity types
+    if not entity.cw_etype.startswith('SEDA'):
+        return None
+    if entity.cw_etype != 'SEDAArchiveTransfer':
+        entity = parent_and_container(entity)[1]
+    return entity
+
+
+@objectify_predicate
+def component_unit(cls, req, rset=None, entity=None, **kwargs):
+    """Predicate returning 1 score if context entity is within "component" archive unit (i.e.
+    container root is not a SEDAArchiveTransfer but a SEDAArchiveUnit).
+    """
+    entity = _seda_container_from_context(rset, entity)
+    return 1 if entity.cw_etype == 'SEDAArchiveUnit' else 0
+
+
+@objectify_predicate
+def simplified_profile(cls, req, rset=None, entity=None, **kwargs):
+    """Predicate returning 1 score if context entity is within a simplified profile."""
+    entity = _seda_container_from_context(rset, entity)
+    if entity is None:
+        return 0
+    if entity.cw_etype == 'SEDAArchiveUnit':
+        # XXX archive unit component, for now suppose it's "simplified"
+        return 1
+    return 1 if entity.simplified_profile else 0
+
+
+def is_full_seda2_profile(entity=None, rset=None):
+    """Return 1 if context entity is within a full seda2 profile, else 0."""
+    entity = _seda_container_from_context(rset, entity)
+    if entity is None:
+        return 1
+    if entity.cw_etype == 'SEDAArchiveUnit':
+        # XXX archive unit component, for now suppose it's "simplified"
+        return 0
+    return 0 if entity.simplified_profile else 1
+
+
+@objectify_predicate
+def full_seda2_profile(cls, req, rset=None, entity=None, **kwargs):
+    """Predicate returning 1 score if context entity is within a full seda2 profile."""
+    return is_full_seda2_profile(entity, rset)
+
+
+def rule_type_from_etype(etype):
+    """Return the rule type (e.g. 'access') from an etype enclosing the information
+    (e.g. 'SEDAAltAccessRulePreventInheritance', 'SEDASeqAaccessRuleRule' or 'SEDAAccessRule')
+    """
+    if etype.startswith('SEDAAlt'):
+        rule_type = etype[len('SEDAAlt'):-len('RulePreventInheritance')]
+    elif etype.startswith('SEDASeq'):
+        rule_type = etype[len('SEDASeq'):-len('RuleRule')]
+    else:
+        rule_type = etype[len('SEDA'):-len('Rule')]
+    rule_type = rule_type.lower()
+    assert rule_type in RULE_TYPES, 'unhandled etype {0}'.format(etype)
+    return rule_type
+
+
+class DirectLinkIContained(IContained):
+    """IContained implementation using a relation that link every contained entities to its parent
+    container.
+    """
+    @property
+    def container(self):
+        """Return the container to which this entity belongs, or None."""
+        container = self.entity.related('container', entities=True)
+        return container and container[0] or None
+
+
+class SEDAArchiveUnitIClonableAdapter(IClonableAdapter):
+    """Cloning adapter for SEDA components."""
+    rtype = 'clone_of'
+    skiprtypes = ()
+
+    def clone_into(self, clone):
+        """Recursivily clone the container graph of this entity into `clone`."""
+        if clone.seda_archive_unit and (
+                clone.seda_archive_unit[0].cw_etype == 'SEDAArchiveTransfer'
+                or clone.seda_archive_unit[0].container[0].cw_etype == 'SEDAArchiveTransfer'):
+            # clone is parented to a transfer profile, we need to propery handle binary/physical
+            # data objects
+            data_objects = self._cw.execute(
+                'Any X WHERE X is IN (SEDABinaryDataObject, SEDAPhysicalDataObject),'
+                ' X container %(c)s', {'c': self.entity.eid})
+        else:
+            data_objects = None
+        clones = super(SEDAArchiveUnitIClonableAdapter, self).clone_into(clone)
+        if data_objects is not None:
+            if clone.seda_archive_unit[0].cw_etype == 'SEDAArchiveTransfer':
+                transfer = clone.seda_archive_unit[0]
+            else:
+                transfer = clone.seda_archive_unit[0].container[0]
+            for data_object in data_objects.entities():
+                rtype = {
+                    'SEDABinaryDataObject': 'seda_binary_data_object',
+                    'SEDAPhysicalDataObject': 'seda_physical_data_object',
+                }[data_object.cw_etype]
+                clones[data_object].cw_set(**{rtype: transfer})
+
+
+def registration_callback(vreg):
+    vreg.register(SEDAArchiveUnitIClonableAdapter)
+    vreg.register(IContainer.build_class('SEDAArchiveTransfer'))
+    vreg.register(IContainer.build_class('SEDAArchiveUnit'))  # archive unit may also be a container
+    for etype, parent_relations in sorted(seda_profile_container_def(vreg.schema)):
+        cls = DirectLinkIContained.build_class(etype, parent_relations)
+        assert cls
+        vreg.register(cls)
+        if etype in ('SEDABinaryDataObject', 'SEDAPhysicalDataObject'):
+            # control parent relation order
+            cls.parent_relations = list(cls.parent_relations)
+            if cls.parent_relations[0][0] == 'seda_data_object_reference_id':
+                cls.parent_relations = cls.parent_relations[::-1]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/entities/custom.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,357 @@
+# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from . import generated
+
+
+def _extract_title(annotation):
+    """Return the first line in the annotation to use as a title"""
+    annotation = annotation.strip()
+    assert annotation
+    return annotation.splitlines()[0]
+
+
+class SEDAArchiveTransfer(generated.SEDAArchiveTransfer):
+
+    def dc_title(self):
+        return self.title
+
+    @property
+    def formats_compat(self):
+        if self.compat_list is None:
+            return set()
+        return set(self.compat_list.split(', '))
+
+    @property
+    def archival_agreement(self):
+        if self.reverse_seda_archival_agreement:
+            return self.reverse_seda_archival_agreement[0]
+        return None
+
+    @property
+    def comments(self):
+        return self.reverse_seda_comment
+
+    @property
+    def archive_units(self):
+        return self.reverse_seda_archive_unit
+
+    @property
+    def physical_data_objects(self):
+        return self.reverse_seda_physical_data_object
+
+    @property
+    def binary_data_objects(self):
+        return self.reverse_seda_binary_data_object
+
+
+class SEDAArchiveUnit(generated.SEDAArchiveUnit):
+
+    def dc_title(self):
+        return _extract_title(self.user_annotation)
+
+    @property
+    def is_archive_unit_ref(self):
+        """Return true if this is a 'reference' archive unit, else false for 'description' archive
+        unit.
+        """
+        return not bool(self.first_level_choice.content_sequence)
+
+    @property
+    def first_level_choice(self):
+        """Return the choice element of an archive unit (SEDAAltArchiveUnitArchiveUnitRefId),
+        holding either a reference or descriptive content
+        """
+        return self.related('seda_alt_archive_unit_archive_unit_ref_id', 'subject').one()
+
+
+class SEDABinaryDataObject(generated.SEDABinaryDataObject):
+
+    def dc_title(self):
+        return _extract_title(self.user_annotation)
+
+    @property
+    def format_id(self):
+        return self.reverse_seda_format_id_from[0] if self.reverse_seda_format_id_from else None
+
+    @property
+    def encoding(self):
+        return self.reverse_seda_encoding_from[0] if self.reverse_seda_encoding_from else None
+
+    @property
+    def mime_type(self):
+        return self.reverse_seda_mime_type_from[0] if self.reverse_seda_mime_type_from else None
+
+    @property
+    def date_created_by_application(self):
+        if self.reverse_seda_date_created_by_application:
+            return self.reverse_seda_date_created_by_application[0]
+        return None
+
+    @property
+    def referenced_by(self):
+        """Return an iterator on archive unit's content sequences referencing this data-object."""
+        for ref in self.reverse_seda_data_object_reference_id:
+            for seq in ref.seda_data_object_reference:
+                yield seq
+
+
+class SEDAPhysicalDataObject(generated.SEDAPhysicalDataObject):
+
+    def dc_title(self):
+        return _extract_title(self.user_annotation)
+
+
+class SEDAAltArchiveUnitArchiveUnitRefId(generated.SEDAAltArchiveUnitArchiveUnitRefId):
+
+    @property
+    def reference(self):
+        """Return the reference element for an archive unit which has no content"""
+        rset = self.related('seda_archive_unit_ref_id_from', 'object')
+        if rset:
+            return rset.one()
+        return None
+
+    @property
+    def content_sequence(self):
+        """Return the sequence element holding content for an archive unit which is not a reference
+        """
+        rset = self.related('seda_seq_alt_archive_unit_archive_unit_ref_id_management', 'subject')
+        if rset:
+            return rset.one()
+        return None
+
+
+class SEDASeqAltArchiveUnitArchiveUnitRefIdManagement(
+        generated.SEDASeqAltArchiveUnitArchiveUnitRefIdManagement):
+
+    @property
+    def title(self):
+        return self.reverse_seda_title[0]
+
+    @property
+    def keywords(self):
+        return self.reverse_seda_keyword
+
+    @property
+    def custodial_history_items(self):
+        return self.reverse_seda_custodial_history_item
+
+    @property
+    def type(self):
+        return self.reverse_seda_type_from[0] if self.reverse_seda_type_from else None
+
+    @property
+    def language(self):
+        return self.reverse_seda_language_from[0] if self.reverse_seda_language_from else None
+
+    @property
+    def description_level_concept(self):
+        return self.seda_description_level[0] if self.seda_description_level else None
+
+    @property
+    def description(self):
+        return self.reverse_seda_description[0] if self.reverse_seda_description else None
+
+    @property
+    def start_date(self):
+        return self.reverse_seda_start_date[0] if self.reverse_seda_start_date else None
+
+    @property
+    def end_date(self):
+        return self.reverse_seda_end_date[0] if self.reverse_seda_end_date else None
+
+    @property
+    def system_id(self):
+        return self.reverse_seda_system_id[0] if self.reverse_seda_system_id else None
+
+    @property
+    def transferring_agency_archive_unit_identifier(self):
+        if self.reverse_seda_transferring_agency_archive_unit_identifier:
+            return self.reverse_seda_transferring_agency_archive_unit_identifier[0]
+        return None
+
+    @property
+    def originating_agency(self):
+        if self.reverse_seda_originating_agency_from:
+            return self.reverse_seda_originating_agency_from[0]
+        return None
+
+
+class SEDAKeyword(generated.SEDAKeyword):
+
+    @property
+    def reference(self):
+        return (self.reverse_seda_keyword_reference_from[0]
+                if self.reverse_seda_keyword_reference_from else None)
+
+    @property
+    def type(self):
+        return (self.reverse_seda_keyword_type_from[0]
+                if self.reverse_seda_keyword_type_from else None)
+
+
+class SEDAKeywordReference(generated.SEDAKeywordReference):
+
+    @property
+    def scheme(self):
+        return (self.seda_keyword_reference_to_scheme[0] if self.seda_keyword_reference_to_scheme
+                else None)
+
+    @property
+    def concept(self):
+        return self.seda_keyword_reference_to[0] if self.seda_keyword_reference_to else None
+
+
+class SEDAKeywordType(generated.SEDAKeywordType):
+
+    @property
+    def concept(self):
+        return self.seda_keyword_type_to[0] if self.seda_keyword_type_to else None
+
+
+class RuleMixIn(object):
+
+    @property
+    def _rule_type(self):
+        return self.cw_etype[4:-4].lower()
+
+    @property
+    def rules(self):
+        return getattr(self, 'seda_seq_{0}_rule_rule'.format(self._rule_type))
+
+    @property
+    def inheritance_control(self):
+        alt = getattr(self, 'seda_alt_{0}_rule_prevent_inheritance'.format(self._rule_type))
+        return alt[0] if alt else None
+
+
+class SEDAAccessRule(RuleMixIn, generated.SEDAAccessRule):
+    pass
+
+
+class SEDAAppraisalRule(RuleMixIn, generated.SEDAAppraisalRule):
+
+    @property
+    def final_action_concept(self):
+        if self.seda_final_action:
+            return self.seda_final_action[0]
+        return None
+
+
+class SEDAClassificationRule(RuleMixIn, generated.SEDAClassificationRule):
+    pass
+
+
+class SEDADisseminationRule(RuleMixIn, generated.SEDADisseminationRule):
+    pass
+
+
+class SEDAReuseRule(RuleMixIn, generated.SEDAReuseRule):
+    pass
+
+
+class SEDAStorageRule(RuleMixIn, generated.SEDAStorageRule):
+
+    @property
+    def final_action_concept(self):
+        if self.seda_final_action:
+            return self.seda_final_action[0]
+        return None
+
+
+class RuleRuleMixIn(object):
+
+    @property
+    def start_date(self):
+        return self.reverse_seda_start_date[0] if self.reverse_seda_start_date else None
+
+    @property
+    def rule_concept(self):
+        return self.seda_rule[0] if self.seda_rule else None
+
+
+class SEDASeqAccessRuleRule(RuleRuleMixIn, generated.SEDASeqAccessRuleRule):
+    pass
+
+
+class SEDASeqAppraisalRuleRule(RuleRuleMixIn, generated.SEDASeqAppraisalRuleRule):
+    pass
+
+
+class SEDASeqClassificationRuleRule(RuleRuleMixIn, generated.SEDASeqClassificationRuleRule):
+    pass
+
+
+class SEDASeqDisseminationRuleRule(RuleRuleMixIn, generated.SEDASeqDisseminationRuleRule):
+    pass
+
+
+class SEDASeqReuseRuleRule(RuleRuleMixIn, generated.SEDASeqReuseRuleRule):
+    pass
+
+
+class SEDASeqStorageRuleRule(RuleRuleMixIn, generated.SEDASeqStorageRuleRule):
+    pass
+
+
+class SEDAFormatId(generated.SEDAFormatId):
+
+    @property
+    def concept(self):
+        return self.seda_format_id_to[0] if self.seda_format_id_to else None
+
+
+class SEDAEncoding(generated.SEDAEncoding):
+
+    @property
+    def concept(self):
+        return self.seda_encoding_to[0] if self.seda_encoding_to else None
+
+
+class SEDAMimeType(generated.SEDAMimeType):
+
+    @property
+    def concept(self):
+        return self.seda_mime_type_to[0] if self.seda_mime_type_to else None
+
+
+class SEDAType(generated.SEDAType):
+
+    @property
+    def concept(self):
+        return self.seda_type_to[0] if self.seda_type_to else None
+
+
+class SEDALanguage(generated.SEDALanguage):
+
+    @property
+    def concept(self):
+        return self.seda_language_to[0] if self.seda_language_to else None
+
+
+class SEDAOriginatingAgency(generated.SEDAOriginatingAgency):
+
+    @property
+    def agency(self):
+        return self.seda_originating_agency_to[0] if self.seda_originating_agency_to else None
+
+
+class SEDACustodialHistoryItem(generated.SEDACustodialHistoryItem):
+
+    @property
+    def when(self):
+        return self.reverse_seda_when[0] if self.reverse_seda_when else None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/entities/diag.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,229 @@
+# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-seda tools to diagnose versions compatibility of profiles."""
+
+from collections import namedtuple
+
+from cubicweb import _
+from cubicweb.view import EntityAdapter
+from cubicweb.predicates import is_instance
+
+
+ALL_FORMATS = frozenset(('SEDA 2.0', 'SEDA 1.0', 'SEDA 0.2', 'simplified'))
+
+Rule = namedtuple('Rule', ['impacted_formats', 'message', 'tab_id', 'watch'])
+RULES = {
+    'use_archive_unit_ref': Rule(
+        set(['SEDA 1.0', 'SEDA 0.2', 'simplified']),
+        _("Archive unit reference are only supported by SEDA 2."),
+        'main_tab',
+        set([
+            'seda_seq_access_rule_rule',
+            'seda_seq_appraisal_rule_rule',
+        ])),
+
+    'seda1_need_access_rule': Rule(
+        set(['SEDA 1.0']),
+        _("First level archive unit must have an associated access rule to be exportable "
+          "in SEDA 1. You may define it on the archive unit or as a default rule on the "
+          "transfer."),
+        'seda_management_tab',
+        set([
+            'seda_archive_unit',
+            'seda_alt_archive_unit_archive_unit_ref_id',
+            'seda_seq_alt_archive_unit_archive_unit_ref_id_management',
+            'seda_access_rule',
+        ])),
+    'rule_without_rule': Rule(
+        set(['SEDA 1.0', 'SEDA 0.2', 'simplified']),
+        _("Some management rule has no inner rule, one is required."),
+        'seda_management_tab',
+        set([
+            'seda_seq_access_rule_rule',
+            'seda_seq_appraisal_rule_rule',
+        ])),
+    'rule_with_too_much_rules': Rule(
+        set(['SEDA 1.0', 'SEDA 0.2', 'simplified']),
+        _("Some management rule has more than one inner rules, a single one is required."),
+        'seda_management_tab',
+        set([
+            'seda_seq_access_rule_rule',
+            'seda_seq_appraisal_rule_rule',
+        ])),
+    'rule_unsupported_card': Rule(
+        set(['SEDA 1.0', 'SEDA 0.2', 'simplified']),
+        _("Inner rule has cardinality other than 1."),
+        'seda_management_tab',
+        set([
+            ('SEDASeqAccessRuleRule', 'user_cardinality'),
+            ('SEDASeqAppraisalRuleRule', 'user_cardinality'),
+        ])),
+    'rule_need_start_date': Rule(
+        set(['SEDA 1.0', 'SEDA 0.2', 'simplified']),
+        _("Inner rule has no start date."),
+        'seda_management_tab',
+        set([
+            'seda_start_date',
+        ])),
+    'rule_start_unsupported_card': Rule(
+        set(['SEDA 1.0', 'SEDA 0.2', 'simplified']),
+        _("Start date has cardinality other than 1."),
+        'seda_management_tab',
+        set([
+            ('SEDAStartDate', 'user_cardinality'),
+        ])),
+    'rule_ref_non_rule_id': Rule(
+        set(['SEDA 1.0', 'SEDA 0.2', 'simplified']),
+        _("Rule has an explicit rule deactivation - only ignore all rules is supported in "
+          "simplified profiles."),
+        'seda_management_tab',
+        set([
+            'seda_ref_non_rule_id_from',
+        ])),
+
+    'seda02_custodial_history_items': Rule(
+        set(['SEDA 0.2']),
+        _("Custodial history is text with SEDA 0.2, hence only one item element is considered."),
+        'seda_history_tab',
+        set([
+            'seda_custodial_history_item',
+        ])),
+    'seda02_custodial_history_when': Rule(
+        set(['SEDA 0.2']),
+        _("Custodial history is text with SEDA 0.2, hence date information isn't considered."),
+        'seda_history_tab',
+        set([
+            'seda_when',
+        ])),
+}
+
+
+class CompatError(namedtuple('_CompatError', ['impacted_formats', 'message', 'tab_id', 'entity'])):
+    """Convenience class holding information about a problem in a profile forbidding export to some
+    format:
+
+    * `impacted_formats`: set of formats that are no more available because of this error,
+    * `message`: message string explaining the problem,
+    * `entity`: 1st class entity where the error lies (one of archive unit, data object, etc.),
+    * `tab`: entity's tab where the problem may be fixed.
+    """
+    def __new__(cls, rule_id, entity):
+        rule = RULES[rule_id]
+        return super(CompatError, cls).__new__(cls, rule.impacted_formats, rule.message,
+                                               rule.tab_id, entity)
+
+
+class ISEDACompatAnalyzer(EntityAdapter):
+    """Adapter that will analyze profile to diagnose its format compatibility (SEDA 2, SEDA 1 and/or
+    SEDA 0.2) while being to explain the problem to end-users.
+    """
+
+    __regid__ = 'ISEDACompatAnalyzer'
+    __select__ = is_instance('SEDAArchiveTransfer')
+
+    def diagnose(self):
+        possible_formats = set(ALL_FORMATS)
+        for compat_error in self.detect_problems():
+            possible_formats -= compat_error.impacted_formats
+        return possible_formats
+
+    def detect_problems(self):
+        """Yield :class:`CompatError` describing a problem that prevents the given profile to be
+        compatible with some format.
+        """
+        for rule_id, entity in self.failing_rules():
+            yield CompatError(rule_id, entity)
+
+    def failing_rules(self):
+        """Yield (rule identifier, problematic entity) describing a problem that prevents the given
+        profile to be compatible with some format.
+        """
+        for check_method in (self._check_usage_of_reference_archive_unit,
+                             self._check_management_rules,
+                             self._check_custodial_history_rules):
+            for problem in check_method():
+                yield problem
+
+    def _check_management_rules(self):  # noqa (too complex)
+        profile = self.entity
+        # First level archive unit needs an access rule (SEDA 1)
+        if not profile.reverse_seda_access_rule:
+            for archive_unit in profile.archive_units:
+                seq = archive_unit.first_level_choice.content_sequence
+                if seq is None:
+                    # reference archive unit
+                    continue
+                if not seq.reverse_seda_access_rule:
+                    yield 'seda1_need_access_rule', archive_unit
+        # Access/appraisal rule must have one and only one sequence, which have one and only one
+        # start date (and both of them must have 1 cardinality)
+        for rule in self._cw.execute(
+                'Any X WHERE X is IN (SEDAAccessRule, SEDAAppraisalRule), '
+                'X container C, C eid %(c)s', {'c': profile.eid}).entities():
+            if not rule.rules:
+                yield 'rule_without_rule', _parent(rule)
+            elif len(rule.rules) > 1:
+                yield 'rule_with_too_much_rules', _parent(rule)
+            else:
+                rule_seq = rule.rules[0]
+                if rule_seq.user_cardinality != '1':
+                    yield 'rule_unsupported_card', _parent(rule)
+                if not rule_seq.start_date:
+                    yield 'rule_need_start_date', _parent(rule)
+                elif rule_seq.start_date.user_cardinality != '1':
+                    yield 'rule_start_unsupported_card', _parent(rule)
+        # Access/appraisal rule shouldn't use ref non rule id
+        for rule_type in ('access', 'appraisal'):
+            for rule in self._cw.execute(
+                    'DISTINCT Any X WHERE X seda_alt_{0}_rule_prevent_inheritance ALT,'
+                    'REF seda_ref_non_rule_id_from ALT, '
+                    'X container C, C eid %(c)s'.format(rule_type),
+                    {'c': profile.eid}).entities():
+                yield 'rule_ref_non_rule_id', _parent(rule)
+
+    def _check_custodial_history_rules(self):
+        profile = self.entity
+        # Check for more than one custodial history items (SEDA 0.2)
+        for content in self._cw.execute(
+                'Any CONT GROUPBY CONT WHERE X seda_custodial_history_item CONT,'
+                'X container C, C eid %(c)s HAVING COUNT(X) > 1',
+                {'c': profile.eid}).entities():
+            parent = _parent(content) if profile.simplified_profile else content
+            yield 'seda02_custodial_history_items', parent
+        # Check for SEDAwhen usage (SEDA 0.2)
+        for content in self._cw.execute(
+                'DISTINCT Any CONT WHERE X seda_custodial_history_item CONT,'
+                'W seda_when X, X container C, C eid %(c)s',
+                {'c': profile.eid}).entities():
+            parent = _parent(content) if profile.simplified_profile else content
+            yield 'seda02_custodial_history_when', parent
+
+    def _check_usage_of_reference_archive_unit(self):
+        for archive_unit in self._cw.execute(
+                'DISTINCT Any X WHERE X is SEDAArchiveUnit,'
+                'X seda_alt_archive_unit_archive_unit_ref_id ALT, '
+                'NOT EXISTS(ALT seda_seq_alt_archive_unit_archive_unit_ref_id_management SEQ),'
+                'X container C, C eid %(c)s', {'c': self.entity.eid}).entities():
+            yield 'use_archive_unit_ref', archive_unit
+
+    # XXX more than one items ->
+
+
+def _parent(entity):
+    """Return the first encountered parent which is an ArchiveUnit"""
+    while entity.cw_etype not in ('SEDAArchiveTransfer', 'SEDAArchiveUnit'):
+        entity = entity.cw_adapt_to('IContained').parent
+    return entity
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/entities/generated.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,897 @@
+# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+"""THIS FILE IS GENERATED FROM SEDA 2.0 XSD FILES, DO NOT EDIT"""
+
+from cubicweb.entities import AnyEntity, fetch_config
+
+
+class SEDAAnyEntity(AnyEntity):
+    __abstract__ = True
+    value_attr = None
+
+    def dc_title(self):
+        if self.value_attr is None:
+            return self.dc_type()
+        return self.printable_value(self.value_attr)
+
+
+class SEDAArchiveTransfer(SEDAAnyEntity):
+    __regid__ = 'SEDAArchiveTransfer'
+    fetch_attrs, cw_fetch_order = fetch_config(['title', 'user_annotation'])
+    value_attr = None
+
+class SEDAComment(SEDAAnyEntity):
+    __regid__ = 'SEDAComment'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'comment', 'user_annotation'])
+    value_attr = 'comment'
+
+class SEDASignature(SEDAAnyEntity):
+    __regid__ = 'SEDASignature'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAArchivalAgreement(SEDAAnyEntity):
+    __regid__ = 'SEDAArchivalAgreement'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'archival_agreement', 'user_annotation'])
+    value_attr = 'archival_agreement'
+
+class SEDARelatedTransferReference(SEDAAnyEntity):
+    __regid__ = 'SEDARelatedTransferReference'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDATransferRequestReplyIdentifier(SEDAAnyEntity):
+    __regid__ = 'SEDATransferRequestReplyIdentifier'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAMimeTypeCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDAMimeTypeCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAEncodingCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDAEncodingCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDACompressionAlgorithmCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDACompressionAlgorithmCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDADataObjectVersionCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDADataObjectVersionCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAStorageRuleCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDAStorageRuleCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAppraisalRuleCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDAAppraisalRuleCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAccessRuleCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDAAccessRuleCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDADisseminationRuleCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDADisseminationRuleCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAReuseRuleCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDAReuseRuleCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAClassificationRuleCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDAClassificationRuleCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDARelationshipCodeListVersion(SEDAAnyEntity):
+    __regid__ = 'SEDARelationshipCodeListVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDABinaryDataObject(SEDAAnyEntity):
+    __regid__ = 'SEDABinaryDataObject'
+    fetch_attrs, cw_fetch_order = fetch_config(['filename', 'user_cardinality', 'user_annotation'])
+    value_attr = None
+
+class SEDAPhysicalDataObject(SEDAAnyEntity):
+    __regid__ = 'SEDAPhysicalDataObject'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'user_annotation'])
+    value_attr = None
+
+class SEDARelationship(SEDAAnyEntity):
+    __regid__ = 'SEDARelationship'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDADataObjectVersion(SEDAAnyEntity):
+    __regid__ = 'SEDADataObjectVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAltBinaryDataObjectAttachment(SEDAAnyEntity):
+    __regid__ = 'SEDAAltBinaryDataObjectAttachment'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDACompressed(SEDAAnyEntity):
+    __regid__ = 'SEDACompressed'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'compressed', 'user_annotation'])
+    value_attr = 'compressed'
+
+class SEDAArchiveUnit(SEDAAnyEntity):
+    __regid__ = 'SEDAArchiveUnit'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'user_annotation'])
+    value_attr = None
+
+class SEDAServiceLevel(SEDAAnyEntity):
+    __regid__ = 'SEDAServiceLevel'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'service_level', 'user_annotation'])
+    value_attr = 'service_level'
+
+class SEDAStorageRule(SEDAAnyEntity):
+    __regid__ = 'SEDAStorageRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAppraisalRule(SEDAAnyEntity):
+    __regid__ = 'SEDAAppraisalRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAccessRule(SEDAAnyEntity):
+    __regid__ = 'SEDAAccessRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDADisseminationRule(SEDAAnyEntity):
+    __regid__ = 'SEDADisseminationRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAReuseRule(SEDAAnyEntity):
+    __regid__ = 'SEDAReuseRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAClassificationRule(SEDAAnyEntity):
+    __regid__ = 'SEDAClassificationRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'classification_owner', 'user_annotation'])
+    value_attr = 'classification_owner'
+
+class SEDANeedAuthorization(SEDAAnyEntity):
+    __regid__ = 'SEDANeedAuthorization'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'need_authorization', 'user_annotation'])
+    value_attr = 'need_authorization'
+
+class SEDAAltArchiveUnitArchiveUnitRefId(SEDAAnyEntity):
+    __regid__ = 'SEDAAltArchiveUnitArchiveUnitRefId'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDAAttachment(SEDAAnyEntity):
+    __regid__ = 'SEDAAttachment'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDAUri(SEDAAnyEntity):
+    __regid__ = 'SEDAUri'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDAFormatLitteral(SEDAAnyEntity):
+    __regid__ = 'SEDAFormatLitteral'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'format_litteral', 'user_annotation'])
+    value_attr = 'format_litteral'
+
+class SEDAMimeType(SEDAAnyEntity):
+    __regid__ = 'SEDAMimeType'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAFormatId(SEDAAnyEntity):
+    __regid__ = 'SEDAFormatId'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAEncoding(SEDAAnyEntity):
+    __regid__ = 'SEDAEncoding'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDACreatingApplicationName(SEDAAnyEntity):
+    __regid__ = 'SEDACreatingApplicationName'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'creating_application_name', 'user_annotation'])
+    value_attr = 'creating_application_name'
+
+class SEDACreatingApplicationVersion(SEDAAnyEntity):
+    __regid__ = 'SEDACreatingApplicationVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'creating_application_version', 'user_annotation'])
+    value_attr = 'creating_application_version'
+
+class SEDADateCreatedByApplication(SEDAAnyEntity):
+    __regid__ = 'SEDADateCreatedByApplication'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDACreatingOs(SEDAAnyEntity):
+    __regid__ = 'SEDACreatingOs'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'creating_os', 'user_annotation'])
+    value_attr = 'creating_os'
+
+class SEDACreatingOsVersion(SEDAAnyEntity):
+    __regid__ = 'SEDACreatingOsVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'creating_os_version', 'user_annotation'])
+    value_attr = 'creating_os_version'
+
+class SEDALastModified(SEDAAnyEntity):
+    __regid__ = 'SEDALastModified'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAWidth(SEDAAnyEntity):
+    __regid__ = 'SEDAWidth'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAHeight(SEDAAnyEntity):
+    __regid__ = 'SEDAHeight'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDADepth(SEDAAnyEntity):
+    __regid__ = 'SEDADepth'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAShape(SEDAAnyEntity):
+    __regid__ = 'SEDAShape'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDADiameter(SEDAAnyEntity):
+    __regid__ = 'SEDADiameter'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDALength(SEDAAnyEntity):
+    __regid__ = 'SEDALength'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAThickness(SEDAAnyEntity):
+    __regid__ = 'SEDAThickness'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAWeight(SEDAAnyEntity):
+    __regid__ = 'SEDAWeight'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDANumberOfPage(SEDAAnyEntity):
+    __regid__ = 'SEDANumberOfPage'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAArchiveUnitRefId(SEDAAnyEntity):
+    __regid__ = 'SEDAArchiveUnitRefId'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDASeqAltArchiveUnitArchiveUnitRefIdManagement(SEDAAnyEntity):
+    __regid__ = 'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDASeqStorageRuleRule(SEDAAnyEntity):
+    __regid__ = 'SEDASeqStorageRuleRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAltStorageRulePreventInheritance(SEDAAnyEntity):
+    __regid__ = 'SEDAAltStorageRulePreventInheritance'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDASeqAppraisalRuleRule(SEDAAnyEntity):
+    __regid__ = 'SEDASeqAppraisalRuleRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAltAppraisalRulePreventInheritance(SEDAAnyEntity):
+    __regid__ = 'SEDAAltAppraisalRulePreventInheritance'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDASeqAccessRuleRule(SEDAAnyEntity):
+    __regid__ = 'SEDASeqAccessRuleRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAltAccessRulePreventInheritance(SEDAAnyEntity):
+    __regid__ = 'SEDAAltAccessRulePreventInheritance'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDASeqDisseminationRuleRule(SEDAAnyEntity):
+    __regid__ = 'SEDASeqDisseminationRuleRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAltDisseminationRulePreventInheritance(SEDAAnyEntity):
+    __regid__ = 'SEDAAltDisseminationRulePreventInheritance'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDASeqReuseRuleRule(SEDAAnyEntity):
+    __regid__ = 'SEDASeqReuseRuleRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAltReuseRulePreventInheritance(SEDAAnyEntity):
+    __regid__ = 'SEDAAltReuseRulePreventInheritance'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDASeqClassificationRuleRule(SEDAAnyEntity):
+    __regid__ = 'SEDASeqClassificationRuleRule'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAltClassificationRulePreventInheritance(SEDAAnyEntity):
+    __regid__ = 'SEDAAltClassificationRulePreventInheritance'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAClassificationReassessingDate(SEDAAnyEntity):
+    __regid__ = 'SEDAClassificationReassessingDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDANeedReassessingAuthorization(SEDAAnyEntity):
+    __regid__ = 'SEDANeedReassessingAuthorization'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'need_reassessing_authorization', 'user_annotation'])
+    value_attr = 'need_reassessing_authorization'
+
+class SEDADataObjectReference(SEDAAnyEntity):
+    __regid__ = 'SEDADataObjectReference'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDArestrictionRuleIdRef(SEDAAnyEntity):
+    __regid__ = 'SEDArestrictionRuleIdRef'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'restriction_rule_id_ref', 'user_annotation'])
+    value_attr = 'restriction_rule_id_ref'
+
+class SEDArestrictionValue(SEDAAnyEntity):
+    __regid__ = 'SEDArestrictionValue'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'restriction_value', 'user_annotation'])
+    value_attr = 'restriction_value'
+
+class SEDArestrictionEndDate(SEDAAnyEntity):
+    __regid__ = 'SEDArestrictionEndDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDATitle(SEDAAnyEntity):
+    __regid__ = 'SEDATitle'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'title', 'user_annotation'])
+    value_attr = 'title'
+
+class SEDAFilePlanPosition(SEDAAnyEntity):
+    __regid__ = 'SEDAFilePlanPosition'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'file_plan_position', 'user_annotation'])
+    value_attr = 'file_plan_position'
+
+class SEDASystemId(SEDAAnyEntity):
+    __regid__ = 'SEDASystemId'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAOriginatingSystemId(SEDAAnyEntity):
+    __regid__ = 'SEDAOriginatingSystemId'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAArchivalAgencyArchiveUnitIdentifier(SEDAAnyEntity):
+    __regid__ = 'SEDAArchivalAgencyArchiveUnitIdentifier'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'archival_agency_archive_unit_identifier', 'user_annotation'])
+    value_attr = 'archival_agency_archive_unit_identifier'
+
+class SEDAOriginatingAgencyArchiveUnitIdentifier(SEDAAnyEntity):
+    __regid__ = 'SEDAOriginatingAgencyArchiveUnitIdentifier'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'originating_agency_archive_unit_identifier', 'user_annotation'])
+    value_attr = 'originating_agency_archive_unit_identifier'
+
+class SEDATransferringAgencyArchiveUnitIdentifier(SEDAAnyEntity):
+    __regid__ = 'SEDATransferringAgencyArchiveUnitIdentifier'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'transferring_agency_archive_unit_identifier', 'user_annotation'])
+    value_attr = 'transferring_agency_archive_unit_identifier'
+
+class SEDADescription(SEDAAnyEntity):
+    __regid__ = 'SEDADescription'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'description', 'user_annotation'])
+    value_attr = 'description'
+
+class SEDAType(SEDAAnyEntity):
+    __regid__ = 'SEDAType'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDADocumentType(SEDAAnyEntity):
+    __regid__ = 'SEDADocumentType'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'document_type', 'user_annotation'])
+    value_attr = 'document_type'
+
+class SEDALanguage(SEDAAnyEntity):
+    __regid__ = 'SEDALanguage'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDADescriptionLanguage(SEDAAnyEntity):
+    __regid__ = 'SEDADescriptionLanguage'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAStatus(SEDAAnyEntity):
+    __regid__ = 'SEDAStatus'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'status', 'user_annotation'])
+    value_attr = 'status'
+
+class SEDAVersion(SEDAAnyEntity):
+    __regid__ = 'SEDAVersion'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'version', 'user_annotation'])
+    value_attr = 'version'
+
+class SEDATag(SEDAAnyEntity):
+    __regid__ = 'SEDATag'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'tag', 'user_annotation'])
+    value_attr = 'tag'
+
+class SEDAKeyword(SEDAAnyEntity):
+    __regid__ = 'SEDAKeyword'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'keyword_content', 'user_annotation'])
+    value_attr = 'keyword_content'
+
+class SEDAOriginatingAgency(SEDAAnyEntity):
+    __regid__ = 'SEDAOriginatingAgency'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDASubmissionAgency(SEDAAnyEntity):
+    __regid__ = 'SEDASubmissionAgency'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAuthorizedAgent(SEDAAnyEntity):
+    __regid__ = 'SEDAAuthorizedAgent'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAWriter(SEDAAnyEntity):
+    __regid__ = 'SEDAWriter'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAddressee(SEDAAnyEntity):
+    __regid__ = 'SEDAAddressee'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDARecipient(SEDAAnyEntity):
+    __regid__ = 'SEDARecipient'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDASource(SEDAAnyEntity):
+    __regid__ = 'SEDASource'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'source', 'user_annotation'])
+    value_attr = 'source'
+
+class SEDACreatedDate(SEDAAnyEntity):
+    __regid__ = 'SEDACreatedDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDATransactedDate(SEDAAnyEntity):
+    __regid__ = 'SEDATransactedDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAcquiredDate(SEDAAnyEntity):
+    __regid__ = 'SEDAAcquiredDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDASentDate(SEDAAnyEntity):
+    __regid__ = 'SEDASentDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAReceivedDate(SEDAAnyEntity):
+    __regid__ = 'SEDAReceivedDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDARegisteredDate(SEDAAnyEntity):
+    __regid__ = 'SEDARegisteredDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAStartDate(SEDAAnyEntity):
+    __regid__ = 'SEDAStartDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAEndDate(SEDAAnyEntity):
+    __regid__ = 'SEDAEndDate'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAEvent(SEDAAnyEntity):
+    __regid__ = 'SEDAEvent'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAPreventInheritance(SEDAAnyEntity):
+    __regid__ = 'SEDAPreventInheritance'
+    fetch_attrs, cw_fetch_order = fetch_config(['prevent_inheritance'])
+    value_attr = 'prevent_inheritance'
+
+class SEDARefNonRuleId(SEDAAnyEntity):
+    __regid__ = 'SEDARefNonRuleId'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDACustodialHistoryItem(SEDAAnyEntity):
+    __regid__ = 'SEDACustodialHistoryItem'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'custodial_history_item', 'user_annotation'])
+    value_attr = 'custodial_history_item'
+
+class SEDACustodialHistoryFile(SEDAAnyEntity):
+    __regid__ = 'SEDACustodialHistoryFile'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAKeywordReference(SEDAAnyEntity):
+    __regid__ = 'SEDAKeywordReference'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAKeywordType(SEDAAnyEntity):
+    __regid__ = 'SEDAKeywordType'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDASpatial(SEDAAnyEntity):
+    __regid__ = 'SEDASpatial'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'spatial', 'user_annotation'])
+    value_attr = 'spatial'
+
+class SEDATemporal(SEDAAnyEntity):
+    __regid__ = 'SEDATemporal'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'temporal', 'user_annotation'])
+    value_attr = 'temporal'
+
+class SEDAJuridictional(SEDAAnyEntity):
+    __regid__ = 'SEDAJuridictional'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality', 'juridictional', 'user_annotation'])
+    value_attr = 'juridictional'
+
+class SEDAIsVersionOf(SEDAAnyEntity):
+    __regid__ = 'SEDAIsVersionOf'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAReplaces(SEDAAnyEntity):
+    __regid__ = 'SEDAReplaces'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDARequires(SEDAAnyEntity):
+    __regid__ = 'SEDARequires'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAIsPartOf(SEDAAnyEntity):
+    __regid__ = 'SEDAIsPartOf'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAReferences(SEDAAnyEntity):
+    __regid__ = 'SEDAReferences'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAEventIdentifier(SEDAAnyEntity):
+    __regid__ = 'SEDAEventIdentifier'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAEventType(SEDAAnyEntity):
+    __regid__ = 'SEDAEventType'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAEventDetail(SEDAAnyEntity):
+    __regid__ = 'SEDAEventDetail'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAGpsVersionID(SEDAAnyEntity):
+    __regid__ = 'SEDAGpsVersionID'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAGpsAltitude(SEDAAnyEntity):
+    __regid__ = 'SEDAGpsAltitude'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAGpsAltitudeRef(SEDAAnyEntity):
+    __regid__ = 'SEDAGpsAltitudeRef'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAGpsLatitude(SEDAAnyEntity):
+    __regid__ = 'SEDAGpsLatitude'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAGpsLatitudeRef(SEDAAnyEntity):
+    __regid__ = 'SEDAGpsLatitudeRef'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAGpsLongitude(SEDAAnyEntity):
+    __regid__ = 'SEDAGpsLongitude'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAGpsLongitudeRef(SEDAAnyEntity):
+    __regid__ = 'SEDAGpsLongitudeRef'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAGpsDateStamp(SEDAAnyEntity):
+    __regid__ = 'SEDAGpsDateStamp'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAwhen(SEDAAnyEntity):
+    __regid__ = 'SEDAwhen'
+    fetch_attrs, cw_fetch_order = fetch_config(['user_cardinality'])
+    value_attr = None
+
+class SEDAAltIsVersionOfArchiveUnitRefId(SEDAAnyEntity):
+    __regid__ = 'SEDAAltIsVersionOfArchiveUnitRefId'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDAAltReplacesArchiveUnitRefId(SEDAAnyEntity):
+    __regid__ = 'SEDAAltReplacesArchiveUnitRefId'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDAAltRequiresArchiveUnitRefId(SEDAAnyEntity):
+    __regid__ = 'SEDAAltRequiresArchiveUnitRefId'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDAAltIsPartOfArchiveUnitRefId(SEDAAnyEntity):
+    __regid__ = 'SEDAAltIsPartOfArchiveUnitRefId'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDAAltReferencesArchiveUnitRefId(SEDAAnyEntity):
+    __regid__ = 'SEDAAltReferencesArchiveUnitRefId'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDARepositoryArchiveUnitPID(SEDAAnyEntity):
+    __regid__ = 'SEDARepositoryArchiveUnitPID'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+class SEDARepositoryObjectPID(SEDAAnyEntity):
+    __regid__ = 'SEDARepositoryObjectPID'
+    fetch_attrs, cw_fetch_order = fetch_config([])
+    value_attr = None
+
+CHOICE_RTYPE = {
+  'SEDAAltAccessRulePreventInheritance': [
+    [
+      'seda_prevent_inheritance',
+      'object'
+    ],
+    [
+      'seda_ref_non_rule_id_from',
+      'object'
+    ]
+  ],
+  'SEDAAltAppraisalRulePreventInheritance': [
+    [
+      'seda_prevent_inheritance',
+      'object'
+    ],
+    [
+      'seda_ref_non_rule_id_from',
+      'object'
+    ]
+  ],
+  'SEDAAltArchiveUnitArchiveUnitRefId': [
+    [
+      'seda_archive_unit_ref_id_from',
+      'object'
+    ],
+    [
+      'seda_seq_alt_archive_unit_archive_unit_ref_id_management',
+      'subject'
+    ]
+  ],
+  'SEDAAltBinaryDataObjectAttachment': [
+    [
+      'seda_attachment',
+      'object'
+    ],
+    [
+      'seda_uri',
+      'object'
+    ]
+  ],
+  'SEDAAltClassificationRulePreventInheritance': [
+    [
+      'seda_prevent_inheritance',
+      'object'
+    ],
+    [
+      'seda_ref_non_rule_id_from',
+      'object'
+    ]
+  ],
+  'SEDAAltDisseminationRulePreventInheritance': [
+    [
+      'seda_prevent_inheritance',
+      'object'
+    ],
+    [
+      'seda_ref_non_rule_id_from',
+      'object'
+    ]
+  ],
+  'SEDAAltIsPartOfArchiveUnitRefId': [
+    [
+      'seda_archive_unit_ref_id_from',
+      'object'
+    ],
+    [
+      'seda_repository_archive_unit_pid',
+      'object'
+    ],
+    [
+      'seda_repository_object_pid',
+      'object'
+    ],
+    [
+      'seda_data_object_reference',
+      'object'
+    ]
+  ],
+  'SEDAAltIsVersionOfArchiveUnitRefId': [
+    [
+      'seda_archive_unit_ref_id_from',
+      'object'
+    ],
+    [
+      'seda_data_object_reference',
+      'object'
+    ],
+    [
+      'seda_repository_archive_unit_pid',
+      'object'
+    ],
+    [
+      'seda_repository_object_pid',
+      'object'
+    ]
+  ],
+  'SEDAAltReferencesArchiveUnitRefId': [
+    [
+      'seda_archive_unit_ref_id_from',
+      'object'
+    ],
+    [
+      'seda_repository_archive_unit_pid',
+      'object'
+    ],
+    [
+      'seda_repository_object_pid',
+      'object'
+    ],
+    [
+      'seda_data_object_reference',
+      'object'
+    ]
+  ],
+  'SEDAAltReplacesArchiveUnitRefId': [
+    [
+      'seda_archive_unit_ref_id_from',
+      'object'
+    ],
+    [
+      'seda_repository_archive_unit_pid',
+      'object'
+    ],
+    [
+      'seda_repository_object_pid',
+      'object'
+    ],
+    [
+      'seda_data_object_reference',
+      'object'
+    ]
+  ],
+  'SEDAAltRequiresArchiveUnitRefId': [
+    [
+      'seda_archive_unit_ref_id_from',
+      'object'
+    ],
+    [
+      'seda_repository_archive_unit_pid',
+      'object'
+    ],
+    [
+      'seda_repository_object_pid',
+      'object'
+    ],
+    [
+      'seda_data_object_reference',
+      'object'
+    ]
+  ],
+  'SEDAAltReuseRulePreventInheritance': [
+    [
+      'seda_prevent_inheritance',
+      'object'
+    ],
+    [
+      'seda_ref_non_rule_id_from',
+      'object'
+    ]
+  ],
+  'SEDAAltStorageRulePreventInheritance': [
+    [
+      'seda_prevent_inheritance',
+      'object'
+    ],
+    [
+      'seda_ref_non_rule_id_from',
+      'object'
+    ]
+  ]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/entities/html_generation.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,291 @@
+# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-seda adapter classes for generation of a profile generation as HTML"""
+
+from six import string_types
+
+from cubicweb import _
+
+from .profile_generation import SEDA2ExportAdapter, content_types
+from .profile_generation import xselement_scheme_attribute, _concept_value
+
+
+def element_uml_cardinality(occ, card_entity):
+    """Return UML like cardinality for the given pyxst Occurence. Cardinality may be
+    overriden by the data model's user_cardinality value.
+    """
+    cardinality = getattr(card_entity, 'user_cardinality', None)
+    if cardinality is None:
+        minimum = occ.minimum
+        maximum = occ.maximum
+        if minimum == maximum == 1:
+            return '1'
+        elif maximum != 1:
+            maximum = 'n'
+        return '%s..%s' % (minimum, maximum)
+    else:
+        return cardinality
+
+
+def attribute_cardinality_as_string(occ, card_entity):
+    """Return 'optional' or 'mandatory' for the given pyxst attribute's Occurence. Cardinality may be
+    overriden by the data model's user_cardinality value.
+    """
+    cardinality = getattr(card_entity, 'user_cardinality', None)
+    if cardinality is None:
+        minimum = occ.minimum
+    else:
+        # XXX assert cardinality in ('0..1', '1'), cardinality
+        if cardinality[0] == '0':
+            minimum = 0
+        else:
+            minimum = 1
+    return _('mandatory') if minimum else _('optional')
+
+
+class SEDA2HTMLExport(SEDA2ExportAdapter):
+    """Adapter to build a HTML representation of a SEDA profile"""
+    __regid__ = 'SEDA-2.0.html'
+    namespaces = {}
+    css = '''
+    h1{
+      font-variant: small-caps;
+      color : #FF9800;
+      text-align: center;
+      padding : 10px;
+      font-weight: lighter;
+      border-bottom : 2px solid  #E0E0E0;
+    }
+
+    a{
+      color : #EF6C00;
+    }
+    body {
+      font-family: helvetica, Arial;
+      background-color: #FAFAFA;
+      color : #666;
+    }
+    body >div{
+      background-color: #FFF;
+      padding-bottom : 20px;
+      margin-top : 20px;
+      box-shadow: 2px 2px 2px 0px #E0E0E0;
+    }
+
+    body >div >h3{
+      font-variant: normal;
+      font-weight: lighter;
+      font-size:15px;
+      padding : 10px;
+      background-color: #EF6C00;
+      color : white;
+      font-variant: small-caps;
+      cursor : pointer;
+    }
+
+    body >div >h3 span.card{
+      font-size: 70%;
+      color : #E0E0E0;
+      margin-left : 5px;
+    }
+
+    body>div>div, .sequence >div{
+      margin-top : 20px
+    }
+
+    body>div>div div{
+      padding-left : 20px;
+      margin-left: 15px;
+      border-left : 1px dashed #EF6C00;
+    }
+
+    body>div>div h3{
+      margin-bottom : 10px;
+    }
+
+    h3 span.card {
+      font-size: 70%;
+      color : #EF6C00;
+      margin-left : 5px;
+    }
+
+    span {
+      font-size: 90%;
+      color: #555;
+    }
+    span.label {
+      font-weight: bold;
+      padding-left: 1px;
+    }
+
+    div.attribute {
+      color: #888;
+      width: 40em;
+      font-size: small;
+      padding : 10px;
+    }
+
+    div.attribute span.label {
+      float: left;
+      color : #EF6C00;
+      font-weight: lighter;
+      margin-right : 20px;
+    }
+
+    .sequence >h3:first-child{
+      color :  #EF6C00;
+    }
+    '''
+
+    def dump_etree(self):
+        """Return an XSD etree for the adapted SEDA profile."""
+        root = self.element('html')
+        head = self.element('head', root)
+        self.element('title', head, text=self.entity.dc_title())
+        self.element('meta', head, {'http-equiv': 'content-type',
+                                    'content': 'text/html; charset=UTF-8'})
+        self.element('style', head, text=self.css)
+        body = self.element('body', root)
+        self._dump(body)
+        return root
+
+    def init_transfer_element(self, xselement, root, entity):
+        self.element('h1', root, text=entity.title)
+        if entity.user_annotation:
+            self.element('div', root, text=entity.user_annotation,
+                         attributes={'class': 'description'})
+        return root
+
+    def jumped_element(self, profile_element):
+        return profile_element[-1]
+
+    def element_alternative(self, occ, profile_element, target_value, to_process, card_entity):
+        div = self.element('div', profile_element,
+                           attributes={'class': 'choice'})
+        self.title(div, self._cw._('Alternative'), occ, card_entity)
+        to_process[occ.target].append((target_value, div))
+
+    def element_sequence(self, occ, profile_element, target_value, to_process, card_entity):
+        div = self.element('div', profile_element,
+                           attributes={'class': 'sequence'})
+        self.title(div, self._cw._('Sequence'), occ, card_entity)
+        to_process[occ.target].append((target_value, div))
+
+    def element_xmlattribute(self, occ, profile_element, target_value, to_process, card_entity):
+        div = self.element('div', profile_element,
+                           attributes={'class': 'attribute'})
+        card = self._cw._(attribute_cardinality_as_string(occ, card_entity))
+        self.element('span', div, text=occ.target.local_name, attributes={'class': 'label'})
+        self.element('span', div, text=card, attributes={'class': 'card'})
+        fixed_value = self.serialize(target_value)
+        if isinstance(fixed_value, string_types):
+            self.element('span', div, text=fixed_value, attributes={'class': 'value'})
+        else:
+            span = self.element('span', div, attributes={'class': 'value'})
+            if fixed_value is not None:
+                span.append(fixed_value)
+
+    def element_xmlelement(self, occ, profile_element, target_value, to_process, card_entity):
+        xselement = occ.target
+        if isinstance(occ, dict):  # fake occurence introduced for some elements'content
+            return
+        attrs = {}
+        if hasattr(target_value, 'id'):
+            attrs['id'] = target_value.id
+        div = self.element('div', profile_element, attrs)
+        self.title(div, xselement.local_name, occ, card_entity)
+        annotation = getattr(card_entity, 'user_annotation', None)
+        if annotation:
+            self.element('div', div, text=annotation,
+                         attributes={'class': 'description'})
+        xstypes = content_types(xselement.textual_content_type)
+        self.fill_element(xselement, div, target_value, card_entity, xstypes)
+        if getattr(target_value, 'eid', None):  # value is an entity
+            to_process[xselement].append((target_value, div))
+
+    def fill_element(self, xselement, div, target_value, card_entity, xstypes=None):
+        if xselement.local_name == 'KeywordType':
+            list_div = self.element('div', div)
+            self.element('span', list_div, text=u'listVersionID',
+                         attributes={'class': 'label'})
+            if target_value:
+                list_value = target_value.scheme.description or target_value.scheme.dc_title()
+            else:
+                list_value = 'edition 2009'
+            self.element('span', list_div, text=list_value, attributes={'class': 'value'})
+
+        elif (xselement.local_name == 'KeywordReference' and card_entity.scheme):
+            self.concept_scheme_attribute(xselement, div, card_entity.scheme)
+
+        elif getattr(target_value, 'cw_etype', None) == 'Concept':
+            self.concept_scheme_attribute(xselement, div, target_value.scheme)
+        if xstypes:
+            ct_div = self.element('div', div)
+            self.element('span', ct_div, text=self._cw._('XSD content type'),
+                         attributes={'class': 'label'})
+            xstypes = self._cw._(' ALT_I18N ').join(u'xsd:' + xstype for xstype in xstypes)
+            self.element('span', ct_div, text=xstypes, attributes={'class': 'value'})
+        fixed_value = self.serialize(target_value)
+        if fixed_value is None:
+            return
+        value_div = self.element('div', div)
+        self.element('span', value_div, text=self._cw._('fixed value'),
+                     attributes={'class': 'label'})
+        if isinstance(fixed_value, string_types):
+            self.element('span', value_div, text=fixed_value, attributes={'class': 'value'})
+        else:
+            span = self.element('span', value_div, attributes={'class': 'value'})
+            span.append(fixed_value)
+
+    def concept_scheme_attribute(self, xselement, div, scheme):
+        try:
+            xsattr = xselement_scheme_attribute(xselement)
+        except KeyError:
+            return  # no attribute to define scheme (e.g. for language specification)
+        div = self.element('div', div)
+        self.element('span', div, text=xsattr, attributes={'class': 'label'})
+        self.element('a', self.element('span', div, attributes={'class': 'value'}),
+                     {'href': scheme.absolute_url()}, text=scheme.dc_title())
+
+    def title(self, div, label, occ, card_entity):
+        h3 = self.element('h3', div, text=label)
+        self.element('span', h3, attributes={'class': 'card'},
+                     text=u'[{0}]'.format(self._cw._(element_uml_cardinality(occ, card_entity))))
+
+    def serialize(self, value):
+        """Return value as None or some text or etree node to be inserted in the HTML DOM."""
+        if value is None:
+            return None
+        if hasattr(value, 'eid'):
+            if value.cw_etype in ('AuthorityRecord', 'ConceptScheme'):
+                href, label = value.absolute_url(), value.dc_title()
+            elif value.cw_etype == 'Concept':
+                href = value.absolute_url()
+                label = _concept_value(value, 'seda-2')
+                if value.label() != label:
+                    label = u'{0} ({1})'.format(label, value.label())
+            elif hasattr(value, 'id'):
+                # value is something in the profile which has a id (e.g. archive unit, data object)
+                href, label = u'#{0}'.format(value.id), value.id
+            else:
+                return None  # intermediary entity
+            return self.element('a', self.element('span', attributes={'class': 'value'}),
+                                attributes={'href': href}, text=label)
+        else:
+            if isinstance(value, bool):
+                value = 'true' if value else 'false'
+            assert isinstance(value, string_types), repr(value)
+            return value
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/entities/itree.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,148 @@
+# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-seda ITreeBase adapters"""
+
+from cubicweb.predicates import is_instance
+from cubicweb.view import EntityAdapter
+
+from . import simplified_profile
+
+
+def parent_archive_unit(entity):
+    """Return the first encountered parent which is an ArchiveUnit"""
+    while entity.cw_etype != 'SEDAArchiveUnit':
+        entity = entity.cw_adapt_to('IContained').parent
+    return entity
+
+
+class IContainedToITreeBase(EntityAdapter):
+    """Map IContained adapters to ITreeBase, additionaly configured with a list of relations leading to
+    contained's children.
+
+    ITreeBase is a simplified version of cubicweb's ITree.
+    """
+
+    __regid__ = 'ITreeBase'
+    __abstract__ = True
+
+    _children_relations = []  # list of (relation type, role) to get entity's children
+
+    def iterchildren(self):
+        """Return an iterator over the item's children."""
+        for rel, role in self._children_relations:
+            for child in self.entity.related(rel, role, entities=True):
+                yield child
+
+    def is_leaf(self):
+        """Returns True if the entity does not have any children."""
+        try:
+            next(iter(self.iterchildren()))
+            return False
+        except StopIteration:
+            return True
+
+    def parent(self):
+        return self.entity.cw_adapt_to('IContained').parent
+
+    def iterancestors(self):
+        """Return an iterator on the ancestors of the entity."""
+        def _uptoroot(self):
+            curr = self
+            while True:
+                curr = curr.parent()
+                if curr is None:
+                    break
+                yield curr
+                curr = curr.cw_adapt_to('ITreeBase')
+        return _uptoroot(self)
+
+    iterparents = iterancestors  # Compat. with CubicWeb's ITree.
+
+
+class ITreeBaseArchiveUnitAdapter(IContainedToITreeBase):
+    """Adapt ArchiveUnit entities to ITreeBase."""
+
+    __select__ = is_instance('SEDAArchiveUnit')
+
+    _children_relations = [('seda_archive_unit', 'object')]
+
+    def parent(self):
+        parent = self.entity.cw_adapt_to('IContained').parent
+        if parent is None:
+            return None
+        if parent.cw_etype == 'SEDAArchiveTransfer':
+            return parent
+        return parent_archive_unit(parent)
+
+    def iterchildren(self):
+        seq = self.entity.first_level_choice.content_sequence
+        if seq is None:
+            # 'reference' archive unit
+            return
+        for rel, role in self._children_relations:
+            for child in seq.related(rel, role, entities=True):
+                yield child
+
+
+class ITreeBaseSimplifiedArchiveUnitAdapter(ITreeBaseArchiveUnitAdapter):
+
+    __select__ = ITreeBaseArchiveUnitAdapter.__select__ & simplified_profile()
+
+    _children_relations = [('seda_archive_unit', 'object')]
+
+    def iterchildren(self):
+        for child in super(ITreeBaseSimplifiedArchiveUnitAdapter, self).iterchildren():
+            yield child
+        seq = self.entity.first_level_choice.content_sequence
+        assert seq is not None, self.entity  # can't be None in simplified profile
+        for do in self._cw.execute(
+                'Any DO, DOUA ORDERBY DOUA WHERE DO user_annotation DOUA, '
+                'REF seda_data_object_reference_id DO, '
+                'REF seda_data_object_reference SEQ, SEQ eid %(x)s',
+                {'x': seq.eid}).entities():
+            yield do
+
+
+class ITreeBaseDataObjectAdapter(IContainedToITreeBase):
+    """Adapt BinaryDataObject and PhysicalDataObject entities to ITreeBase."""
+
+    __select__ = is_instance('SEDABinaryDataObject', 'SEDAPhysicalDataObject')
+
+
+class ITreeBaseSimplifiedDataObjectAdapter(ITreeBaseDataObjectAdapter):
+
+    __select__ = ITreeBaseDataObjectAdapter.__select__ & simplified_profile()
+
+    def parent(self):
+        return parent_archive_unit(self.entity.reverse_seda_data_object_reference_id[0])
+
+
+class ITreeBaseArchiveTransferAdapter(IContainedToITreeBase):
+    """Adapt ArchiveTransfer entities to ITreeBase."""
+
+    __select__ = is_instance('SEDAArchiveTransfer')
+
+    @property
+    def _children_relations(self):
+        if self.entity.simplified_profile:
+            return [('seda_archive_unit', 'object')]
+        else:
+            return [('seda_binary_data_object', 'object'),
+                    ('seda_physical_data_object', 'object'),
+                    ('seda_archive_unit', 'object')]
+
+    def parent(self):
+        return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/entities/profile_generation.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,1560 @@
+# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-seda adapter classes for profile (schema) generation"""
+
+from functools import partial
+from collections import defaultdict, namedtuple
+
+from six import text_type, string_types
+
+from lxml import etree
+from pyxst.xml_struct import graph_nodes
+
+from logilab.common import attrdict
+
+from yams import BASE_TYPES
+
+from cubicweb.predicates import is_instance
+from cubicweb.view import EntityAdapter
+
+from ..xsd import XSDM_MAPPING, JUMP_ELEMENTS
+from ..xsd2yams import SKIP_ATTRS
+from . import simplified_profile
+
+
+JUMPED_OPTIONAL_ELEMENTS = set(('DataObjectPackage', 'FileInfo', 'PhysicalDimensions', 'Coverage'))
+
+
+def substitute_xml_prefix(prefix_name, namespaces):
+    """Given an XML prefixed name in the form `'ns:name'`, return the string `'{<ns_uri>}name'`
+    where `<ns_uri>` is the URI for the namespace prefix found in `namespaces`.
+
+    This new string is then suitable to build an LXML etree.Element object.
+
+    Example::
+
+      >>> substitude_xml_prefix('xlink:href', {'xlink': 'http://wwww.w3.org/1999/xlink'})
+      '{http://www.w3.org/1999/xlink}href'
+
+    """
+    try:
+        prefix, name = prefix_name.split(':', 1)
+    except ValueError:
+        return prefix_name
+    assert prefix in namespaces, 'Unknown namespace prefix: {0}'.format(prefix)
+    return '{{{0}}}'.format(namespaces[prefix]) + name
+
+
+def content_types(content_type):
+    """Return an ordered tuple of content types from pyxst `textual_content_type` that may be None, a
+    set or a string value.
+    """
+    if content_type:
+        if isinstance(content_type, set):
+            content_types = sorted(content_type)
+        else:
+            content_types = (content_type,)
+    else:
+        content_types = ()
+    return content_types
+
+
+def _internal_reference(value):
+    """Return True if the given value is a reference to an entity within the profile."""
+    return getattr(value, 'cw_etype', None) in ('SEDAArchiveUnit',
+                                                'SEDABinaryDataObject', 'SEDAPhysicalDataObject')
+
+
+def _concept_value(concept, language):
+    """Return string value to be inserted in a SEDA export for the given concept.
+
+    * `concept` may be None, in which case None will be returned
+
+    * `language` is the language matching the exported format (one of 'seda-2', 'seda-1' or
+      'seda-02')
+    """
+    assert language in ('seda-2', 'seda-1', 'seda-02')
+    if concept is None:
+        return None
+    for code in (language, 'seda', 'en'):
+        try:
+            return concept.labels[code]
+        except KeyError:
+            continue
+    raise RuntimeError('Concept %s has no preferred label in one of "%s", "seda" or "en" language'
+                       % (concept.eid, language))
+
+
+def eid2xmlid(eid):
+    """Return a value usable as ID/IDREF for the given eid."""
+    return 'id' + text_type(eid)
+
+
+def serialize(value):
+    """Return typed `value` as an XSD string."""
+    if value is None:
+        return None
+    if hasattr(value, 'eid'):
+        if value.cw_etype == 'ConceptScheme':
+            return value.absolute_url()
+        if value.cw_etype == 'Concept':
+            return _concept_value(value, 'seda-2')
+        if _internal_reference(value):
+            return eid2xmlid(value.eid)
+        return None  # intermediary entity
+    if isinstance(value, bool):
+        return 'true' if value else 'false'
+    assert isinstance(value, string_types), repr(value)
+    return text_type(value)
+
+
+def minmax_cardinality(string_cardinality, _allowed=('0..1', '0..n', '1', '1..n')):
+    """Return (minimum, maximum) cardinality for the cardinality as string (one of '0..1', '0..n',
+    '1' or '1..n').
+    """
+    assert string_cardinality in _allowed, '%s not allowed %s' % (string_cardinality, _allowed)
+    if string_cardinality[0] == '0':
+        minimum = 0
+    else:
+        minimum = 1
+    if string_cardinality[-1] == 'n':
+        maximum = graph_nodes.INFINITY
+    else:
+        maximum = 1
+    return minimum, maximum
+
+
+def element_minmax_cardinality(occ, card_entity):
+    """Return (minimum, maximum) cardinality for the given pyxst Occurence and entity.
+
+    Occurence 's cardinality may be overriden by the entity's user_cardinality value.
+    """
+    cardinality = getattr(card_entity, 'user_cardinality', None)
+    if cardinality is None:
+        return occ.minimum, occ.maximum
+    else:
+        return minmax_cardinality(cardinality)
+
+
+def attribute_minimum_cardinality(occ, card_entity):
+    """Return 0 or 1 for the given pyxst attribute's Occurence. Cardinality may be overriden by
+    the data model's user_cardinality value.
+    """
+    cardinality = getattr(card_entity, 'user_cardinality', None)
+    if cardinality is None:
+        return occ.minimum
+    else:
+        return minmax_cardinality(cardinality, ('0..1', '1'))[0]
+
+
+def iter_path_children(xselement, entity):
+    """Return an iterator on `entity` children entities according to `xselement` definition.
+
+    (`path`, `target`) is returned with `path` the path definition leading to the target, and
+    `target` either a final value in case of attributes or a list of entities.
+    """
+    for rtype, role, _path in XSDM_MAPPING.iter_rtype_role(xselement.local_name):
+        if _path[0][2] in BASE_TYPES:
+            # entity attribute
+            if getattr(entity, rtype) is not None:
+                yield _path, getattr(entity, rtype)
+        else:
+            related = entity.related(rtype, role, entities=True)
+            if related:
+                yield _path, related
+
+
+class RNGMixin(object):
+    """Mixin class providing some Relax NG schema generation helper methods."""
+
+    def rng_element_parent(self, parent, minimum, maximum=1):
+        """Given a etree node and minimum/maximum cardinalities of a desired child element,
+        return suitable parent node for it.
+
+        This will be one of rng:optional, rng:zeroOrMore or rng:oneOrMore that will be created by
+        this method or the given parent itself if minimum == maximum == 1.
+        """
+        if minimum == 1 and maximum == 1:
+            return parent
+        elif minimum == 0 and maximum == 1:
+            return self.element('rng:optional', parent)
+        elif minimum == 0 and maximum == graph_nodes.INFINITY:
+            return self.element('rng:zeroOrMore', parent)
+        elif minimum == 1 and maximum == graph_nodes.INFINITY:
+            return self.element('rng:oneOrMore', parent)
+        else:
+            assert False, ('unexpected min/max cardinality:', minimum, maximum)
+
+    def rng_attribute_parent(self, parent, minimum):
+        """Given a etree node and minimum cardinality of a desired attribute,
+        return suitable parent node for it.
+
+        This will be rng:optional that will be created by this method or the given parent itself if
+        minimum == 1.
+        """
+        if minimum == 1:
+            return parent
+        else:
+            return self.element('rng:optional', parent)
+
+    def rng_value(self, element, qualified_datatype, fixed_value=None):
+        """Given a (etree) schema element, a data type (e.g. 'xsd:token') and an optional fixed
+        value, add RNG declaration to the element to declare the datatype and fix the value if
+        necessary.
+        """
+        prefix, datatype = qualified_datatype.split(':')
+        if prefix != 'xsd':
+            # XXX RelaxNG compatible version of custom types? this would allow
+            # `type_attrs['datatypeLibrary'] = self.namespaces[prefix]`. In the mean time, turn
+            # every custom type to string, supposing transfer are also checked against the original
+            # schema (as agape v1 was doing).
+            datatype = 'string'
+        type_attrs = {'type': datatype}
+        if fixed_value is not None:
+            self.element('rng:value', element, type_attrs, text=fixed_value)
+        else:
+            self.element('rng:data', element, type_attrs)
+
+
+class SEDA2ExportAdapter(EntityAdapter):
+    """Abstract base class for export of SEDA profile."""
+    __abstract__ = True
+    __select__ = is_instance('SEDAArchiveTransfer')
+    encoding = 'utf-8'
+    content_type = 'application/xml'
+    # to be defined in concret implementations
+    namespaces = {}
+    root_attributes = {}
+
+    def dump(self):
+        """Return an schema string for the adapted SEDA profile."""
+        root = self.dump_etree()
+        return etree.tostring(root, encoding=self.encoding, pretty_print=True, standalone=False)
+
+    def dump_etree(self):
+        """Return an XSD etree for the adapted SEDA profile."""
+        raise NotImplementedError()
+
+    def qname(self, tag):
+        return substitute_xml_prefix(tag, self.namespaces)
+
+    def element(self, tag, parent=None, attributes=None, text=None):
+        """Generic method to build a XSD element tag.
+
+        Params:
+
+        * `tag`, tag name of the element
+
+        * `parent`, the parent etree node
+
+        * `attributes`, dictionary of attributes - may contain a special 'documentation' attribute
+          that will be added in a xsd:annotation node
+
+        * `text`, textual content of the tag if any
+        """
+        attributes = attributes or {}
+        tag = self.qname(tag)
+        documentation = attributes.pop('documentation', None)
+        for attr, value in attributes.items():
+            newattr = substitute_xml_prefix(attr, self.namespaces)
+            attributes[newattr] = value
+            if newattr != attr:
+                attributes.pop(attr)
+        if parent is None:
+            elt = etree.Element(tag, attributes, nsmap=self.namespaces)
+        else:
+            elt = etree.SubElement(parent, tag, attributes)
+        if text is not None:
+            elt.text = text
+        if documentation:
+            annot = self.element('xsd:annotation', elt)
+            self.element('xsd:documentation', annot).text = documentation
+        return elt
+
+    def dispatch_occ(self, profile_element, occ, target_value, to_process, card_entity):
+        callback = getattr(self, 'element_' + occ.target.__class__.__name__.lower())
+        callback(occ, profile_element, target_value, to_process, card_entity)
+
+    def _dump(self, root):
+        entity = self.entity
+        xselement = XSDM_MAPPING.root_xselement
+        transfer_element = self.init_transfer_element(xselement, root, entity)
+        to_process = defaultdict(list)
+        to_process[xselement].append((entity, transfer_element))
+        # first round to ensure we have necessary basic structure
+        for xselement, etype, child_defs in XSDM_MAPPING:
+            # print 'PROCESS', getattr(xselement, 'local_name', xselement.__class__.__name__), etype
+            for entity, profile_element in to_process.pop(xselement, ()):
+                assert etype == entity.cw_etype
+                self._process(entity, profile_element, child_defs, to_process)
+        # then process remaining elements
+        # print 'STARTING ROUND 2'
+        while to_process:
+            xselement = next(iter(to_process))
+            entities_profiles = to_process.pop(xselement, ())
+            if entities_profiles:
+                try:
+                    etype, child_defs = XSDM_MAPPING[xselement]
+                except KeyError:
+                    # element has no children
+                    continue
+                for entity, profile_element in entities_profiles:
+                    assert etype == entity.cw_etype
+                    self._process(entity, profile_element, child_defs, to_process)
+
+        assert not to_process, to_process
+
+    def _process(self, entity, profile_element, child_defs, to_process):
+        for occ, path in child_defs:
+            # print '  child', getattr(occ.target, 'local_name', occ.target.__class__.__name__), \
+            #    [x[:-1] for x in path]
+            if not path:
+                assert not isinstance(occ.target, graph_nodes.XMLAttribute)
+                assert occ.target.local_name in JUMP_ELEMENTS, occ.target
+                if occ.minimum == 0 and not any(iter_path_children(occ.target, entity)):
+                    # element has no children, skip it
+                    continue
+                if occ.target.local_name in JUMPED_OPTIONAL_ELEMENTS:
+                    # elements in JUMPED_OPTIONAL_ELEMENTS are jumped but have optional cardinality,
+                    # so search in all it's child element, and mark it as mandatory if one of them
+                    # is mandatory, else keep it optional
+                    cardinality = '0..1'
+                    for _path, target in iter_path_children(occ.target, entity):
+                        if _path[0][2] in BASE_TYPES:
+                            # special case of a mandatory attribute: parent element will be
+                            # mandatory if some value is specified, else that's fine
+                            if target is not None:
+                                cardinality = '1'
+                                break
+                        elif any(te.user_cardinality == '1' for te in target):
+                            cardinality = '1'
+                            break
+                else:
+                    cardinality = None
+                # jumped element: give None as target_value but register the generated element for
+                # later processing
+                self.dispatch_occ(profile_element, occ, None, to_process,
+                                  card_entity=attrdict({'user_cardinality': cardinality}))
+                to_process[occ.target].append((entity, self.jumped_element(profile_element)))
+            else:
+                # print '  values', _path_target_values(entity, path)
+                for card_entity, target_value in _path_target_values(entity, path):
+                    self.dispatch_occ(profile_element, occ, target_value, to_process,
+                                      card_entity=card_entity)
+
+    def init_transfer_element(self, xselement, root, entity):
+        """Initialize and return the XML element holding the ArchiveTransfer definition, as well as
+        any other necessary global definitions.
+        """
+        raise NotImplementedError()
+
+    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'
+    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',
+    }
+
+    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',
+    }
+
+    def dump_etree(self):
+        """Return an XSD etree for the adapted SEDA profile."""
+        root = self.element('rng:grammar', attributes=self.root_attributes)
+        start = self.element('rng:start', root)
+        # XXX http://lists.xml.org/archives/xml-dev/200206/msg01074.html ?
+        # self.element('xsd:import', parent=root,
+        #              attributes={'namespace': 'http://www.w3.org/1999/xlink',
+        #                          'schemaLocation': 'http://www.w3.org/1999/xlink.xsd'})
+        self._dump(start)
+
+        open_type = self.element('rng:define', root, {'name': 'OpenType'})
+        open_elt = self._create_hierarchy(open_type, ['rng:zeroOrMore', 'rng:element'])
+        self.element('rng:anyName', open_elt)
+        self._create_hierarchy(open_elt, ['rng:zeroOrMore', 'rng:attribute', 'rng:anyName'])
+
+        # add a 'text' node to empty rng:element to satisfy the RNG grammar
+        namespaces = self.namespaces.copy()
+        del namespaces[None]  # xpath engine don't want None prefix
+        for element in root.xpath('//rng:element[not(*)]', namespaces=namespaces):
+            self.element('rng:text', element)
+        return root
+
+    def init_transfer_element(self, xselement, root, entity):
+        transfer_element = self.element('rng:element', root,
+                                        {'name': xselement.local_name,
+                                         'documentation': entity.user_annotation})
+        exc = self._create_hierarchy(
+            transfer_element, ['rng:zeroOrMore', 'rng:attribute', 'rng:anyName', 'rng:except'])
+        self.element('rng:nsName', exc)
+        self.element('rng:nsName', exc, {'ns': ''})
+        return transfer_element
+
+    def jumped_element(self, profile_element):
+        element = profile_element[-1]
+        if element.tag != '{http://relaxng.org/ns/structure/1.0}element':
+            # optional, zeroOrMore, etc.: should pick their child element
+            element = element[-1]
+            assert element.tag == '{http://relaxng.org/ns/structure/1.0}element', element
+        return element
+
+    def element_alternative(self, occ, profile_element, target_value, to_process, card_entity):
+        parent_element = self._rng_element_parent(occ, card_entity, profile_element)
+        target_element = self.element('rng:choice', parent_element)
+        to_process[occ.target].append((target_value, target_element))
+
+    def element_sequence(self, occ, profile_element, target_value, to_process, card_entity):
+        parent_element = self._rng_element_parent(occ, card_entity, profile_element)
+        target_element = self.element('rng:group', parent_element)  # XXX sequence
+        to_process[occ.target].append((target_value, target_element))
+
+    def element_xmlattribute(self, occ, profile_element, target_value, to_process, card_entity):
+        parent_element = self._rng_attribute_parent(occ, card_entity, profile_element)
+        self._rng_attribute(occ.target, parent_element, serialize(target_value))
+
+    def element_xmlelement(self, occ, profile_element, target_value, to_process, card_entity):  # noqa
+        parent_element = self._rng_element_parent(occ, card_entity, profile_element)
+        xselement = occ.target
+        attrs = {'documentation': getattr(card_entity, 'user_annotation', None),
+                 'name': xselement.local_name}
+        if xselement.local_name == 'Signature':
+            element = self.element('rng:element', parent_element, attrs)
+            self.element('rng:ref', element, {'name': 'OpenType'})
+        elif isinstance(occ, dict):  # fake occurence introduced for some elements'content
+            # target element has already been introduced: it is now given as profile_element
+            self.fill_element(xselement, profile_element, target_value, card_entity)
+        else:
+            target_element = self.element('rng:element', parent_element, attrs)
+            xstypes = content_types(xselement.textual_content_type)
+            if xstypes:
+                if len(xstypes) == 1:
+                    parent_element = target_element
+                else:
+                    parent_element = self.element('rng:choice', target_element)
+                for xstype in xstypes:
+                    self.fill_element(xselement, parent_element, target_value, card_entity,
+                                      xstype=xstype, copy_attributes=True)
+            else:
+                # 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(target_element, target_value)
+                elif xselement.local_name in ('ArchivalAgency', 'TransferringAgency'):
+                    self.fill_organization_element(target_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, profile_element, value, card_entity,  # noqa
+                     copy_attributes=False, xstype=None):
+        if xselement.local_name == 'KeywordType':
+            attr = self.element('rng:attribute', attributes={'name': 'listVersionID'},
+                                parent=self.element('rng:optional', profile_element))
+            if value:
+                list_value = value.scheme.description or value.scheme.dc_title()
+                attrs = {'type': xstype} if xstype else {}
+                self.element('rng:value', attr, attrs, text=list_value)
+            else:
+                attr.attrib[self.qname('a:defaultValue')] = 'edition 2009'
+
+        elif (xselement.local_name == 'KeywordReference' and card_entity.scheme):
+            self.concept_scheme_attribute(xselement, profile_element, card_entity.scheme)
+
+        elif getattr(value, 'cw_etype', None) == 'Concept':
+            self.concept_scheme_attribute(xselement, profile_element, value.scheme)
+
+        elif copy_attributes:
+            for attrname, occ in xselement.attributes.items():
+                if attrname in ('id', 'href') or attrname.startswith(('list', 'scheme')):
+                    parent_element = self._rng_attribute_parent(occ, None, profile_element)
+                    self._rng_attribute(occ.target, parent_element)
+        fixed_value = serialize(value)
+        if fixed_value is not None:
+            if _internal_reference(value):
+                profile_element.attrib[self.qname('a:defaultValue')] = fixed_value
+                self.element('rng:data', profile_element, {'type': 'NCName'})
+            else:
+                if (len(profile_element)
+                        and profile_element[-1].tag == '{http://relaxng.org/ns/structure/1.0}data'):
+                    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)
+        elif xstype is not None:
+            self.element('rng:data', profile_element, {'type': xstype})
+
+    def concept_scheme_attribute(self, xselement, type_element, scheme):
+        try:
+            scheme_attr = xselement_scheme_attribute(xselement)
+        except KeyError:
+            return
+        scheme_attr = self.element('rng:attribute', type_element,
+                                   attributes={'name': scheme_attr})
+        self.element('rng:value', scheme_attr, text=scheme.absolute_url())
+
+    def fill_organization_element(self, parent_element, value):
+        target_element = self.element('rng:element', parent_element, {'name': 'Identifier'})
+        if value:
+            self.element('rng:value', target_element, text=value.absolute_url())
+
+    def _rng_element_parent(self, occ, card_entity, profile_element):
+        minimum, maximum = element_minmax_cardinality(occ, card_entity)
+        return self.rng_element_parent(profile_element, minimum, maximum)
+
+    def _rng_attribute_parent(self, occ, card_entity, profile_element):
+        minimum = attribute_minimum_cardinality(occ, card_entity)
+        return self.rng_element_parent(profile_element, minimum)
+
+    def _rng_attribute(self, xselement, parent_element, value=None):
+        xstypes = content_types(xselement.textual_content_type)
+        if len(xstypes) > 1:
+            parent_element = self.element('rng:choice', parent_element)
+        for xstype in xstypes:
+            attr_element = self.element('rng:attribute', parent_element,
+                                        {'name': xselement.local_name})
+            if value is not None:
+                if xselement.local_name == 'id':
+                    attr_element.attrib[self.qname('seda:profid')] = value
+                    self.element('rng:data', attr_element, {'type': 'ID'})
+                else:
+                    self.element('rng:value', attr_element, {'type': xstype}, text=value)
+            else:
+                self.element('rng:data', attr_element, {'type': xstype})
+
+    def _create_hierarchy(self, parent, tags):
+        for tag in tags:
+            parent = self.element(tag, parent)
+        return parent
+
+
+def climb_rule_holders(transfer_or_archive_unit):
+    """Starting from a transfer or archive unit entity, yield entity that may be linked to management
+    rule until the root (transfer) is reached.
+    """
+    while transfer_or_archive_unit is not None:
+        if transfer_or_archive_unit.cw_etype == 'SEDAArchiveTransfer':
+            yield transfer_or_archive_unit
+        else:
+            yield transfer_or_archive_unit.first_level_choice.content_sequence
+        transfer_or_archive_unit = transfer_or_archive_unit.cw_adapt_to('ITreeBase').parent()
+
+
+def _safe_cardinality(entity):
+    """Return entity's cardinality if some entity is given, else None."""
+    if entity is None:
+        return None
+    return entity.user_cardinality
+
+
+def _safe_concept_value(entity, concepts_language):
+    """Return entity's targetted concept if some entity is given, else None."""
+    if entity is None:
+        return None
+    return _concept_value(entity.concept, concepts_language)
+
+
+class XAttr(namedtuple('_XAttr', ['name', 'qualified_type', 'cardinality', 'fixed_value'])):
+    """Simple representation of an attribute element in a schema (RNG or XSD).
+
+    Parameters:
+
+    * `name`, the attribute's name,
+
+    * `qualified_type`, its qualified type (e.g. 'xsd:string'),
+
+    * `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 __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:
+            cardinality = '1'
+        return super(XAttr, cls).__new__(cls, name, qualified_type, cardinality, fixed_value)
+
+
+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):
+    """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()
+
+    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',
+        'udt': 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:10',
+        'clmDAFFileTypeCode': 'urn:un:unece:uncefact:codelist:draft:DAF:fileTypeCode:2009-08-18',
+        'clmIANACharacterSetCode':
+        'urn:un:unece:uncefact:codelist:standard:IANA:CharacterSetCode:2007-05-14',
+        'clmIANAMIMEMediaType':
+        'urn:un:unece:uncefact:codelist:standard:IANA:MIMEMediaType:2008-11-12',
+        'clm60133': 'urn:un:unece:uncefact:codelist:standard:6:0133:40106',
+    }
+    root_attributes = {
+        'targetNamespace': 'fr:gouv:culture:archivesdefrance:seda:v1.0',
+        'attributeFormDefault': 'unqualified',
+        'elementFormDefault': 'qualified',
+        'version': '1.0',
+    }
+
+    concepts_language = 'seda-1'
+
+    def element_schema(self, parent, name, xsd_type=None, fixed_value=None, cardinality='1',
+                       documentation=None, xsd_attributes=()):
+        attributes = {'name': name}
+        if fixed_value is not None:
+            attributes['fixed'] = text_type(fixed_value)
+        if xsd_type is not None and not xsd_attributes:
+            attributes['type'] = xsd_type
+        assert cardinality in ('0..1', '0..n', '1', '1..n')
+        if cardinality != '1':
+            if cardinality[0] == '0':
+                attributes['minOccurs'] = '0'
+            if cardinality[-1] == 'n':
+                attributes['maxOccurs'] = 'unbounded'
+        if documentation:
+            attributes['documentation'] = documentation
+        element = self.element('xsd:element', parent, attributes)
+        children_parent = None
+        if xsd_type is None:
+            attributes_parent = self.element('xsd:complexType', element)
+            children_parent = self.element('xsd:sequence', attributes_parent)
+        elif xsd_attributes:
+            ct = self.element('xsd:complexType', element)
+            scontent = self.element('xsd:simpleContent', ct)
+            attributes_parent = self.element('xsd:extension', scontent, {'base': xsd_type})
+        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):
+        """Return an XSD etree for the adapted SEDA profile."""
+        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.xsd_transfer(root, self.entity)
+        return root
+
+    def xsd_transfer(self, parent, archive_transfer):
+        """Append XSD elements for the archive transfer to the given parent node."""
+        transfer_node = self.element_schema(parent, 'ArchiveTransfer',
+                                            documentation=archive_transfer.title,
+                                            xsd_attributes=[XAttr('Id', 'xsd:ID')])
+        for comment in archive_transfer.comments:
+            self.element_schema(transfer_node, 'Comment', 'udt:TextType',
+                                fixed_value=comment.comment,
+                                cardinality=comment.user_cardinality,
+                                documentation=comment.user_annotation,
+                                xsd_attributes=[XAttr('languageID', 'xsd:language')])
+        self.element_schema(transfer_node, 'Date', 'udt:DateTimeType')
+        self.element_schema(transfer_node, 'TransferIdentifier', 'qdt:ArchivesIDType',
+                            xsd_attributes=self.xsd_attributes_scheme())
+        for agency_type in ('TransferringAgency', 'ArchivalAgency'):
+            self.xsd_agency(transfer_node, agency_type)
+
+        for archive_unit in archive_transfer.archive_units:
+            self.xsd_archive(transfer_node, archive_unit)
+
+    def xsd_archive(self, parent, archive_unit):
+        """Append XSD elements for an archive to the given parent node."""
+        archive_node = self.element_schema(parent, 'Archive',
+                                           cardinality=archive_unit.user_cardinality,
+                                           documentation=archive_unit.user_annotation,
+                                           xsd_attributes=[XAttr('Id', 'xsd:ID')])
+        transfer = archive_unit.cw_adapt_to('ITreeBase').parent()
+        self.xsd_archival_agreement(archive_node, transfer)
+        # hard-coded description's language XXX fine, content language may be specified
+        self.element_schema(archive_node, 'DescriptionLanguage', 'qdt:CodeLanguageType',
+                            fixed_value='fra',
+                            xsd_attributes=[LIST_VERSION_ID_2011])
+        name_entity = self.archive_unit_name(archive_unit)
+        self.element_schema(archive_node, 'Name', 'udt:TextType',
+                            fixed_value=name_entity.title,
+                            documentation=name_entity.user_annotation,
+                            xsd_attributes=[XAttr('languageID', 'xsd:language')])
+        content_entity = self.archive_unit_content(archive_unit)
+        self.xsd_transferring_agency_archive_identifier(archive_node, content_entity,
+                                                        'TransferringAgencyArchiveIdentifier')
+        self.xsd_content_description(archive_node, content_entity)
+        appraisal_rule_entity = self.archive_unit_appraisal_rule(archive_unit)
+        if appraisal_rule_entity:
+            self.xsd_appraisal_rule(archive_node, appraisal_rule_entity)
+        # not optional in seda 1
+        access_rule_entity = self.archive_unit_access_rule(archive_unit)
+        self.xsd_access_rule(archive_node, access_rule_entity)
+        self.xsd_children(archive_node, archive_unit)
+
+    archive_object_tag_name = 'ArchiveObject'
+
+    def xsd_archive_object(self, parent, archive_unit):
+        """Append XSD elements for the archive object to the given parent node."""
+        ao_node = self.element_schema(parent, self.archive_object_tag_name,
+                                      cardinality=archive_unit.user_cardinality,
+                                      documentation=archive_unit.user_annotation,
+                                      xsd_attributes=[XAttr('Id', 'xsd:ID')])
+        content_entity = self.archive_unit_content(archive_unit)
+        self.element_schema(ao_node, 'Name', 'udt:TextType',
+                            fixed_value=content_entity.title.title,
+                            documentation=content_entity.title.user_annotation,
+                            xsd_attributes=[XAttr('languageID', 'xsd:language')])
+        self.xsd_transferring_agency_archive_identifier(ao_node, content_entity,
+                                                        'TransferringAgencyObjectIdentifier')
+        if (self.__regid__.startswith('SEDA-1.0')
+                or content_entity.start_date
+                or content_entity.end_date
+                or content_entity.description
+                or content_entity.keywords):
+            self.xsd_content_description(ao_node, content_entity)
+        appraisal_rule_entity = self.archive_unit_appraisal_rule(archive_unit)
+        if appraisal_rule_entity:
+            self.xsd_appraisal_rule(ao_node, appraisal_rule_entity)
+        access_rule_entity = self.archive_unit_access_rule(archive_unit)
+        if access_rule_entity:
+            self.xsd_access_rule(ao_node, access_rule_entity)
+        self.xsd_children(ao_node, archive_unit)
+
+        return ao_node
+
+    def xsd_document(self, parent, data_object):
+        """Append XSD elements for the document to the given parent node."""
+        document_node = self.element_schema(parent, 'Document',
+                                            cardinality=data_object.user_cardinality,
+                                            documentation=data_object.user_annotation,
+                                            xsd_attributes=[XAttr('Id', 'xsd:ID')])
+
+        self.xsd_system_id(document_node, data_object)
+        self.xsd_attachment(document_node, data_object)
+        self.xsd_date_created(document_node, data_object)
+        self.xsd_document_type(document_node, data_object)
+
+    def xsd_children(self, parent, entity):
+        """Iter on archive/archive object children, which may be either
+        archive objects or documents, and append XSD elements for them to the given parent node.
+        """
+        for au_or_bdo in entity.cw_adapt_to('ITreeBase').iterchildren():
+            if au_or_bdo.cw_etype == 'SEDABinaryDataObject':
+                self.xsd_document(parent, au_or_bdo)
+            else:
+                assert au_or_bdo.cw_etype == 'SEDAArchiveUnit'
+                self.xsd_archive_object(parent, au_or_bdo)
+
+    def xsd_attachment(self, parent, data_object):
+        _safe_concept = partial(_safe_concept_value, concepts_language=self.concepts_language)
+
+        format_id = data_object.format_id
+        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)),
+                                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)),
+                                XAttr('filename', 'xsd:string',
+                                      cardinality='0..1',
+                                      fixed_value=data_object.filename),
+                                # hard-coded attributes
+                                XAttr('characterSetCode',
+                                      'clmIANACharacterSetCode:CharacterSetCodeContentType',
+                                      cardinality=None),
+                                XAttr('uri', 'xsd:anyURI',
+                                      cardinality=None),
+                            ])
+
+    def xsd_date_created(self, parent, data_object):
+        date_created = data_object.date_created_by_application
+        if date_created:
+            self.element_schema(parent, 'Creation', 'udt:DateTimeType',
+                                cardinality=date_created.user_cardinality,
+                                documentation=date_created.user_annotation)
+
+    def xsd_document_type(self, parent, data_object):
+        references = list(data_object.referenced_by)
+        assert len(references) == 1, (
+            'Unexpected number of references in document {} of {}: {}'.format(
+                data_object.eid, data_object.container[0].eid, references))
+        seq = references[0]
+        self.element_schema(parent, 'Type', 'qdt:CodeDocumentType',
+                            fixed_value=_safe_concept_value(seq.type, self.concepts_language),
+                            xsd_attributes=[LIST_VERSION_ID_2009])
+
+    system_id_tag_name = 'ArchivalAgencyDocumentIdentifier'
+
+    def xsd_system_id(self, parent, data_object):
+        system_id = self.system_id(data_object)
+        if system_id:
+            self.element_schema(parent, self.system_id_tag_name, 'qdt:ArchivesIDType',
+                                cardinality=system_id.user_cardinality,
+                                documentation=system_id.user_annotation,
+                                xsd_attributes=self.xsd_attributes_scheme())
+
+    def xsd_agency(self, parent, agency_type, agency=None):
+        agency_node = self.element_schema(parent, agency_type,
+                                          cardinality=agency.user_cardinality if agency else '1')
+        self.element_schema(agency_node, 'Identification', 'qdt:ArchivesIDType',
+                            fixed_value=self.agency_id(agency) if agency else None,
+                            xsd_attributes=self.xsd_attributes_scheme())
+        self.element_schema(agency_node, 'Name', 'udt:TextType',
+                            fixed_value=self.agency_name(agency) if agency else None,
+                            cardinality='0..1')
+
+    def xsd_archival_agreement(self, parent, transfer):
+        agreement = transfer.archival_agreement
+        if agreement:
+            self.element_schema(parent, 'ArchivalAgreement', 'qdt:ArchivesIDType',
+                                cardinality=agreement.user_cardinality,
+                                documentation=agreement.user_annotation,
+                                fixed_value=agreement.archival_agreement,
+                                xsd_attributes=self.xsd_attributes_scheme())
+
+    def xsd_transferring_agency_archive_identifier(self, parent, content_entity, tag_name):
+        if content_entity.transferring_agency_archive_unit_identifier:
+            agency_entity = content_entity.transferring_agency_archive_unit_identifier
+            self.element_schema(
+                parent, tag_name, 'qdt:ArchiveIDType',
+                fixed_value=agency_entity.transferring_agency_archive_unit_identifier,
+                cardinality=agency_entity.user_cardinality,
+                documentation=agency_entity.user_annotation,
+                xsd_attributes=self.xsd_attributes_scheme())
+
+    def xsd_appraisal_rule(self, parent, appraisal_rule):
+        # XXX cardinality 1 on rule, not multiple + element name : 'Appraisal' ou 'AppraisalRule'
+        # (cf http://www.archivesdefrance.culture.gouv.fr/seda/api/index.html)
+        ar_node = self.element_schema(parent, 'Appraisal',
+                                      cardinality=appraisal_rule.user_cardinality,
+                                      documentation=appraisal_rule.user_annotation,
+                                      xsd_attributes=[XAttr('Id', 'xsd:ID')])
+        ar_code = appraisal_rule.final_action_concept
+        ar_code_value = _concept_value(ar_code, self.concepts_language)
+        self.element_schema(ar_node, 'Code', 'qdt:CodeAppraisalType',
+                            fixed_value=ar_code_value,
+                            xsd_attributes=[LIST_VERSION_ID_2009])
+
+        rule = appraisal_rule.rules[0] if appraisal_rule.rules else None
+        value = _concept_value(rule.rule_concept, self.concepts_language) if rule else None
+        self.element_schema(ar_node, 'Duration', 'qdt:ArchivesDurationType',
+                            fixed_value=value,
+                            documentation=rule.user_annotation if rule else None)
+        self.element_schema(ar_node, 'StartDate', 'udt:DateType')
+
+    access_restriction_tag_name = 'AccessRestrictionRule'
+
+    def xsd_access_rule(self, parent, access_rule):
+        """Append XSD elements for an access restriction to the given parent node."""
+        ar_node = self.element_schema(parent, self.access_restriction_tag_name,
+                                      cardinality=access_rule.user_cardinality,
+                                      documentation=access_rule.user_annotation,
+                                      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
+        self.element_schema(ar_node, 'Code', 'qdt:CodeAccessRestrictionType',
+                            fixed_value=value,
+                            documentation=rule.user_annotation if rule else None,
+                            xsd_attributes=[LIST_VERSION_ID_2009])
+        self.element_schema(ar_node, 'StartDate', 'udt:DateType')
+
+    def xsd_content_description(self, parent, content):
+        """Append XSD elements for a description content to the given parent node"""
+        cd_node = self.element_schema(parent, 'ContentDescription',
+                                      xsd_attributes=[XAttr('Id', 'xsd:ID')])
+        self.xsd_description_level(cd_node, content.description_level_concept)
+        self.xsd_language(cd_node, content)
+        self.xsd_content_dates(cd_node, content)
+        self.xsd_description(cd_node, content)
+        self.xsd_custodial_history(cd_node, content)
+        self.xsd_keywords(cd_node, content)
+        self.xsd_originating_agency(cd_node, content)
+
+    def xsd_language(self, parent, content):
+        # XXX language is 0..1 in SEDA 2, 1..n in earlier version
+        language = content.language.concept if content.language else None
+        self.element_schema(parent, 'Language', 'qdt:CodeLanguageType',
+                            fixed_value=_concept_value(language, self.concepts_language),
+                            xsd_attributes=[LIST_VERSION_ID_2009])
+
+    def xsd_content_dates(self, parent, content):
+        for seda2_name, seda1_name in (('end', 'latest'), ('start', 'oldest')):
+            date_entity = getattr(content, '%s_date' % seda2_name)
+            if date_entity:
+                self.element_schema(parent, '%sDate' % seda1_name.capitalize(), 'udt:DateType',
+                                    cardinality=date_entity.user_cardinality,
+                                    documentation=date_entity.user_annotation)
+
+    def xsd_description_level(self, parent, concept):
+        """Append XSD elements for a description level to the given parent node"""
+        value = _concept_value(concept, self.concepts_language)
+        self.element_schema(parent, 'DescriptionLevel', 'qdt:CodeDescriptionLevelType',
+                            fixed_value=value,
+                            xsd_attributes=[LIST_VERSION_ID_2009])
+
+    def xsd_description(self, parent, content):
+        """Append XSD elements for a description to the given parent node"""
+        if content.description:
+            self.element_schema(parent, 'Description', 'udt:TextType',
+                                cardinality=content.description.user_cardinality,
+                                documentation=content.description.user_annotation,
+                                fixed_value=content.description.description,
+                                xsd_attributes=[XAttr('languageID', 'xsd:language')])
+
+    def xsd_keywords(self, parent, content):
+        for keyword in 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:
+                when_card = item.when.user_cardinality if item.when else None
+                self.element_schema(ch_node, 'CustodialHistoryItem', 'qdt:CustodialHistoryItemType',
+                                    cardinality=item.user_cardinality,
+                                    documentation=item.user_annotation,
+                                    xsd_attributes=[XAttr('when', 'udt:DateType',
+                                                          cardinality=when_card),
+                                                    XAttr('languageID', 'xsd:language')])
+
+    def xsd_originating_agency(self, parent, content):
+        if content.originating_agency:
+            self.xsd_agency(parent, 'OriginatingAgency', content.originating_agency)
+
+    # 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 = [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.element_schema(parent, self.kw_tag_name,
+                                      cardinality=keyword.user_cardinality,
+                                      documentation=keyword.user_annotation,
+                                      xsd_attributes=[XAttr('Id', 'xsd:ID')])
+        content = keyword.keyword_content
+        url = None
+        if keyword.reference:
+            concept = keyword.reference.concept
+            if concept:
+                url = self.cwuri_url(concept)
+                if content is None:
+                    content = concept.label()
+        self.element_schema(kw_node, 'KeywordContent', self.kw_content_tag_type,
+                            fixed_value=content,
+                            xsd_attributes=self.kw_content_tag_attributes)
+        if keyword.reference:
+            scheme = keyword.reference.scheme
+            self.element_schema(kw_node, 'KeywordReference', 'qdt:ArchivesIDType',
+                                cardinality=keyword.reference.user_cardinality,
+                                documentation=keyword.reference.user_annotation,
+                                fixed_value=url,
+                                xsd_attributes=self.xsd_attributes_scheme(scheme))
+        if keyword.type:
+            self.element_schema(kw_node, 'KeywordType', 'qdt:CodeKeywordType',
+                                cardinality=keyword.type.user_cardinality,
+                                documentation=keyword.type.user_annotation,
+                                fixed_value=_concept_value(keyword.type.concept,
+                                                           self.concepts_language),
+                                xsd_attributes=[LIST_VERSION_ID_2009])
+
+    @classmethod
+    def xsd_attributes_scheme(cls, scheme=None):
+        """Return a list of :class:`XAttr` for a scheme definition, with some proper values
+        specified if a scheme is given.
+        """
+        attributes = [
+            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:
+        #     # schemeID XXX move to saem
+        #     attributes[0]['fixed'] = scheme.ark
+        #     attributes[0]['use'] = 'required'
+        return attributes
+
+    def archive_unit_access_rule(self, archive_unit):
+        for rule_holder in climb_rule_holders(archive_unit):
+            if rule_holder.reverse_seda_access_rule:
+                return rule_holder.reverse_seda_access_rule[0]
+        return None
+
+    def archive_unit_name(self, archive_unit):
+        seq = archive_unit.first_level_choice.content_sequence
+        return seq.title
+
+    def archive_unit_appraisal_rule(self, archive_unit):
+        for rule_holder in climb_rule_holders(archive_unit):
+            if rule_holder.reverse_seda_appraisal_rule:
+                return rule_holder.reverse_seda_appraisal_rule[0]
+        return None
+
+    def agency_name(self, agency):
+        return agency.agency.dc_title() if agency and agency.agency else None
+
+    def agency_id(self, agency):
+        return text_type(agency.agency.eid) if agency and agency.agency else None
+
+    def archive_unit_content(self, archive_unit):
+        return archive_unit.first_level_choice.content_sequence
+
+    def system_id(self, data_object):
+        return (data_object.cw_adapt_to('ITreeBase').parent().first_level_choice.content_sequence
+                .system_id)
+
+    @staticmethod
+    def cwuri_url(entity):
+        """Return "public" URI for the given entity.
+
+        In a staticmethod to ease overriding in subclasses (eg saem).
+        """
+        return entity.cwuri
+
+
+class SEDA02XSDExport(SEDA1XSDExport):
+    """Adapter to build an XSD representation of a SEDA profile, using SEDA 0.2 specification"""
+    __regid__ = 'SEDA-0.2.xsd'
+
+    namespaces = SEDA1XSDExport.namespaces.copy()
+    namespaces[None] = 'fr:gouv:ae:archive:draft:standard_echange_v0.2'
+    namespaces['qdt'] = 'fr:gouv:ae:archive:draft:standard_echange_v0.2:QualifiedDataType:1'
+    namespaces['udt'] = 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:6'
+
+    root_attributes = SEDA1XSDExport.root_attributes.copy()
+    root_attributes['targetNamespace'] = 'fr:gouv:ae:archive:draft:standard_echange_v0.2'
+    root_attributes['version'] = '1.1'
+
+    def xsd_archive(self, parent, archive_unit):
+        """Append XSD elements for an archive to the given parent node."""
+        archive_node = self.element_schema(parent, 'Contains',
+                                           cardinality=archive_unit.user_cardinality,
+                                           documentation=archive_unit.user_annotation,
+                                           xsd_attributes=[XAttr('Id', 'xsd:ID')])
+        transfer = archive_unit.cw_adapt_to('ITreeBase').parent()
+        self.xsd_archival_agreement(archive_node, transfer)
+        # hard-coded description's language
+        self.element_schema(archive_node, 'DescriptionLanguage', 'qdt:CodeLanguageType',
+                            fixed_value='fr',
+                            xsd_attributes=[LIST_VERSION_ID_2009])
+        # 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)
+        name_entity = self.archive_unit_name(archive_unit)
+        self.element_schema(archive_node, 'Name', 'udt:TextType',
+                            fixed_value=name_entity.title,
+                            documentation=name_entity.user_annotation,
+                            xsd_attributes=[XAttr('languageID', 'xsd:language')])
+        self.xsd_transferring_agency_archive_identifier(archive_node, content_entity,
+                                                        'TransferringAgencyArchiveIdentifier')
+        self.xsd_content_description(archive_node, content_entity)
+
+        appraisal_rule_entity = self.archive_unit_appraisal_rule(archive_unit)
+        if appraisal_rule_entity:
+            self.xsd_appraisal_rule(archive_node, appraisal_rule_entity)
+        # in SEDA 0.2, access restriction is not mandatory
+        access_rule_entity = self.archive_unit_access_rule(archive_unit)
+        if access_rule_entity:
+            self.xsd_access_rule(archive_node, access_rule_entity)
+        self.xsd_children(archive_node, archive_unit)
+        return archive_node
+
+    def xsd_archive_object(self, parent, archive_unit):
+        """Append XSD elements for the archive object to the given parent node."""
+        ao_node = super(SEDA02XSDExport, self).xsd_archive_object(parent, archive_unit)
+        # in SEDA 0.2, description level is on the archive object element, not on its content
+        # description
+        content_entity = self.archive_unit_content(archive_unit)
+        self.xsd_description_level(ao_node, content_entity.description_level_concept)
+        # actually DescriptionLevel should be before Name (ie. currently the last and first
+        # elements)
+        ao_node.insert(0, ao_node[-1])
+
+    def xsd_document(self, parent, data_object):
+        """Append XSD elements for the document to the given parent node."""
+        document_node = self.element_schema(parent, 'Document',
+                                            cardinality=data_object.user_cardinality,
+                                            documentation=data_object.user_annotation,
+                                            xsd_attributes=[XAttr('Id', 'xsd:ID')])
+
+        self.xsd_attachment(document_node, data_object)
+        self.xsd_date_created(document_node, data_object)
+        self.xsd_system_id(document_node, data_object)
+        self.xsd_document_type(document_node, data_object)
+
+    def xsd_content_description(self, parent, content):
+        """Append XSD elements for a description content to the given parent node"""
+        cd_node = self.element_schema(parent, 'ContentDescription',
+                                      xsd_attributes=[XAttr('Id', 'xsd:ID')])
+        self.xsd_custodial_history(cd_node, content)
+        self.xsd_language(cd_node, content)
+        self.xsd_content_dates(cd_node, content)
+        self.xsd_description(cd_node, content)
+        self.xsd_originating_agency(cd_node, content)
+        self.xsd_keywords(cd_node, content)
+
+    def xsd_custodial_history(self, parent, content):
+        if content.custodial_history_items:
+            item = content.custodial_history_items[0]
+            self.element_schema(parent, 'CustodialHistory', 'udt:TextType',
+                                cardinality=item.user_cardinality,
+                                documentation=item.user_annotation,
+                                xsd_attributes=[XAttr('languageID', 'xsd:language')])
+
+    system_id_tag_name = 'Identification'
+    # in SEDA 0.2, ArchiveObject tag name is 'Contains' (as for Archive)
+    archive_object_tag_name = 'Contains'
+    # in SEDA 0.2, AccessRestrictionRule tag name is 'AccessRestriction'
+    access_restriction_tag_name = 'AccessRestriction'
+    # in SEDA 0.2, keyword tag name is 'ContentDescriptive', not 'Keyword' and keyword content type
+    # is TextType and there is no 'role' attribute
+    kw_tag_name = 'ContentDescriptive'
+    kw_content_tag_type = 'udt:TextType'
+    kw_content_tag_attributes = [XAttr('languageID', 'xsd:language')]
+
+
+class OldSEDARNGExportMixin(RNGMixin):
+
+    def element_schema(self, parent, name, xsd_type=None, fixed_value=None, cardinality='1',
+                       documentation=None, xsd_attributes=()):
+        attributes = {'name': name}
+        if documentation:
+            attributes['documentation'] = documentation
+        parent = self.rng_element_parent(parent, *minmax_cardinality(cardinality))
+        element = self.element('rng:element', parent, attributes)
+        for xattr in xsd_attributes:
+            self.attribute_schema(element, xattr)
+
+        if xsd_type is not None:
+            self.rng_value(element, xsd_type, fixed_value)
+        else:
+            assert fixed_value is None
+        return element
+
+    def attribute_schema(self, parent, xattr):
+        if xattr.cardinality is None:
+            return  # XXX prohibit?
+        minimum = minmax_cardinality(xattr.cardinality, ('0..1', '1'))[0]
+        parent = self.rng_element_parent(parent, minimum)
+        attr = self.element('rng:attribute', parent, {'name': xattr.name})
+        self.rng_value(attr, xattr.qualified_type, xattr.fixed_value)
+
+    def dump_etree(self):
+        """Return an RNG etree for the adapted SEDA profile."""
+        root = self.element('rng:grammar', attributes=self.root_attributes)
+        start = self.element('rng:start', root)
+        self.xsd_transfer(start, self.entity)
+        return root
+
+
+class SEDA1RNGExport(OldSEDARNGExportMixin, SEDA1XSDExport):
+    """Adapter to build an RNG representation of a simplified SEDA profile, using SEDA 1.0
+    specification.
+    """
+    __regid__ = 'SEDA-1.0.rng'
+
+    namespaces = SEDA1XSDExport.namespaces.copy()
+    namespaces['rng'] = 'http://relaxng.org/ns/structure/1.0'
+    root_attributes = {
+        'ns': 'fr:gouv:culture:archivesdefrance:seda:v1.0',
+        'datatypeLibrary': 'http://www.w3.org/2001/XMLSchema-datatypes',
+    }
+
+
+class SEDA02RNGExport(OldSEDARNGExportMixin, SEDA02XSDExport):
+    """Adapter to build an RNG representation of a simplified SEDA profile, using SEDA 0.2
+    specification.
+    """
+    __regid__ = 'SEDA-0.2.rng'
+
+    namespaces = SEDA1XSDExport.namespaces.copy()
+    namespaces['rng'] = 'http://relaxng.org/ns/structure/1.0'
+    root_attributes = {
+        '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
+    are associated to the entity holding their cardinality, which may be either the given entity or
+    an intermediary entity.
+    """
+    is_simple = len(path) == 1
+    if is_simple:
+        (rtype, role, target_etype, _), = path
+        return _simple_path_target_values(entity, rtype, role, target_etype)
+    else:
+        return _complex_path_target_values(entity, path)
+
+
+def _simple_path_target_values(entity, rtype, role, target_etype):
+    if target_etype in BASE_TYPES:
+        if rtype == 'id':
+            return [(None, eid2xmlid(entity.eid))]
+        return [(None, getattr(entity, rtype, None))]
+    targets = 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
+    # (set explicitly in schema/__init__.py)
+    if rtype != 'seda_data_object_reference_id' and rdefschema.composite:
+        return [(target, target) for target in targets]
+    elif targets:
+        return [(None, target) for target in targets]
+    else:
+        return [(None, None)]
+
+
+def _complex_path_target_values(entity, path):
+    rschema = entity._cw.vreg.schema.rschema
+    entities = (entity,)
+    rtype_targets = []
+    for rtype, role, target_etype, _ in path:
+        try:
+            rdefschema = next(iter(rschema(rtype).rdefs.values()))
+        except KeyError:
+            if rtype in SKIP_ATTRS:
+                return [(e, None) for e in entities]
+            # element is still in the intermediary representation but not in the schema
+            return [(None, None)]
+        rtype_targets = []
+        for entity in entities:
+            if target_etype in BASE_TYPES:
+                rtype_targets.append((entity, getattr(entity, rtype, None)))
+            else:
+                try:
+                    targets = 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
+                    rtype_targets.append((entity, None))
+                    continue
+                if targets:
+                    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:
+                    rtype_targets.append((entity, None))
+        entities = [v for e, v in rtype_targets]
+    return rtype_targets
+
+
+def xselement_scheme_attribute(xselement):
+    try:
+        return xselement.attributes['listSchemeURI'].target.local_name
+    except KeyError:
+        return xselement.attributes['schemeURI'].target.local_name
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/hooks.py	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,350 @@
+# copyright 2016-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-seda hooks"""
+
+from collections import defaultdict
+import itertools
+
+from yams import ValidationError
+from yams.schema import role_name
+
+from cubicweb import _
+from cubicweb.predicates import is_instance, score_entity
+from cubicweb.server import hook
+
+from .entities import rule_type_from_etype, diag
+from .entities.generated import CHOICE_RTYPE
+
+
+SEDA_PARENT_RTYPES = {}
+CHOICE_RTYPE_ROLE = dict(itertools.chain(*CHOICE_RTYPE.values()))
+CHOICE_RTYPES = set(CHOICE_RTYPE_ROLE)
+
+
+class SetContainerOp(hook.DataOperationMixIn, hook.Operation):
+    def precommit_event(self):
+        cnx = self.cnx
+        # build a mapping <eid>: <container eid>
+        containers = {}
+        data = dict(self.get_data())  # data is a 2-uple (eid, parent eid)
+        for eid in data:
+            # search for the container from the parent
+            peid = data[eid]
+            # climb up to the uppermost parent
+            while peid in data:
+                peid = data[peid]
+            entity = cnx.entity_from_eid(peid)
+            # we may run into caching issue if this operation is triggered several times in a
+            # transaction
+            entity.cw_clear_all_caches()
+            adapter = entity.cw_adapt_to('IContained')
+            if adapter is None:  # we reached the container
+                containers[eid] = peid
+            else:  # the adapter must know the container at this point
+                if adapter.container is not None:
+                    containers[eid] = adapter.container.eid
+                else:
+                    assert entity.cw_adapt_to('IContainer'), \
+                        "can't retrieve container for entity %s" % entity
+                    containers[eid] = peid
+        # turn previous mapping into <container eid>: [<list of contained eids>]
+        contained = defaultdict(list)
+        for eid, ceid in containers.items():
+            contained[ceid].append(eid)
+        # and uses it to insert data using raw SQL for performance
+        cursor = cnx.cnxset.cu
+        for ceid, eids in contained.items():
+            args = [{'x': eid} for eid in eids]
+            cursor.executemany('INSERT INTO container_relation(eid_from, eid_to)'
+                               ' SELECT %%(x)s, %(c)s'
+                               ' WHERE NOT EXISTS(SELECT 1 FROM container_relation'
+                               ' WHERE eid_from=%%(x)s AND eid_to=%(c)s)' % {'c': ceid}, args)
+
+
+class SetContainerHook(hook.Hook):
+    __regid__ = 'seda.graph.updated'
+    __select__ = hook.Hook.__select__ & hook.match_rtype_sets(SEDA_PARENT_RTYPES)
+    events = ('before_add_relation',)
+    category = 'metadata'
+
+    def __call__(self):
+        if SEDA_PARENT_RTYPES[self.rtype] == 'subject':
+            SetContainerOp.get_instance(self._cw).add_data((self.eidfrom, self.eidto))
+        else:
+            SetContainerOp.get_instance(self._cw).add_data((self.eidto, self.eidfrom))
+
+
+class CheckRefNonRuleIdCodeListHook(hook.Hook):
+    """Watch for addition of concept through seda_ref_non_rule_id_to relation, to ensure it belongs
+    to the scheme specified on the transfer
+
+    This depends on the parent entity type and can not properly be handled with a rql constraint,
+    since it would require several disjoint constraints, while cw's semantic is that all constraints
+    should be matched.
+    """
+    __regid__ = 'seda.schema.ref_non_rule_id'
+    __select__ = hook.Hook.__select__ & hook.match_rtype('seda_ref_non_rule_id_to')
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        CheckRefNonRuleIdCodeListOp(self._cw, parent=self.eidfrom, concept=self.eidto)
+
+
+# Late operation to be called once `container` relation is set
+class CheckRefNonRuleIdCodeListOp(hook.LateOperation):
+
+    def precommit_event(self):
+        parent = self.cnx.entity_from_eid(self.parent, etype='SEDARefNonRuleId')
+        # generate constraint on the concept's scheme depending on the parent's parent type
+        parent_parent = parent.cw_adapt_to('IContained').parent
+        rule_type = rule_type_from_etype(parent_parent.cw_etype)
+        rql = ('Any C WHERE C eid %(c)s, C in_scheme CS, X eid %(x)s, X container AT, '
+               'CACLV seda_{0}_rule_code_list_version_from AT, '
+               'CACLV seda_{0}_rule_code_list_version_to CS'.format(rule_type))
+        if not self.cnx.execute(rql, {'c': self.concept, 'x': parent_parent.eid}):
+            msg = _("this concept doesn't belong to scheme specified on the profile")
+            raise ValidationError(parent.eid,
+                                  {role_name('seda_ref_non_rule_id_to', 'subject'): msg})
+
+
+class EnsureChoiceNotEmptyOp(hook.DataOperationMixIn, hook.Operation):
+    """Make sure that, when a choice element is created/updated, it is not empty."""
+
+    def precommit_event(self):
+        for entity in self.get_data():
+            if self.cnx.deleted_in_transaction(entity.eid):
+                continue
+            rtype_roles = CHOICE_RTYPE[entity.cw_etype]
+            for rtype, role in rtype_roles:
+                if entity.related(rtype, role=role):
+                    break
+            else:
+                msg = _('An alternative cannot be empty')
+                raise ValidationError(entity.eid, {'': msg})
+
+
+class EnsureChoiceNotEmptyAtCreationHook(hook.Hook):
+    """Make sure that, when a choice element is created, it is not empty."""
+
+    __regid__ = 'seda.choice.creation.ensure-not-empty'
+    __select__ = hook.Hook.__select__ & score_entity(lambda x: x.cw_etype.startswith('SEDAAlt'))
+    events = ('before_add_entity',)
+
+    def __call__(self):
+        EnsureChoiceNotEmptyOp.get_instance(self._cw).add_data(self.entity)
+
+
+class EnsureChoiceNotEmptyAtRTypeDeletionHook(hook.Hook):
+    """Make sure that, when a relation with a choice element is deleted, the choice remains not
+    empty."""
+
+    __regid__ = 'seda.choice.relation-deletion.ensure-not-empty'
+    __select__ = hook.Hook.__select__ & hook.match_rtype_sets(CHOICE_RTYPES)
+    events = ('before_delete_relation',)
+
+    def __call__(self):
+        eid = {'object': self.eidto, 'subject': self.eidfrom}[CHOICE_RTYPE_ROLE[self.rtype]]
+        choice = self._cw.entity_from_eid(eid)
+        if choice.cw_etype in CHOICE_RTYPE:
+            # Else this is an ambiguous relation, which in this case isn't
+            # targetting a choice.
+            EnsureChoiceNotEmptyOp.get_instance(self._cw).add_data(choice)
+
+
+class SetDefaultCodeListVersionsHook(hook.Hook):
+    """Hook triggering an operation to set sensible default values for a transfer's code list
+    versions.
+    """
+    __regid__ = 'seda.transfer.default-code-lists'
+    __select__ = hook.Hook.__select__ & is_instance('SEDAArchiveTransfer')
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        SetDefaultCodeListVersionsOp(self._cw, transfer_eid=self.entity.eid)
+
+
+class SetDefaultCodeListVersionsOp(hook.Operation):
+    """Set sensible default values for a transfer's code list versions."""
+    simple_rtypes = [
+        ('seda_file_format_code_list_version', 'seda_format_id_to'),
+        # ('seda_message_digest_algorithm_code_list_version'),
+    ]
+    # XXX factorize with data structure in dataimport
+    complex_rtypes = [
+        ('seda_mime_type_code_list_version', 'seda_mime_type_to', None),
+        ('seda_encoding_code_list_version', 'seda_encoding_to', None),
+        ('seda_data_object_version_code_list_version', 'seda_data_object_version_to', None),
+        ('seda_relationship_code_list_version', 'seda_type_relationship', None),
+        ('seda_access_rule_code_list_version', 'seda_rule', 'SEDASeqAccessRuleRule'),
+        ('seda_appraisal_rule_code_list_version', 'seda_rule', 'SEDASeqAppraisalRuleRule'),
+        # 'seda_compression_algorithm_code_list_version',
+        # 'seda_classification_rule_code_list_version',
+        # 'seda_reuse_rule_code_list_version',
+        # 'seda_dissemination_rule_code_list_version',
+        # 'seda_storage_rule_code_list_version',
+    ]
+
+    def precommit_event(self):
+        transfer = self.transfer_eid
+        cnx = self.cnx
+        for rtype, ref_rtype in self.simple_rtypes:
+            cnx.execute('SET T {rtype} CS WHERE NOT T {rtype} CS, T eid %(t)s, '
+                        'CS scheme_relation_type RT, RT name %(rt)s'.format(rtype=rtype),
+                        {'t': transfer, 'rt': ref_rtype})
+        for rtype, ref_rtype, ref_etype in self.complex_rtypes:
+            etype = 'SEDA' + ''.join(word.capitalize() for word in rtype.split('_')[1:])
+            rql = ('INSERT {etype} X: X {rtype}_from T, X {rtype}_to CS '
+                   'WHERE NOT Y {rtype}_from T, T eid %(t)s, '
+                   'CS scheme_relation_type RT, RT name %(rt)s')
+            if ref_etype is not None:
+                rql += ', CS scheme_entity_type ET, ET name %(et)s'
+            cnx.execute(rql.format(etype=etype, rtype=rtype),
+                        {'t': transfer, 'rt': ref_rtype, 'et': ref_etype})
+
+
+class SetDefaultDataObjectRefCardinalityHook(hook.Hook):
+    """Hook triggering an operation to set cardinality to 1 on creation of a data object 'typed'
+    reference.
+    """
+    __regid__ = 'seda.doref.default'
+    __select__ = hook.Hook.__select__ & is_instance('SEDADataObjectReference')
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        SetDefaultDataObjectRefCardinalityOp(self._cw, do_ref_eid=self.entity.eid)
+
+
+class SetDefaultDataObjectRefCardinalityOp(hook.Operation):
+    """Set cardinality to 1 on creation of a data object 'typed' reference."""
+
+    def precommit_event(self):
+        do_ref = self.cnx.entity_from_eid(self.do_ref_eid)
+        parent = do_ref.seda_data_object_reference[0]
+        if parent.cw_etype != 'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement':
+            do_ref.cw_set(user_cardinality=u'1')
+
+
+class CheckProfileSEDACompatiblityOp(hook.DataOperationMixIn, hook.LateOperation):
+    """Data operation that will check compatibility of a SEDA profile upon modification.
+
+    This is a late operation since it has to be executed once the 'container' relation is set.
+    """
+
+    def precommit_event(self):
+        cnx = self.cnx
+        profiles = set()
+        for eid in self.get_data():
+            if cnx.deleted_in_transaction(eid):
+                continue
+            entity = cnx.entity_from_eid(eid)
+            if entity.cw_etype == 'SEDAArchiveTransfer':
+                profiles.add(entity)
+            else:
+                container = entity.cw_adapt_to('IContained').container
+                if container is not None and container.cw_etype == 'SEDAArchiveTransfer':
+                    profiles.add(container)
+        with cnx.deny_all_hooks_but():
+            for profile in profiles:
+                adapter = profile.cw_adapt_to('ISEDACompatAnalyzer')
+                supported_formats = adapter.diagnose()
+                profile.cw_set(compat_list=u', '.join(sorted(supported_formats)))
+                if profile.simplified_profile and 'simplified' not in supported_formats:
+                    raise ValidationError(
+                        profile.eid, {'': _("This profile can't be turned to simplified. "
+                                            "See the diagnostic tab for more information")})
+
+    def add_entity(self, entity):
+        """Add entity, its parent entities (up to the container root) for update of their
+        modification date at commit time.
+        """
+        self.add_data(entity.eid)
+        while True:
+            safety_belt = set((entity.eid,))
+            contained = entity.cw_adapt_to('IContained')
+            if contained is None:
+                assert entity.cw_adapt_to('IContainer')
+                break
+            else:
+                entity = contained.container
+                if entity is None or entity.eid in safety_belt:
+                    break
+                self.add_data(entity.eid)
+                safety_belt.add(entity.eid)
+
+
+class CheckNewProfile(hook.Hook):
+    """Instantiate operation checking for profile seda compat on its creation"""
+    __regid__ = 'seda.transfer.created.checkcompat'
+    __select__ = hook.Hook.__select__ & is_instance('SEDAArchiveTransfer')
+    events = ('after_add_entity', )
+    category = 'metadata'
+
+    def __call__(self):
+        CheckProfileSEDACompatiblityOp.get_instance(self._cw).add_entity(self.entity)
+
+
+WATCH_RTYPES_SET = set().union(*((rtype for rtype in rule.watch
+                                  # filter out (entity type, attribute)
+                                  if not isinstance(rtype, tuple))
+                                 for rule in diag.RULES.values()))
+
+
+class AddOrRemoveChildrenHook(hook.Hook):
+    """Some relation involved in diagnosis of the profile is added or removed."""
+    __regid__ = 'seda.transfer.relupdated.checkcompat'
+    __select__ = hook.Hook.__select__ & hook.match_rtype_sets(WATCH_RTYPES_SET)
+    events = ('before_add_relation', 'before_delete_relation')
+    category = 'metadata'
+
+    def __call__(self):
+        op = CheckProfileSEDACompatiblityOp.get_instance(self._cw)
+        op.add_entity(self._cw.entity_from_eid(self.eidfrom))
+        op.add_entity(self._cw.entity_from_eid(self.eidto))
+
+
+WATCH_ETYPES = defaultdict(set)
+for rule in diag.RULES.values():
+    for etype_attr in rule.watch:
+        if isinstance(etype_attr, tuple):
+            WATCH_ETYPES[etype_attr[0]].add(etype_attr[1])
+
+WATCH_ETYPES['SEDAArchiveTransfer'].add('simplified_profile')
+
+
+class UpdateWatchedProfileElementHook(hook.Hook):
+    """Some entity involved in diagnosis of the profile is created or updated."""
+    __regid__ = 'seda.transfer.updated.checkcompat'
+    __select__ = hook.Hook.__select__ & is_instance(*WATCH_ETYPES)
+    events = ('before_add_entity', 'before_update_entity')
+    category = 'metadata'
+
+    def __call__(self):
+        if WATCH_ETYPES[self.entity.cw_etype] & set(self.entity.cw_edited):
+            CheckProfileSEDACompatiblityOp.get_instance(self._cw).add_entity(self.entity)
+
+
+def registration_callback(vreg):
+    from cubicweb.server import ON_COMMIT_ADD_RELATIONS
+    from cubicweb_seda import seda_profile_container_def, iter_all_rdefs
+
+    vreg.register_all(globals().values(), __name__)
+
+    for etype, parent_rdefs in seda_profile_container_def(vreg.schema):
+        for rtype, role in parent_rdefs:
+            if SEDA_PARENT_RTYPES.setdefault(rtype, role) != role:
+                raise Exception('inconsistent relation', rtype, role)
+    for rdef, role in iter_all_rdefs(vreg.schema, 'SEDAArchiveTransfer'):
+        ON_COMMIT_ADD_RELATIONS.add(str(rdef.rtype))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_seda/i18n/en.po	Thu Feb 23 16:17:34 2017 +0100
@@ -0,0 +1,8766 @@
+msgid ""
+msgstr ""
+"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgid " ALT_I18N "
+msgstr ""
+
+msgid ", no relationship type specified"
+msgstr ""
+
+#, python-format
+msgid ", of relationship type %s"
+msgstr ""
+
+#, python-brace-format
+msgid ", using {algorithm}"
+msgstr ""
+
+msgid "0..1"
+msgstr ""
+
+msgid "0..n"
+msgstr ""
+
+msgid "1"
+msgstr ""
+
+msgid "1..n"
+msgstr ""
+
+msgid "<no classification level specified>"
+msgstr ""
+
+msgid "<no classification owner specified>"
+msgstr ""
+
+msgid "<no data-object specified>"
+msgstr ""
+
+msgid "<no final action specified>"
+msgstr ""
+
+msgid "<no reference specified>"
+msgstr ""
+
+msgid "<no rule specified>"
+msgstr ""
+
+msgid "<no rule to prevent specified>"
+msgstr ""
+
+msgid "<no type specified>"
+msgstr ""
+
+msgid "<no unit specified>"
+msgstr ""
+
+msgid "<no value specified>"
+msgstr ""
+
+msgid "<not specified>"
+msgstr ""
+
+msgid "Alternative"
+msgstr ""
+
+msgid "An alternative cannot be empty"
+msgstr ""
+
+msgid "Archive unit component tree"
+msgstr ""
+
+msgid "Archive unit reference are only supported by SEDA 2."
+msgstr ""
+
+msgid ""
+"Custodial history is text with SEDA 0.2, hence date information isn't "
+"considered."
+msgstr ""
+
+msgid ""
+"Custodial history is text with SEDA 0.2, hence only one item element is "
+"considered."
+msgstr ""
+
+msgid "Entity"
+msgstr ""
+
+#, python-brace-format
+msgid "Export to SEDA version {0} is not possible."
+msgstr ""
+
+msgid ""
+"First level archive unit must have an associated access rule to be "
+"exportable in SEDA 1. You may define it on the archive unit or as a default "
+"rule on the transfer."
+msgstr ""
+
+msgid "HTML documentation"
+msgstr ""
+
+msgid "Impacted formats"
+msgstr ""
+
+msgid "Inner rule has cardinality other than 1."
+msgstr ""
+
+msgid "Inner rule has no start date."
+msgstr ""
+
+msgid "Message"
+msgstr ""
+
+msgid "New SEDAAccessRule"
+msgstr ""
+
+msgid "New SEDAAccessRuleCodeListVersion"
+msgstr ""
+
+msgid "New SEDAAcquiredDate"
+msgstr ""
+
+msgid "New SEDAAddressee"
+msgstr ""
+
+msgid "New SEDAAltAccessRulePreventInheritance"
+msgstr ""
+
+msgid "New SEDAAltAppraisalRulePreventInheritance"
+msgstr ""
+
+msgid "New SEDAAltArchiveUnitArchiveUnitRefId"
+msgstr ""
+
+msgid "New SEDAAltBinaryDataObjectAttachment"
+msgstr ""
+
+msgid "New SEDAAltClassificationRulePreventInheritance"
+msgstr ""
+
+msgid "New SEDAAltDisseminationRulePreventInheritance"
+msgstr ""
+
+msgid "New SEDAAltIsPartOfArchiveUnitRefId"
+msgstr ""
+
+msgid "New SEDAAltIsVersionOfArchiveUnitRefId"
+msgstr ""
+
+msgid "New SEDAAltReferencesArchiveUnitRefId"
+msgstr ""
+
+msgid "New SEDAAltReplacesArchiveUnitRefId"
+msgstr ""
+
+msgid "New SEDAAltRequiresArchiveUnitRefId"
+msgstr ""
+
+msgid "New SEDAAltReuseRulePreventInheritance"
+msgstr ""
+
+msgid "New SEDAAltStorageRulePreventInheritance"
+msgstr ""
+
+msgid "New SEDAAppraisalRule"
+msgstr ""
+
+msgid "New SEDAAppraisalRuleCodeListVersion"
+msgstr ""
+
+msgid "New SEDAArchivalAgencyArchiveUnitIdentifier"
+msgstr ""
+
+msgid "New SEDAArchivalAgreement"
+msgstr ""
+
+msgid "New SEDAArchiveTransfer"
+msgstr ""
+
+msgid "New SEDAArchiveUnit"
+msgstr "New archive unit"
+
+msgid "New SEDAArchiveUnitRefId"
+msgstr ""
+
+msgid "New SEDAAttachment"
+msgstr ""
+
+msgid "New SEDAAuthorizedAgent"
+msgstr ""
+
+msgid "New SEDABinaryDataObject"
+msgstr ""
+
+msgid "New SEDAClassificationReassessingDate"
+msgstr ""
+
+msgid "New SEDAClassificationRule"
+msgstr ""
+
+msgid "New SEDAClassificationRuleCodeListVersion"
+msgstr ""
+
+msgid "New SEDAComment"
+msgstr ""
+
+msgid "New SEDACompressed"
+msgstr ""
+
+msgid "New SEDACompressionAlgorithmCodeListVersion"
+msgstr ""
+
+msgid "New SEDACreatedDate"
+msgstr ""
+
+msgid "New SEDACreatingApplicationName"
+msgstr ""
+
+msgid "New SEDACreatingApplicationVersion"
+msgstr ""
+
+msgid "New SEDACreatingOs"
+msgstr ""
+
+msgid "New SEDACreatingOsVersion"
+msgstr ""
+
+msgid "New SEDACustodialHistoryFile"
+msgstr ""
+
+msgid "New SEDACustodialHistoryItem"
+msgstr ""
+
+msgid "New SEDADataObjectReference"
+msgstr ""
+
+msgid "New SEDADataObjectVersion"
+msgstr ""
+
+msgid "New SEDADataObjectVersionCodeListVersion"
+msgstr ""
+
+msgid "New SEDADateCreatedByApplication"
+msgstr ""
+
+msgid "New SEDADepth"
+msgstr ""
+
+msgid "New SEDADescription"
+msgstr ""
+
+msgid "New SEDADescriptionLanguage"
+msgstr ""
+
+msgid "New SEDADiameter"
+msgstr ""
+
+msgid "New SEDADisseminationRule"
+msgstr ""
+
+msgid "New SEDADisseminationRuleCodeListVersion"
+msgstr ""
+
+msgid "New SEDADocumentType"
+msgstr ""
+
+msgid "New SEDAEncoding"
+msgstr ""
+
+msgid "New SEDAEncodingCodeListVersion"
+msgstr ""
+
+msgid "New SEDAEndDate"
+msgstr ""
+
+msgid "New SEDAEvent"
+msgstr ""
+
+msgid "New SEDAEventDetail"
+msgstr ""
+
+msgid "New SEDAEventIdentifier"
+msgstr ""
+
+msgid "New SEDAEventType"
+msgstr ""
+
+msgid "New SEDAFilePlanPosition"
+msgstr ""
+
+msgid "New SEDAFormatId"
+msgstr ""
+
+msgid "New SEDAFormatLitteral"
+msgstr ""
+
+msgid "New SEDAGpsAltitude"
+msgstr ""
+
+msgid "New SEDAGpsAltitudeRef"
+msgstr ""
+
+msgid "New SEDAGpsDateStamp"
+msgstr ""
+
+msgid "New SEDAGpsLatitude"
+msgstr ""
+
+msgid "New SEDAGpsLatitudeRef"
+msgstr ""
+
+msgid "New SEDAGpsLongitude"
+msgstr ""
+
+msgid "New SEDAGpsLongitudeRef"
+msgstr ""
+
+msgid "New SEDAGpsVersionID"
+msgstr ""
+
+msgid "New SEDAHeight"
+msgstr ""
+
+msgid "New SEDAIsPartOf"
+msgstr ""
+
+msgid "New SEDAIsVersionOf"
+msgstr ""
+
+msgid "New SEDAJuridictional"
+msgstr ""
+
+msgid "New SEDAKeyword"
+msgstr ""
+
+msgid "New SEDAKeywordReference"
+msgstr ""
+
+msgid "New SEDAKeywordType"
+msgstr ""
+
+msgid "New SEDALanguage"
+msgstr ""
+
+msgid "New SEDALastModified"
+msgstr ""
+
+msgid "New SEDALengt