]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.export.ui/src/org/simantics/export/ui/OptionsPage.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.export.ui / src / org / simantics / export / ui / OptionsPage.java
index b796f1b11cb1810e3191c68bbddef085fdaaee77..9d2cea02042eb8432d75f52c03c1a08323a57dce 100644 (file)
-package org.simantics.export.ui;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.TreeMap;\r
-import java.util.TreeSet;\r
-import java.util.regex.Matcher;\r
-import java.util.regex.Pattern;\r
-\r
-import org.eclipse.jface.wizard.WizardPage;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.custom.ScrolledComposite;\r
-import org.eclipse.swt.layout.GridData;\r
-import org.eclipse.swt.layout.GridLayout;\r
-import org.eclipse.swt.widgets.Combo;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Control;\r
-import org.eclipse.swt.widgets.Event;\r
-import org.eclipse.swt.widgets.Listener;\r
-import org.osgi.service.prefs.BackingStoreException;\r
-import org.osgi.service.prefs.Preferences;\r
-import org.simantics.databoard.Accessors;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.Datatypes;\r
-import org.simantics.databoard.accessor.RecordAccessor;\r
-import org.simantics.databoard.accessor.error.AccessorConstructionException;\r
-import org.simantics.databoard.accessor.error.AccessorException;\r
-import org.simantics.databoard.accessor.reference.ChildReference;\r
-import org.simantics.databoard.accessor.reference.LabelReference;\r
-import org.simantics.databoard.binding.RecordBinding;\r
-import org.simantics.databoard.binding.error.BindingException;\r
-import org.simantics.databoard.binding.error.DatatypeConstructionException;\r
-import org.simantics.databoard.binding.error.RuntimeBindingException;\r
-import org.simantics.databoard.binding.mutable.Variant;\r
-import org.simantics.databoard.forms.DataboardForm;\r
-import org.simantics.databoard.forms.DataboardForm.Problem;\r
-import org.simantics.databoard.type.RecordType;\r
-import org.simantics.databoard.type.UnionType;\r
-import org.simantics.export.core.ExportContext;\r
-import org.simantics.export.core.error.ExportException;\r
-import org.simantics.export.core.intf.ContentType;\r
-import org.simantics.export.core.intf.Exporter;\r
-import org.simantics.export.core.intf.Format;\r
-import org.simantics.export.core.intf.Publisher;\r
-import org.simantics.export.core.manager.Content;\r
-import org.simantics.export.core.manager.ExportAction;\r
-import org.simantics.export.core.manager.ExportManager;\r
-import org.simantics.export.core.manager.ExportPlan;\r
-import org.simantics.export.core.manager.ExportWizardResult;\r
-import org.simantics.export.core.util.ExporterUtils;\r
-import org.simantics.utils.datastructures.Arrays;\r
-import org.simantics.utils.datastructures.MapList;\r
-import org.simantics.utils.datastructures.ToStringComparator;\r
-import org.simantics.utils.datastructures.collections.CollectionUtils;\r
-import org.simantics.utils.ui.ExceptionUtils;\r
-import org.simantics.utils.ui.dialogs.ShowMessage;\r
-\r
-/**\r
- * Dynamic Options page. Exporter, Importer and Format contributes options to this page. \r
- *  \r
- * @author toni.kalajainen@semantum.fi\r
- */\r
-public class OptionsPage extends WizardPage {\r
-\r
-       public static String S_OUTPUT_OPTIONS = "Output Options";\r
-       public static LabelReference P_OUTPUT_OPTIONS = new LabelReference( S_OUTPUT_OPTIONS );\r
-       \r
-       /** A reference to combo box selection */\r
-       public static String S_PUBLISH = "Publish to";\r
-       public static ChildReference P_PUBLISH = ChildReference.compile(P_OUTPUT_OPTIONS, new LabelReference(S_PUBLISH));\r
-       \r
-       ExportContext ctx; \r
-       \r
-       ScrolledComposite scroll;\r
-       Composite composite;\r
-       DataboardForm form;\r
-\r
-       List<Content> selection;\r
-       int selectionHash;\r
-       String selectedPublisherId;\r
-       \r
-       public OptionsPage(ExportContext ctx) throws ExportException {\r
-               super("Options page", "Select export options", null);\r
-               \r
-               this.ctx = ctx;\r
-       }\r
-\r
-       Listener modificationListener = new Listener() {                \r
-               public void handleEvent(Event event) {\r
-                       if ( updatingForm ) return;\r
-                       validate();\r
-               }\r
-       };\r
-       \r
-       boolean updatingForm;\r
-       Listener outputSettingsModifiedListener = new Listener() {              \r
-               public void handleEvent(Event event) {\r
-                       updatingForm = true;\r
-                       try {\r
-                               Preferences contentScopePrefs = ctx.store.node( "Selection-"+selectionHash );\r
-                               Preferences workspaceScopePrefs = ctx.store;\r
-                               \r
-                               Composite outputOptions = (Composite) form.getControl(composite, P_OUTPUT_OPTIONS);\r
-                               Combo publishTo = (Combo) form.getControl(composite, P_PUBLISH);                        \r
-                               String newPublisherLabel = publishTo.getText();\r
-                               Publisher newPublisher = ctx.eep.getPublisherByLabel(newPublisherLabel);\r
-                               String newPublisherId = newPublisher==null?null:newPublisher.id();\r
-                               //if ( newPublisherId.equals(selectedPublisherId) ) return;\r
-                               \r
-                               // Save Preferences\r
-                               RecordBinding binding = ctx.databoard.getMutableBinding( form.type() );\r
-                               Object obj = binding.createDefault();\r
-                               form.readFields(composite, binding, obj);\r
-                               Variant options = new Variant(binding, obj);\r
-                               \r
-                               Publisher oldPublisher = ctx.eep.getPublisher( selectedPublisherId );\r
-                               if ( oldPublisher!=null ) {\r
-                                       ChildReference oldOptionsRef = new LabelReference( oldPublisher.label() );\r
-                                       try {\r
-                                               Variant locationOptions = options.getComponent( oldOptionsRef );\r
-                                               oldPublisher.publisherClass().savePref(locationOptions, contentScopePrefs, workspaceScopePrefs);\r
-                                       } catch ( AccessorConstructionException ee ) {}\r
-                               }\r
-\r
-                               List<Content> manifest = getManifestFor(selection, options);\r
-                               cleanPublisherFromGroup(selectedPublisherId);\r
-                               addPublisherToGroup(newPublisherId, manifest);\r
-                               \r
-                               outputOptions.pack(true);\r
-                               outputOptions.layout(true);\r
-                               composite.pack(true);\r
-                               composite.layout(true);\r
-                               \r
-                               RecordType dummy = new RecordType();\r
-                               RecordType newPublisherOptions = newPublisher.publisherClass().locationOptions(ctx, manifest);\r
-                               dummy.addComponent( newPublisherLabel, newPublisherOptions );\r
-                               binding = ctx.databoard.getMutableBinding( dummy );\r
-                               obj = binding.createDefault();\r
-                               options = new Variant(binding, obj);\r
-                               ChildReference locationOptionsRef = new LabelReference( newPublisherLabel );\r
-                               Variant locationOptions = options.getComponent( locationOptionsRef );\r
-                               newPublisher.publisherClass().fillDefaultPrefs(ctx, selection, options, locationOptions);\r
-                               newPublisher.publisherClass().loadPref(locationOptions, contentScopePrefs, workspaceScopePrefs);        \r
-                               //loadPublisherPref(newPublisherId, options, contentScopePrefs, workspaceScopePrefs);\r
-                               form.writeFields(outputOptions, (RecordBinding) options.getBinding(), options.getValue());\r
-                               \r
-                               selectedPublisherId = newPublisherId;\r
-\r
-                               updatingForm = false;\r
-                               validate();\r
-                       } catch ( BindingException e ) {\r
-                               setErrorMessage( e.getClass().getName()+": "+ e.getMessage() );\r
-                   ExceptionUtils.logError(e);                 \r
-                       } catch (AccessorConstructionException e) {\r
-                               setErrorMessage( e.getClass().getName()+": "+ e.getMessage() );\r
-                   ExceptionUtils.logError(e);                 \r
-                       } catch (AccessorException e) {\r
-                               setErrorMessage( e.getClass().getName()+": "+ e.getMessage() );\r
-                   ExceptionUtils.logError(e);                 \r
-                       } catch (ExportException e) {\r
-                               setErrorMessage( e.getClass().getName()+": "+ e.getMessage() );\r
-                   ExceptionUtils.logError(e);                 \r
-                       } finally {\r
-                               updatingForm = false;\r
-                       }\r
-               }\r
-       };\r
-       \r
-       @Override\r
-       public void createControl(Composite parent) {\r
-               \r
-           scroll = new ScrolledComposite(parent, SWT.V_SCROLL);\r
-           composite = new Composite(scroll, 0);\r
-               composite.setLayout( new GridLayout(3, false) );                        \r
-               composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));                   \r
-        scroll.setContent(composite);\r
-        scroll.setExpandHorizontal(true);\r
-        scroll.setExpandVertical(false);\r
-        scroll.getVerticalBar().setIncrement(scroll.getVerticalBar().getIncrement()*8);\r
-\r
-           form = new DataboardForm();\r
-           form.setFirstColumnWidth(200);\r
-\r
-               composite.pack();\r
-               setControl(scroll);\r
-        setPageComplete(true);\r
-        \r
-       }\r
-       \r
-       public void update(List<Content> contents) \r
-       {\r
-               try {\r
-                       // 0. Initialization\r
-                       Set<String> contentUris = new TreeSet<String>();\r
-                       MapList<String, Content> uriToContentMap = new MapList<String, Content>();\r
-                       MapList<ContentType, String> contentByContentType = new MapList<ContentType, String>();\r
-                       MapList<Format, String> contentByFormat = new MapList<Format, String>();\r
-                       List<Format> formats = new ArrayList<Format>();\r
-                       Map<String, Format> contentFormatMap = new TreeMap<String, Format>( String.CASE_INSENSITIVE_ORDER );\r
-                       for (Content content : contents) {\r
-                               contentUris.add(content.url);\r
-                               ContentType ct = ctx.eep.getContentType( content.contentTypeId );\r
-                               contentByContentType.add(ct, content.url);\r
-                               Format format = ctx.eep.getFormat( content.formatId );\r
-                               contentByFormat.add(format, content.url);\r
-                               uriToContentMap.add(content.url, content);\r
-                       }\r
-                       formats.addAll( contentByFormat.getKeys() );\r
-                       \r
-                       Collections.sort(formats, new ToStringComparator());\r
-                                               \r
-                       for (Content content : contents) {\r
-                               String id = content.formatId;\r
-                               Format format = contentFormatMap.get( id );\r
-                               if ( format != null ) continue;\r
-                               format = ctx.eep.getFormat(id);\r
-                               contentFormatMap.put(id, format);\r
-                       }\r
-\r
-                       MapList<Exporter, String> exporterContentMap = new MapList<Exporter, String>();\r
-                       TreeMap<String, Exporter> orderedExporters = new TreeMap<String, Exporter>( String.CASE_INSENSITIVE_ORDER );\r
-                       for (Content content : contents) {\r
-                               for (Exporter exporter : ctx.eep.getExporters(content.formatId, content.contentTypeId) ) {\r
-                                       exporterContentMap.add(exporter, content.url);\r
-                                       orderedExporters.put(exporter.formatId()+exporter.contentTypeId()+exporter.exportAction().getClass(), exporter);\r
-                               }\r
-                       }\r
-                       \r
-                       // 1. Save selections from previous form\r
-                       savePrefs();\r
-                       \r
-                       // 2. Clear previous form\r
-                       form.clear(composite);\r
-\r
-                       // 3. Create options record\r
-                       RecordType optionsRecord = new RecordType();\r
-\r
-                       // Add Output options box\r
-                   RecordType outputOptions = new RecordType();\r
-                       \r
-                       for ( Format format : formats ) {\r
-                               if ( format.isGroupFormat() && contentByFormat.getValues(format).size()>1 ) {\r
-                                       outputOptions.addComponent("Merge "+format.fileext()+" content into one file", Datatypes.BOOLEAN);\r
-                               }\r
-                               \r
-                               if ( format.isContainerFormat() ) {\r
-                                       List<String> formatsContentUris = contentByFormat.getValues(format);\r
-                                       int attachmentCount = 0;\r
-                                       for ( String contentUri : formatsContentUris ) {\r
-                                               for ( Content content : uriToContentMap.getValues(contentUri) ) {\r
-                                                       if ( !content.formatId.equals(format.id()) ) attachmentCount++;\r
-                                               }\r
-                                       }\r
-                                       // Add as possible attachment, all contents that don't have this format\r
-                                       // their their content type.\r
-                                       for ( Content content : contents ) {\r
-                                               if ( ctx.eep.getExporters(format.id(), content.contentTypeId).length == 0) attachmentCount++;\r
-                                       }\r
-                                       \r
-                                       if ( attachmentCount > 0 ) { \r
-                                               outputOptions.addComponent("Include attachments to "+format.fileext(), Datatypes.BOOLEAN);\r
-                                               outputOptions.addComponent("Export attachments of "+format.fileext()+" to separate files", Datatypes.BOOLEAN);\r
-                                       }\r
-                               }\r
-                       }\r
-                       \r
-                       UnionType publisherType = new UnionType();\r
-                       for (Publisher publisher : ctx.eep.publishers()) publisherType.addComponent(publisher.label(), Datatypes.VOID);\r
-                       outputOptions.addComponent(S_PUBLISH, publisherType);\r
-                       \r
-                   optionsRecord.addComponent(S_OUTPUT_OPTIONS, outputOptions);                    \r
-                   \r
-                       // Add Format specific boxes\r
-                       for (Format format : contentFormatMap.values()) {\r
-                               RecordType formatOptions = format.formatActions().options(ctx);\r
-                               if ( formatOptions==null ) continue;\r
-                               optionsRecord.mergeRecord( formatOptions );\r
-                       }\r
-       \r
-                       // Add Exporter specific boxes\r
-                       for (Exporter exporter : orderedExporters.values()) {                           \r
-                               List<String> exportSpecificContents = exporterContentMap.getValues(exporter);\r
-                               if ( exportSpecificContents==null || exportSpecificContents.isEmpty() ) continue;\r
-                               RecordType exporterOptions = exporter.exportAction().options(ctx, exportSpecificContents);\r
-                               if ( exporterOptions == null ) continue;\r
-                               optionsRecord.mergeRecord( exporterOptions );\r
-                       }\r
-\r
-                       // 4. Load default and previous selections of the options ( All but publisher )\r
-                       ctx.databoard.clear();\r
-                       RecordBinding optionsBinding = ctx.databoard.getMutableBinding( optionsRecord );\r
-                       Object optionsObj = optionsBinding.createDefaultUnchecked();\r
-                       Variant options = new Variant(optionsBinding, optionsObj);\r
-                       {\r
-                               Preferences workspaceScopePrefs = ctx.store;\r
-                               Preferences contentScopePrefs = ctx.store( contents );\r
-                               \r
-                               for (Exporter exporter : orderedExporters.values()) {\r
-                                       exporter.exportAction().fillDefaultPrefs(ctx, options);\r
-                                       exporter.exportAction().loadPref(options, contentScopePrefs, workspaceScopePrefs);\r
-                               }\r
-                               \r
-                               for (Format format : contentByFormat.getKeys()) {\r
-                                       format.formatActions().fillDefaultPrefs( ctx, options );\r
-                                       format.formatActions().loadPref(options, contentScopePrefs, workspaceScopePrefs);\r
-                               }\r
-                               \r
-                               fillDefaultPrefs(options);\r
-                               loadPref(options, contentScopePrefs, workspaceScopePrefs);\r
-                       }\r
-                       \r
-                       // 5. Create form\r
-                       form.addFields(composite, optionsRecord);\r
-                       form.addListener(composite, form.type(), modificationListener);\r
-                       Composite outputOptionsGroup = (Composite) form.getControl(composite, P_OUTPUT_OPTIONS);\r
-                       RecordType dummy = new RecordType();\r
-                       dummy.addComponent(S_OUTPUT_OPTIONS, outputOptions);\r
-                       form.addListener(outputOptionsGroup, dummy, outputSettingsModifiedListener);\r
-                       form.writeFields(composite, optionsBinding, optionsObj);\r
-                       \r
-                       // 6. Add publisher\r
-                       {\r
-                               selection = contents;\r
-                               Preferences workspaceScopePrefs = ctx.store;\r
-                               Preferences contentScopePrefs = ctx.store.node( "Selection-"+selectionHash );\r
-                               selectedPublisherId = ctx.store.get("publisherId", "");         \r
-                               Publisher publisher = ctx.eep.getPublisher(selectedPublisherId);\r
-                               if ( publisher != null ) {\r
-                                       \r
-                                       // 6A. Manifest\r
-                                       List<Content> manifest = getManifestFor(contents, options);\r
-                                       \r
-                                       // 6B. Default and previous settings\r
-                                       String label = publisher.label();\r
-                                       RecordType publisherOptionsType = publisher.publisherClass().locationOptions(ctx, manifest);\r
-                                       RecordType publisherOptionsRootType = new RecordType();\r
-                                       publisherOptionsRootType.addComponent(label, publisherOptionsType);\r
-                                       RecordBinding publisherOptionsRootBinding = ctx.databoard.getMutableBinding( publisherOptionsRootType );\r
-                                       Object publisherOptionsRootObj = publisherOptionsRootBinding.createDefaultUnchecked();\r
-                                       Variant publisherOptionsRoot = new Variant(publisherOptionsRootBinding, publisherOptionsRootObj);\r
-                                       try {\r
-                                               ChildReference locationOptionsRef = new LabelReference( label );\r
-                                               Variant locationOptions = publisherOptionsRoot.getComponent( locationOptionsRef );\r
-                                               publisher.publisherClass().fillDefaultPrefs(ctx, selection, options, locationOptions);\r
-                                               publisher.publisherClass().loadPref(locationOptions, contentScopePrefs, workspaceScopePrefs);   \r
-                                       } catch ( AccessorConstructionException ee ) {\r
-                                       }\r
-                                       \r
-                                       // 6C. Add Publisher form\r
-                                       addPublisherToGroup(selectedPublisherId, manifest);\r
-                                       form.writeFields(composite, publisherOptionsRootBinding, publisherOptionsRootObj);\r
-                               }\r
-                       }\r
-\r
-                       // 8. Validate page\r
-                       composite.pack();\r
-                       validate();\r
-                       \r
-               } catch (BindingException e) {\r
-            ExceptionUtils.logError(e);                        \r
-                       ShowMessage.showError("Unexpected error", e.getClass().getName()+" "+e.getMessage());\r
-                       throw new RuntimeBindingException(e);\r
-               } catch (ExportException e) {\r
-            ExceptionUtils.logError(e);                        \r
-                       ShowMessage.showError("Unexpected error", e.getClass().getName()+" "+e.getMessage());\r
-               } catch (DatatypeConstructionException e) {\r
-            ExceptionUtils.logError(e);                        \r
-                       ShowMessage.showError("Unexpected error", e.getClass().getName()+" "+e.getMessage());\r
-               } catch (AccessorConstructionException e) {\r
-            ExceptionUtils.logError(e);                        \r
-                       ShowMessage.showError("Unexpected error", e.getClass().getName()+" "+e.getMessage());\r
-               } catch (AccessorException e) {\r
-            ExceptionUtils.logError(e);                        \r
-                       // TODO Auto-generated catch block\r
-                       e.printStackTrace();\r
-               }\r
-               \r
-       }\r
-\r
-       /**\r
-        * Saves publisher prefs from the UI to preference nodes.\r
-        * \r
-        * @throws ExportException\r
-        */\r
-       void savePublisherPref(String publisherId, Variant options, Preferences contentScopePrefs, Preferences workspaceScopePrefs) throws ExportException {\r
-               try {\r
-//                     Preferences contentScopePrefs = ctx.store.node( "Selection-"+selectionHash );\r
-//                     Preferences workspaceScopePrefs = ctx.store;\r
-\r
-//                     RecordBinding binding = ctx.databoard.getMutableBinding( form.type() );\r
-//                     Object obj = binding.createDefault();\r
-//                     form.readFields(composite, binding, obj);\r
-//                     Variant options = new Variant(binding, obj);\r
-                       \r
-                       Publisher publisher = ctx.eep.getPublisher( publisherId );\r
-                       if ( publisher==null ) return;\r
-                       Variant locationOptions = options.getComponent( new LabelReference(publisher.label()) );\r
-                       publisher.publisherClass().savePref(locationOptions, contentScopePrefs, workspaceScopePrefs);\r
-//             } catch (BindingException e) {\r
-//                     throw new ExportException( e );\r
-               } catch (AccessorConstructionException e) {\r
-            ExceptionUtils.logError(e);                        \r
-                       //throw new ExportException( e );\r
-//             } catch (AccessorException e) {\r
-//                     throw new ExportException( e );\r
-               }\r
-       }\r
-       \r
-       void loadPublisherPref(String publisherId, Variant options, Preferences contentScopePrefs, Preferences workspaceScopePrefs) throws ExportException {\r
-               try {\r
-//                     Preferences contentScopePrefs = ctx.store.node( "Selection-"+selectionHash );\r
-//                     Preferences workspaceScopePrefs = ctx.store;\r
-                       \r
-                       Publisher publisher = ctx.eep.getPublisher( publisherId );\r
-                       if ( publisher == null ) return;\r
-                       \r
-                       // There is a problem here if selection was built with manifest\r
-//                     RecordType optionsType = publisher.publiserClass().locationOptions(ctx, selection);                     \r
-//                     RecordBinding binding = ctx.databoard.getMutableBinding( optionsType );\r
-//                     Object obj = binding.createDefault();\r
-//                     Variant locationOptions = new Variant(binding, obj);\r
-                       Variant locationOptions = options.getComponent( new LabelReference( publisher.label() ) );\r
-                       publisher.publisherClass().fillDefaultPrefs(ctx, selection, options, locationOptions);\r
-                       publisher.publisherClass().loadPref(locationOptions, contentScopePrefs, workspaceScopePrefs);   \r
-//             } catch (BindingException e) {\r
-//                     throw new ExportException( e );\r
-               } catch (AccessorConstructionException e) {\r
-            ExceptionUtils.logError(e);                        \r
-                       //throw new ExportException( e );\r
-//             } catch (AccessorException e) {\r
-//                     throw new ExportException( e );\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Remove any publisher specific controls from output options.\r
-        */\r
-       void cleanPublisherFromGroup(String oldPublisherId) {\r
-               Publisher publisher = ctx.eep.getPublisher(oldPublisherId);\r
-               if ( publisher != null ) {\r
-                       String fieldName = publisher.label();\r
-                       if ( form.type().hasComponent(fieldName)) {\r
-                               form.type().removeComponent(fieldName);\r
-                               ctx.databoard.clear();\r
-                       }\r
-               }\r
-               Combo publishTo = (Combo) form.getControl(composite, P_PUBLISH);\r
-               Composite group = (Composite) publishTo.getParent();\r
-               Control children[] = group.getChildren();\r
-               int index = Arrays.indexOf( children, publishTo );\r
-               for (int i=children.length-1; i>index; i--) {\r
-                       children[i].dispose();\r
-               }\r
-       }\r
-       \r
-       /**\r
-        * Add controls of a publisher id \r
-        * \r
-        * @param publisherId\r
-        * @throws ExportException \r
-        */\r
-       void addPublisherToGroup( String publisherId, List<Content> contents ) throws ExportException {\r
-               try {\r
-                       Composite group = (Composite) form.getControl(composite, P_OUTPUT_OPTIONS);\r
-                       if ( group == null ) return;\r
-                       Publisher publisher = ctx.eep.getPublisher( publisherId );\r
-                       if ( publisher == null ) return;\r
-                       RecordType publisherOptions = publisher.publisherClass().locationOptions(ctx, contents);                \r
-                       //form.type().addComponent(publisher.label(), publisherOptions);\r
-                       RecordType options = new RecordType();\r
-                       options.addComponent(publisher.label(), publisherOptions);\r
-                       form.addFields(group, publisherOptions, publisher.label());\r
-                       form.addListener(group, options, modificationListener);\r
-                       ctx.databoard.clear();\r
-               } catch (BindingException e) {\r
-                       throw new ExportException(e);\r
-               } catch (AccessorConstructionException e) {\r
-                       throw new ExportException(e);\r
-               } catch (AccessorException e) {\r
-                       throw new ExportException(e);\r
-               }\r
-       }\r
-       \r
-       List<Content> getManifestFor(List<Content> selection, Variant options) {                \r
-               try {\r
-                       ExportWizardResult result = new ExportWizardResult();\r
-                       result.options = options;\r
-                       result.accessor = Accessors.getAccessor(result.options);\r
-                       result.contents = selection;\r
-                       result.type = (RecordType) options.type();\r
-                       result.publisherId = "file";\r
-                       \r
-                       List<ExportAction> actions = new ArrayList<ExportAction>();\r
-                       List<Content> manifest = new ArrayList<Content>(); \r
-                       result.createExportActions(ctx, actions, manifest);\r
-                       return manifest;\r
-               } catch (AccessorConstructionException e) {\r
-                       return selection;\r
-               } catch (ExportException e) {\r
-                       e.printStackTrace();\r
-                       return selection;\r
-               }\r
-       }       \r
-       \r
-       public ExportWizardResult getOutput()\r
-       throws ExportException\r
-       {\r
-               ExportWizardResult result = new ExportWizardResult();\r
-               try {\r
-                       ctx.databoard.clear();\r
-                       result.type = form.type();\r
-                   RecordBinding binding = (RecordBinding) ctx.databoard.getMutableBinding( result.type );\r
-                   Object optionsObj = binding.createDefault();\r
-                   result.options = new Variant(binding, optionsObj);\r
-                       form.readFields(composite, binding, optionsObj);\r
-                       result.accessor = Accessors.getAccessor(result.options);\r
-                       result.contents = selection;\r
-                       \r
-                       \r
-                       String publisherLabel = ExporterUtils.getUnionValue(result.accessor, P_PUBLISH);\r
-                       Publisher publisher = ctx.eep.getPublisherByLabel(publisherLabel);\r
-                       result.publisherId = publisher==null?"":publisher.id();                 \r
-                       \r
-               } catch (BindingException e) {\r
-                       throw new ExportException(e);                   \r
-               } catch (AccessorException e) {\r
-                       throw new ExportException(e);\r
-               } catch (AccessorConstructionException e) {\r
-                       throw new ExportException(e);\r
-               }\r
-               \r
-               return result;\r
-       }\r
-               \r
-       void validate() {               \r
-               List<Problem> problems = form.validate(composite);\r
-               List<String> errs = DataboardForm.toStrings(problems);\r
-               \r
-               if ( errs.isEmpty() ) {\r
-                       try {\r
-                               ExportWizardResult result = getOutput();\r
-                               ExportPlan plan = new ExportPlan();\r
-                               result.createPlan(ctx, plan);\r
-                               ExportManager mgr = new ExportManager(result.options, ctx);\r
-                               errs.addAll( mgr.validate(ctx, plan) ) ;\r
-                       } catch (ExportException e) {\r
-                               errs.add(e.getMessage());\r
-                       }\r
-               } else {\r
-                       CollectionUtils.unique( errs );                 \r
-               }\r
-               \r
-\r
-               setErrorMessage( errs.isEmpty() ? null : CollectionUtils.toString(errs, ", ") );\r
-               setPageComplete( errs.isEmpty() );\r
-       }\r
-               \r
-       /**\r
-        * Save wizard preferences\r
-        * \r
-        * @param options\r
-        * @param contentScopePrefs\r
-        * @param workspaceScopePrefs\r
-        * @throws ExportException\r
-        */\r
-       void savePref(Variant options, Preferences contentScopePrefs, Preferences workspaceScopePrefs) throws ExportException\r
-       {\r
-        try {\r
-                       RecordAccessor ra = Accessors.getAccessor(options);\r
-                       String publisherId = ExporterUtils.getUnionValue(ra, P_PUBLISH); \r
-                       Publisher publisher = ctx.eep.getPublisherByLabel(publisherId);\r
-                       if ( publisher!=null ) workspaceScopePrefs.put("publisherId", publisher.id());\r
-                       \r
-                       if ( ra.type().hasComponent(P_OUTPUT_OPTIONS.label) ) {\r
-                               RecordAccessor rao = ra.getComponent( P_OUTPUT_OPTIONS );\r
-                                                               \r
-                               Pattern merge_pattern = Pattern.compile("Merge ([^\\s]*) content into one file");\r
-                               Pattern include_pattern = Pattern.compile("Include attachments to ([^\\s]*)");\r
-                               Pattern export_pattern = Pattern.compile("Export attachments of ([^\\s]*) to separate files");\r
-                               \r
-                               for (int i=0; i<rao.count(); i++) {\r
-                                       String name = rao.type().getComponent(i).name;\r
-                                       Matcher m;\r
-                                       \r
-                                       m = merge_pattern.matcher(name);\r
-                                       if ( m.matches() ) {\r
-                                               String fileExt = m.group(1);\r
-                                               Format format = ctx.eep.getFormatByExt(fileExt);\r
-                                               Boolean value = (Boolean) rao.getFieldValue(i, Bindings.BOOLEAN);\r
-                                               String key = format.id()+"_merge";\r
-                                               contentScopePrefs.putBoolean(key, value);\r
-                                               workspaceScopePrefs.putBoolean(key, value);\r
-                                       }\r
-                                       \r
-                                       m = include_pattern.matcher(name);\r
-                                       if ( m.matches() ) {\r
-                                               String fileExt = m.group(1);\r
-                                               Format format = ctx.eep.getFormatByExt(fileExt);\r
-                                               Boolean value = (Boolean) rao.getFieldValue(i, Bindings.BOOLEAN);\r
-                                               String key = format.id()+"_include_attachments";\r
-                                               contentScopePrefs.putBoolean(key, value);\r
-                                               workspaceScopePrefs.putBoolean(key, value);\r
-                                       }\r
-                                       \r
-                                       m = export_pattern.matcher(name);\r
-                                       if ( m.matches() ) {\r
-                                               String fileExt = m.group(1);\r
-                                               Format format = ctx.eep.getFormatByExt(fileExt);\r
-                                               Boolean value = (Boolean) rao.getFieldValue(i, Bindings.BOOLEAN);\r
-                                               String key = format.id()+"_export_attachments";\r
-                                               contentScopePrefs.putBoolean(key, value);\r
-                                               workspaceScopePrefs.putBoolean(key, value);\r
-                                       }\r
-                               }\r
-                       }\r
-                       \r
-               } catch (AccessorConstructionException e) {\r
-                       throw new ExportException(e);\r
-               } catch (AccessorException e) {\r
-                       throw new ExportException(e);\r
-               }\r
-               \r
-       }\r
-\r
-       /**\r
-        * Load wizard preferences\r
-        * \r
-        * @param binding\r
-        * @param options\r
-        * @param contentScopePrefs\r
-        * @param workspaceScopePrefs\r
-        * @throws ExportException\r
-        */\r
-       void loadPref(Variant options, Preferences contentScopePrefs, Preferences workspaceScopePrefs) throws ExportException\r
-       {\r
-               //Preferences workspaceScopePrefs = ctx.store;\r
-        try {\r
-                       RecordAccessor ra = Accessors.getAccessor(options);             \r
-                       String publisherId = workspaceScopePrefs.get("publisherId", "");\r
-                       Publisher publisher = ctx.eep.getPublisher(publisherId);\r
-                       if ( publisher != null ) ExporterUtils.setUnionValue(ra, P_PUBLISH, publisher.label());\r
-                       \r
-                       if ( ra.type().hasComponent(P_OUTPUT_OPTIONS.label) ) {\r
-                               RecordAccessor rao = ra.getComponent( P_OUTPUT_OPTIONS );\r
-                                                               \r
-                               for (Format format : ctx.eep.formats()) {\r
-                                       if ( format.isGroupFormat() ) {\r
-                                               String key = format.id()+"_merge";\r
-                                               Boolean value = null;\r
-                                               if ( containsKey(contentScopePrefs, key) ) {\r
-                                                       value = contentScopePrefs.getBoolean(key, false); \r
-                                               } else if ( containsKey(workspaceScopePrefs, key) ) {\r
-                                                       value = workspaceScopePrefs.getBoolean(key, false);\r
-                                               }\r
-                                                                                       \r
-                                               if ( value != null ) {\r
-                                                       String key2 = "Merge "+format.fileext()+" content into one file";\r
-                                                       int index = rao.type().getComponentIndex2(key2);\r
-                                                       if ( index>=0 ) {\r
-                                                               rao.setFieldValue(index, Bindings.BOOLEAN, value);\r
-                                                       }\r
-                                               }                                       \r
-                                       }\r
-                                       \r
-                                       if ( format.isContainerFormat() ) {\r
-                                               String key = format.id()+"_include_attachments";\r
-                                               Boolean value = null;\r
-                                               if ( containsKey(contentScopePrefs, key) ) {\r
-                                                       value = contentScopePrefs.getBoolean(key, false); \r
-                                               } else if ( containsKey(workspaceScopePrefs, key) ) {\r
-                                                       value = workspaceScopePrefs.getBoolean(key, false);\r
-                                               }\r
-                                                                                       \r
-                                               if ( value != null ) {\r
-                                                       String key2 = "Include attachments to "+format.fileext();\r
-                                                       int index = rao.type().getComponentIndex2(key2);\r
-                                                       if ( index>=0 ) {\r
-                                                               rao.setFieldValue(index, Bindings.BOOLEAN, value);\r
-                                                       }\r
-                                               }                                       \r
-                                       }\r
-                                       \r
-                                       if ( format.isContainerFormat() ) {\r
-                                               String key = format.id()+"_export_attachments";\r
-                                               Boolean value = null;\r
-                                               if ( containsKey(contentScopePrefs, key) ) {\r
-                                                       value = contentScopePrefs.getBoolean(key, false); \r
-                                               } else if ( containsKey(workspaceScopePrefs, key) ) {\r
-                                                       value = workspaceScopePrefs.getBoolean(key, false);\r
-                                               }\r
-                                                                                       \r
-                                               if ( value != null ) {\r
-                                                       String key2 = "Export attachments of "+format.fileext()+" to separate files";\r
-                                                       int index = rao.type().getComponentIndex2(key2);\r
-                                                       if ( index>=0 ) {\r
-                                                               rao.setFieldValue(index, Bindings.BOOLEAN, value);\r
-                                                       }\r
-                                               }                                       \r
-                                       }\r
-                                       \r
-                               }\r
-                       }\r
-                                       \r
-               } catch (AccessorConstructionException e) {\r
-                       throw new ExportException(e);\r
-               } catch (AccessorException e) {\r
-                       throw new ExportException(e);\r
-               }               \r
-       }\r
-       \r
-       /**\r
-        * Save selections from previous form\r
-        */\r
-       public void savePrefs() throws ExportException {\r
-               try {\r
-                       int oldSelectionHash = selectionHash;                   \r
-                       Preferences contentScopePrefs = ctx.store.node( "Selection-"+oldSelectionHash );\r
-                       Preferences workspaceScopePrefs = ctx.store;\r
-                                       \r
-                       RecordBinding binding = ctx.databoard.getMutableBinding( form.type() );\r
-                       Object obj = binding.createDefault();\r
-                       Variant options = new Variant(binding, obj);\r
-                       form.readFields(composite, binding, obj);\r
-                                               \r
-                       Combo publishTo = (Combo) form.getControl(composite, P_PUBLISH);                \r
-                       String publisherLabel = publishTo==null?null:publishTo.getText();\r
-                       if ( publisherLabel != null ) {\r
-                               Publisher publisher = ctx.eep.getPublisherByLabel( publisherLabel );\r
-                               if ( publisher!=null ) {\r
-                                       try {\r
-                                               ChildReference locationOptionsRef = new LabelReference(publisher.label());\r
-                                               Variant locationOptions = options.getComponent( locationOptionsRef );\r
-                                               publisher.publisherClass().savePref(locationOptions, contentScopePrefs, workspaceScopePrefs);\r
-                                       } catch ( AccessorConstructionException e ) {} \r
-                               }\r
-                       }\r
-                       \r
-                       for (Exporter exporter : ctx.eep.exporters()) {\r
-                               exporter.exportAction().savePref(options, contentScopePrefs, workspaceScopePrefs);\r
-                       }\r
-                                       \r
-                       for (Format format : ctx.eep.formats()) {\r
-                               format.formatActions().savePref(options, contentScopePrefs, workspaceScopePrefs);\r
-                       }                               \r
-                                       \r
-                       savePref(options, contentScopePrefs, workspaceScopePrefs);\r
-               } catch (BindingException e) {\r
-                       throw new ExportException( e ); \r
-               } catch (AccessorConstructionException e) {\r
-                       throw new ExportException( e ); \r
-               } catch (AccessorException e) {\r
-                       throw new ExportException( e ); \r
-               } catch (ExportException e) {\r
-                       throw new ExportException( e ); \r
-               }\r
-       }\r
-\r
-       public void fillDefaultPrefs(Variant options) throws ExportException {\r
-        try {\r
-                       RecordAccessor ra = Accessors.getAccessor(options);\r
-\r
-                       if ( ra.type().hasComponent(P_OUTPUT_OPTIONS.label) ) {\r
-                               RecordAccessor rao = ra.getComponent( P_OUTPUT_OPTIONS );\r
-                               \r
-                               for (Format format : ctx.eep.formats()) {\r
-                                       if ( format.isContainerFormat() ) {\r
-                                               String key = "Include attachments to "+format.fileext();\r
-                                               int index = rao.type().getComponentIndex2(key);\r
-                                               if ( index>=0 ) rao.setFieldValue(index, Bindings.BOOLEAN, true);\r
-                                               \r
-                                               key = "Export attachments of "+format.fileext()+" to separate files";;\r
-                                               index = rao.type().getComponentIndex2(key);\r
-                                               if ( index>=0 ) rao.setFieldValue(index, Bindings.BOOLEAN, true);\r
-                                       }                       \r
-                               }\r
-                               \r
-                       }\r
-               \r
-               } catch (AccessorConstructionException e) {\r
-                       throw new ExportException(e);\r
-               } catch (AccessorException e) {\r
-                       throw new ExportException(e);\r
-               }\r
-       }       \r
-       \r
-       static boolean containsKey(Preferences pref, String key) {\r
-               try {\r
-                       for (String x : pref.keys()) if ( x.equals(key) ) return true;\r
-               } catch (BackingStoreException e) {\r
-                       e.printStackTrace();\r
-               } \r
-               return false;           \r
-       }\r
-               \r
-}\r
+package org.simantics.export.ui;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+import org.simantics.databoard.Accessors;
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.Datatypes;
+import org.simantics.databoard.accessor.RecordAccessor;
+import org.simantics.databoard.accessor.error.AccessorConstructionException;
+import org.simantics.databoard.accessor.error.AccessorException;
+import org.simantics.databoard.accessor.reference.ChildReference;
+import org.simantics.databoard.accessor.reference.LabelReference;
+import org.simantics.databoard.binding.RecordBinding;
+import org.simantics.databoard.binding.error.BindingException;
+import org.simantics.databoard.binding.error.DatatypeConstructionException;
+import org.simantics.databoard.binding.error.RuntimeBindingException;
+import org.simantics.databoard.binding.mutable.Variant;
+import org.simantics.databoard.forms.DataboardForm;
+import org.simantics.databoard.forms.DataboardForm.Problem;
+import org.simantics.databoard.type.RecordType;
+import org.simantics.databoard.type.UnionType;
+import org.simantics.export.core.ExportContext;
+import org.simantics.export.core.error.ExportException;
+import org.simantics.export.core.intf.ContentType;
+import org.simantics.export.core.intf.Exporter;
+import org.simantics.export.core.intf.Format;
+import org.simantics.export.core.intf.Publisher;
+import org.simantics.export.core.manager.Content;
+import org.simantics.export.core.manager.ExportAction;
+import org.simantics.export.core.manager.ExportManager;
+import org.simantics.export.core.manager.ExportPlan;
+import org.simantics.export.core.manager.ExportWizardResult;
+import org.simantics.export.core.util.ExporterUtils;
+import org.simantics.utils.datastructures.Arrays;
+import org.simantics.utils.datastructures.MapList;
+import org.simantics.utils.datastructures.ToStringComparator;
+import org.simantics.utils.datastructures.collections.CollectionUtils;
+import org.simantics.utils.ui.ExceptionUtils;
+import org.simantics.utils.ui.dialogs.ShowMessage;
+
+/**
+ * Dynamic Options page. Exporter, Importer and Format contributes options to this page. 
+ *  
+ * @author toni.kalajainen@semantum.fi
+ */
+public class OptionsPage extends WizardPage {
+
+       public static String S_OUTPUT_OPTIONS = "Output Options";
+       public static LabelReference P_OUTPUT_OPTIONS = new LabelReference( S_OUTPUT_OPTIONS );
+       
+       /** A reference to combo box selection */
+       public static String S_PUBLISH = "Publish to";
+       public static ChildReference P_PUBLISH = ChildReference.compile(P_OUTPUT_OPTIONS, new LabelReference(S_PUBLISH));
+       
+       ExportContext ctx; 
+       
+       ScrolledComposite scroll;
+       Composite composite;
+       DataboardForm form;
+
+       List<Content> selection;
+       int selectionHash;
+       String selectedPublisherId;
+       
+       public OptionsPage(ExportContext ctx) throws ExportException {
+               super("Options page", "Select export options", null);
+               
+               this.ctx = ctx;
+       }
+
+       Listener modificationListener = new Listener() {                
+               public void handleEvent(Event event) {
+                       if ( updatingForm ) return;
+                       validate();
+               }
+       };
+       
+       boolean updatingForm;
+       Listener outputSettingsModifiedListener = new Listener() {              
+               public void handleEvent(Event event) {
+                       updatingForm = true;
+                       try {
+                               Preferences contentScopePrefs = ctx.store.node( "Selection-"+selectionHash );
+                               Preferences workspaceScopePrefs = ctx.store;
+                               
+                               Composite outputOptions = (Composite) form.getControl(composite, P_OUTPUT_OPTIONS);
+                               Combo publishTo = (Combo) form.getControl(composite, P_PUBLISH);                        
+                               String newPublisherLabel = publishTo.getText();
+                               Publisher newPublisher = ctx.eep.getPublisherByLabel(newPublisherLabel);
+                               String newPublisherId = newPublisher==null?null:newPublisher.id();
+                               //if ( newPublisherId.equals(selectedPublisherId) ) return;
+                               
+                               // Save Preferences
+                               RecordBinding binding = ctx.databoard.getMutableBinding( form.type() );
+                               Object obj = binding.createDefault();
+                               form.readFields(composite, binding, obj);
+                               Variant options = new Variant(binding, obj);
+                               
+                               Publisher oldPublisher = ctx.eep.getPublisher( selectedPublisherId );
+                               if ( oldPublisher!=null ) {
+                                       ChildReference oldOptionsRef = new LabelReference( oldPublisher.label() );
+                                       try {
+                                               Variant locationOptions = options.getComponent( oldOptionsRef );
+                                               oldPublisher.publisherClass().savePref(locationOptions, contentScopePrefs, workspaceScopePrefs);
+                                       } catch ( AccessorConstructionException ee ) {}
+                               }
+
+                               List<Content> manifest = getManifestFor(selection, options);
+                               cleanPublisherFromGroup(selectedPublisherId);
+                               addPublisherToGroup(newPublisherId, manifest);
+                               
+                               outputOptions.pack(true);
+                               outputOptions.layout(true);
+                               composite.pack(true);
+                               composite.layout(true);
+                               
+                               RecordType dummy = new RecordType();
+                               RecordType newPublisherOptions = newPublisher.publisherClass().locationOptions(ctx, manifest);
+                               dummy.addComponent( newPublisherLabel, newPublisherOptions );
+                               binding = ctx.databoard.getMutableBinding( dummy );
+                               obj = binding.createDefault();
+                               options = new Variant(binding, obj);
+                               ChildReference locationOptionsRef = new LabelReference( newPublisherLabel );
+                               Variant locationOptions = options.getComponent( locationOptionsRef );
+                               newPublisher.publisherClass().fillDefaultPrefs(ctx, selection, options, locationOptions);
+                               newPublisher.publisherClass().loadPref(locationOptions, contentScopePrefs, workspaceScopePrefs);        
+                               //loadPublisherPref(newPublisherId, options, contentScopePrefs, workspaceScopePrefs);
+                               form.writeFields(outputOptions, (RecordBinding) options.getBinding(), options.getValue());
+                               
+                               selectedPublisherId = newPublisherId;
+
+                               updatingForm = false;
+                               validate();
+                       } catch ( BindingException e ) {
+                               setErrorMessage( e.getClass().getName()+": "+ e.getMessage() );
+                   ExceptionUtils.logError(e);                 
+                       } catch (AccessorConstructionException e) {
+                               setErrorMessage( e.getClass().getName()+": "+ e.getMessage() );
+                   ExceptionUtils.logError(e);                 
+                       } catch (AccessorException e) {
+                               setErrorMessage( e.getClass().getName()+": "+ e.getMessage() );
+                   ExceptionUtils.logError(e);                 
+                       } catch (ExportException e) {
+                               setErrorMessage( e.getClass().getName()+": "+ e.getMessage() );
+                   ExceptionUtils.logError(e);                 
+                       } finally {
+                               updatingForm = false;
+                       }
+               }
+       };
+       
+       @Override
+       public void createControl(Composite parent) {
+               
+           scroll = new ScrolledComposite(parent, SWT.V_SCROLL);
+           composite = new Composite(scroll, 0);
+               composite.setLayout( new GridLayout(3, false) );                        
+               composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));                   
+        scroll.setContent(composite);
+        scroll.setExpandHorizontal(true);
+        scroll.setExpandVertical(false);
+        scroll.getVerticalBar().setIncrement(scroll.getVerticalBar().getIncrement()*8);
+
+           form = new DataboardForm();
+           form.setFirstColumnWidth(200);
+
+               composite.pack();
+               setControl(scroll);
+        setPageComplete(true);
+        
+       }
+       
+       public void update(List<Content> contents) 
+       {
+               try {
+                       // 0. Initialization
+                       Set<String> contentUris = new TreeSet<String>();
+                       MapList<String, Content> uriToContentMap = new MapList<String, Content>();
+                       MapList<ContentType, String> contentByContentType = new MapList<ContentType, String>();
+                       MapList<Format, String> contentByFormat = new MapList<Format, String>();
+                       List<Format> formats = new ArrayList<Format>();
+                       Map<String, Format> contentFormatMap = new TreeMap<String, Format>( String.CASE_INSENSITIVE_ORDER );
+                       for (Content content : contents) {
+                               contentUris.add(content.url);
+                               ContentType ct = ctx.eep.getContentType( content.contentTypeId );
+                               contentByContentType.add(ct, content.url);
+                               Format format = ctx.eep.getFormat( content.formatId );
+                               contentByFormat.add(format, content.url);
+                               uriToContentMap.add(content.url, content);
+                       }
+                       formats.addAll( contentByFormat.getKeys() );
+                       
+                       Collections.sort(formats, new ToStringComparator());
+                                               
+                       for (Content content : contents) {
+                               String id = content.formatId;
+                               Format format = contentFormatMap.get( id );
+                               if ( format != null ) continue;
+                               format = ctx.eep.getFormat(id);
+                               contentFormatMap.put(id, format);
+                       }
+
+                       MapList<Exporter, String> exporterContentMap = new MapList<Exporter, String>();
+                       TreeMap<String, Exporter> orderedExporters = new TreeMap<String, Exporter>( String.CASE_INSENSITIVE_ORDER );
+                       for (Content content : contents) {
+                               for (Exporter exporter : ctx.eep.getExporters(content.formatId, content.contentTypeId) ) {
+                                       exporterContentMap.add(exporter, content.url);
+                                       orderedExporters.put(exporter.formatId()+exporter.contentTypeId()+exporter.exportAction().getClass(), exporter);
+                               }
+                       }
+                       
+                       // 1. Save selections from previous form
+                       savePrefs();
+                       
+                       // 2. Clear previous form
+                       form.clear(composite);
+
+                       // 3. Create options record
+                       RecordType optionsRecord = new RecordType();
+
+                       // Add Output options box
+                   RecordType outputOptions = new RecordType();
+                       
+                       for ( Format format : formats ) {
+                               if ( format.isGroupFormat() && contentByFormat.getValues(format).size()>1 ) {
+                                       outputOptions.addComponent("Merge "+format.fileext()+" content into one file", Datatypes.BOOLEAN);
+                               }
+                               
+                               if ( format.isContainerFormat() ) {
+                                       List<String> formatsContentUris = contentByFormat.getValues(format);
+                                       int attachmentCount = 0;
+                                       for ( String contentUri : formatsContentUris ) {
+                                               for ( Content content : uriToContentMap.getValues(contentUri) ) {
+                                                       if ( !content.formatId.equals(format.id()) ) attachmentCount++;
+                                               }
+                                       }
+                                       // Add as possible attachment, all contents that don't have this format
+                                       // their their content type.
+                                       for ( Content content : contents ) {
+                                               if ( ctx.eep.getExporters(format.id(), content.contentTypeId).length == 0) attachmentCount++;
+                                       }
+                                       
+                                       if ( attachmentCount > 0 ) { 
+                                               outputOptions.addComponent("Include attachments to "+format.fileext(), Datatypes.BOOLEAN);
+                                               outputOptions.addComponent("Export attachments of "+format.fileext()+" to separate files", Datatypes.BOOLEAN);
+                                       }
+                               }
+                       }
+                       
+                       UnionType publisherType = new UnionType();
+                       for (Publisher publisher : ctx.eep.publishers()) publisherType.addComponent(publisher.label(), Datatypes.VOID);
+                       outputOptions.addComponent(S_PUBLISH, publisherType);
+                       
+                   optionsRecord.addComponent(S_OUTPUT_OPTIONS, outputOptions);                    
+                   
+                       // Add Format specific boxes
+                       for (Format format : contentFormatMap.values()) {
+                               RecordType formatOptions = format.formatActions().options(ctx);
+                               if ( formatOptions==null ) continue;
+                               optionsRecord.mergeRecord( formatOptions );
+                       }
+       
+                       // Add Exporter specific boxes
+                       for (Exporter exporter : orderedExporters.values()) {                           
+                               List<String> exportSpecificContents = exporterContentMap.getValues(exporter);
+                               if ( exportSpecificContents==null || exportSpecificContents.isEmpty() ) continue;
+                               RecordType exporterOptions = exporter.exportAction().options(ctx, exportSpecificContents);
+                               if ( exporterOptions == null ) continue;
+                               optionsRecord.mergeRecord( exporterOptions );
+                       }
+
+                       // 4. Load default and previous selections of the options ( All but publisher )
+                       ctx.databoard.clear();
+                       RecordBinding optionsBinding = ctx.databoard.getMutableBinding( optionsRecord );
+                       Object optionsObj = optionsBinding.createDefaultUnchecked();
+                       Variant options = new Variant(optionsBinding, optionsObj);
+                       {
+                               Preferences workspaceScopePrefs = ctx.store;
+                               Preferences contentScopePrefs = ctx.store( contents );
+                               
+                               for (Exporter exporter : orderedExporters.values()) {
+                                       exporter.exportAction().fillDefaultPrefs(ctx, options);
+                                       exporter.exportAction().loadPref(options, contentScopePrefs, workspaceScopePrefs);
+                               }
+                               
+                               for (Format format : contentByFormat.getKeys()) {
+                                       format.formatActions().fillDefaultPrefs( ctx, options );
+                                       format.formatActions().loadPref(options, contentScopePrefs, workspaceScopePrefs);
+                               }
+                               
+                               fillDefaultPrefs(options);
+                               loadPref(options, contentScopePrefs, workspaceScopePrefs);
+                       }
+                       
+                       // 5. Create form
+                       form.addFields(composite, optionsRecord);
+                       form.addListener(composite, form.type(), modificationListener);
+                       Composite outputOptionsGroup = (Composite) form.getControl(composite, P_OUTPUT_OPTIONS);
+                       RecordType dummy = new RecordType();
+                       dummy.addComponent(S_OUTPUT_OPTIONS, outputOptions);
+                       form.addListener(outputOptionsGroup, dummy, outputSettingsModifiedListener);
+                       form.writeFields(composite, optionsBinding, optionsObj);
+                       
+                       // 6. Add publisher
+                       {
+                               selection = contents;
+                               Preferences workspaceScopePrefs = ctx.store;
+                               Preferences contentScopePrefs = ctx.store.node( "Selection-"+selectionHash );
+                               selectedPublisherId = ctx.store.get("publisherId", "");         
+                               Publisher publisher = ctx.eep.getPublisher(selectedPublisherId);
+                               if ( publisher != null ) {
+                                       
+                                       // 6A. Manifest
+                                       List<Content> manifest = getManifestFor(contents, options);
+                                       
+                                       // 6B. Default and previous settings
+                                       String label = publisher.label();
+                                       RecordType publisherOptionsType = publisher.publisherClass().locationOptions(ctx, manifest);
+                                       RecordType publisherOptionsRootType = new RecordType();
+                                       publisherOptionsRootType.addComponent(label, publisherOptionsType);
+                                       RecordBinding publisherOptionsRootBinding = ctx.databoard.getMutableBinding( publisherOptionsRootType );
+                                       Object publisherOptionsRootObj = publisherOptionsRootBinding.createDefaultUnchecked();
+                                       Variant publisherOptionsRoot = new Variant(publisherOptionsRootBinding, publisherOptionsRootObj);
+                                       try {
+                                               ChildReference locationOptionsRef = new LabelReference( label );
+                                               Variant locationOptions = publisherOptionsRoot.getComponent( locationOptionsRef );
+                                               publisher.publisherClass().fillDefaultPrefs(ctx, selection, options, locationOptions);
+                                               publisher.publisherClass().loadPref(locationOptions, contentScopePrefs, workspaceScopePrefs);   
+                                       } catch ( AccessorConstructionException ee ) {
+                                       }
+                                       
+                                       // 6C. Add Publisher form
+                                       addPublisherToGroup(selectedPublisherId, manifest);
+                                       form.writeFields(composite, publisherOptionsRootBinding, publisherOptionsRootObj);
+                               }
+                       }
+
+                       // 8. Validate page
+                       composite.pack();
+                       validate();
+                       
+               } catch (BindingException e) {
+            ExceptionUtils.logError(e);                        
+                       ShowMessage.showError("Unexpected error", e.getClass().getName()+" "+e.getMessage());
+                       throw new RuntimeBindingException(e);
+               } catch (ExportException e) {
+            ExceptionUtils.logError(e);                        
+                       ShowMessage.showError("Unexpected error", e.getClass().getName()+" "+e.getMessage());
+               } catch (DatatypeConstructionException e) {
+            ExceptionUtils.logError(e);                        
+                       ShowMessage.showError("Unexpected error", e.getClass().getName()+" "+e.getMessage());
+               } catch (AccessorConstructionException e) {
+            ExceptionUtils.logError(e);                        
+                       ShowMessage.showError("Unexpected error", e.getClass().getName()+" "+e.getMessage());
+               } catch (AccessorException e) {
+            ExceptionUtils.logError(e);                        
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+               
+       }
+
+       /**
+        * Saves publisher prefs from the UI to preference nodes.
+        * 
+        * @throws ExportException
+        */
+       void savePublisherPref(String publisherId, Variant options, Preferences contentScopePrefs, Preferences workspaceScopePrefs) throws ExportException {
+               try {
+//                     Preferences contentScopePrefs = ctx.store.node( "Selection-"+selectionHash );
+//                     Preferences workspaceScopePrefs = ctx.store;
+
+//                     RecordBinding binding = ctx.databoard.getMutableBinding( form.type() );
+//                     Object obj = binding.createDefault();
+//                     form.readFields(composite, binding, obj);
+//                     Variant options = new Variant(binding, obj);
+                       
+                       Publisher publisher = ctx.eep.getPublisher( publisherId );
+                       if ( publisher==null ) return;
+                       Variant locationOptions = options.getComponent( new LabelReference(publisher.label()) );
+                       publisher.publisherClass().savePref(locationOptions, contentScopePrefs, workspaceScopePrefs);
+//             } catch (BindingException e) {
+//                     throw new ExportException( e );
+               } catch (AccessorConstructionException e) {
+            ExceptionUtils.logError(e);                        
+                       //throw new ExportException( e );
+//             } catch (AccessorException e) {
+//                     throw new ExportException( e );
+               }
+       }
+       
+       void loadPublisherPref(String publisherId, Variant options, Preferences contentScopePrefs, Preferences workspaceScopePrefs) throws ExportException {
+               try {
+//                     Preferences contentScopePrefs = ctx.store.node( "Selection-"+selectionHash );
+//                     Preferences workspaceScopePrefs = ctx.store;
+                       
+                       Publisher publisher = ctx.eep.getPublisher( publisherId );
+                       if ( publisher == null ) return;
+                       
+                       // There is a problem here if selection was built with manifest
+//                     RecordType optionsType = publisher.publiserClass().locationOptions(ctx, selection);                     
+//                     RecordBinding binding = ctx.databoard.getMutableBinding( optionsType );
+//                     Object obj = binding.createDefault();
+//                     Variant locationOptions = new Variant(binding, obj);
+                       Variant locationOptions = options.getComponent( new LabelReference( publisher.label() ) );
+                       publisher.publisherClass().fillDefaultPrefs(ctx, selection, options, locationOptions);
+                       publisher.publisherClass().loadPref(locationOptions, contentScopePrefs, workspaceScopePrefs);   
+//             } catch (BindingException e) {
+//                     throw new ExportException( e );
+               } catch (AccessorConstructionException e) {
+            ExceptionUtils.logError(e);                        
+                       //throw new ExportException( e );
+//             } catch (AccessorException e) {
+//                     throw new ExportException( e );
+               }
+       }
+
+       /**
+        * Remove any publisher specific controls from output options.
+        */
+       void cleanPublisherFromGroup(String oldPublisherId) {
+               Publisher publisher = ctx.eep.getPublisher(oldPublisherId);
+               if ( publisher != null ) {
+                       String fieldName = publisher.label();
+                       if ( form.type().hasComponent(fieldName)) {
+                               form.type().removeComponent(fieldName);
+                               ctx.databoard.clear();
+                       }
+               }
+               Combo publishTo = (Combo) form.getControl(composite, P_PUBLISH);
+               Composite group = (Composite) publishTo.getParent();
+               Control children[] = group.getChildren();
+               int index = Arrays.indexOf( children, publishTo );
+               for (int i=children.length-1; i>index; i--) {
+                       children[i].dispose();
+               }
+       }
+       
+       /**
+        * Add controls of a publisher id 
+        * 
+        * @param publisherId
+        * @throws ExportException 
+        */
+       void addPublisherToGroup( String publisherId, List<Content> contents ) throws ExportException {
+               try {
+                       Composite group = (Composite) form.getControl(composite, P_OUTPUT_OPTIONS);
+                       if ( group == null ) return;
+                       Publisher publisher = ctx.eep.getPublisher( publisherId );
+                       if ( publisher == null ) return;
+                       RecordType publisherOptions = publisher.publisherClass().locationOptions(ctx, contents);                
+                       //form.type().addComponent(publisher.label(), publisherOptions);
+                       RecordType options = new RecordType();
+                       options.addComponent(publisher.label(), publisherOptions);
+                       form.addFields(group, publisherOptions, publisher.label());
+                       form.addListener(group, options, modificationListener);
+                       ctx.databoard.clear();
+               } catch (BindingException e) {
+                       throw new ExportException(e);
+               } catch (AccessorConstructionException e) {
+                       throw new ExportException(e);
+               } catch (AccessorException e) {
+                       throw new ExportException(e);
+               }
+       }
+       
+       List<Content> getManifestFor(List<Content> selection, Variant options) {                
+               try {
+                       ExportWizardResult result = new ExportWizardResult();
+                       result.options = options;
+                       result.accessor = Accessors.getAccessor(result.options);
+                       result.contents = selection;
+                       result.type = (RecordType) options.type();
+                       result.publisherId = "file";
+                       
+                       List<ExportAction> actions = new ArrayList<ExportAction>();
+                       List<Content> manifest = new ArrayList<Content>(); 
+                       result.createExportActions(ctx, actions, manifest);
+                       return manifest;
+               } catch (AccessorConstructionException e) {
+                       return selection;
+               } catch (ExportException e) {
+                       e.printStackTrace();
+                       return selection;
+               }
+       }       
+       
+       public ExportWizardResult getOutput()
+       throws ExportException
+       {
+               ExportWizardResult result = new ExportWizardResult();
+               try {
+                       ctx.databoard.clear();
+                       result.type = form.type();
+                   RecordBinding binding = (RecordBinding) ctx.databoard.getMutableBinding( result.type );
+                   Object optionsObj = binding.createDefault();
+                   result.options = new Variant(binding, optionsObj);
+                       form.readFields(composite, binding, optionsObj);
+                       result.accessor = Accessors.getAccessor(result.options);
+                       result.contents = selection;
+                       
+                       
+                       String publisherLabel = ExporterUtils.getUnionValue(result.accessor, P_PUBLISH);
+                       Publisher publisher = ctx.eep.getPublisherByLabel(publisherLabel);
+                       result.publisherId = publisher==null?"":publisher.id();                 
+                       
+               } catch (BindingException e) {
+                       throw new ExportException(e);                   
+               } catch (AccessorException e) {
+                       throw new ExportException(e);
+               } catch (AccessorConstructionException e) {
+                       throw new ExportException(e);
+               }
+               
+               return result;
+       }
+               
+       void validate() {               
+               List<Problem> problems = form.validate(composite);
+               List<String> errs = DataboardForm.toStrings(problems);
+               
+               if ( errs.isEmpty() ) {
+                       try {
+                               ExportWizardResult result = getOutput();
+                               ExportPlan plan = new ExportPlan();
+                               result.createPlan(ctx, plan);
+                               ExportManager mgr = new ExportManager(result.options, ctx);
+                               errs.addAll( mgr.validate(ctx, plan) ) ;
+                       } catch (ExportException e) {
+                               errs.add(e.getMessage());
+                       }
+               } else {
+                       CollectionUtils.unique( errs );                 
+               }
+               
+
+               setErrorMessage( errs.isEmpty() ? null : CollectionUtils.toString(errs, ", ") );
+               setPageComplete( errs.isEmpty() );
+       }
+               
+       /**
+        * Save wizard preferences
+        * 
+        * @param options
+        * @param contentScopePrefs
+        * @param workspaceScopePrefs
+        * @throws ExportException
+        */
+       void savePref(Variant options, Preferences contentScopePrefs, Preferences workspaceScopePrefs) throws ExportException
+       {
+        try {
+                       RecordAccessor ra = Accessors.getAccessor(options);
+                       String publisherId = ExporterUtils.getUnionValue(ra, P_PUBLISH); 
+                       Publisher publisher = ctx.eep.getPublisherByLabel(publisherId);
+                       if ( publisher!=null ) workspaceScopePrefs.put("publisherId", publisher.id());
+                       
+                       if ( ra.type().hasComponent(P_OUTPUT_OPTIONS.label) ) {
+                               RecordAccessor rao = ra.getComponent( P_OUTPUT_OPTIONS );
+                                                               
+                               Pattern merge_pattern = Pattern.compile("Merge ([^\\s]*) content into one file");
+                               Pattern include_pattern = Pattern.compile("Include attachments to ([^\\s]*)");
+                               Pattern export_pattern = Pattern.compile("Export attachments of ([^\\s]*) to separate files");
+                               
+                               for (int i=0; i<rao.count(); i++) {
+                                       String name = rao.type().getComponent(i).name;
+                                       Matcher m;
+                                       
+                                       m = merge_pattern.matcher(name);
+                                       if ( m.matches() ) {
+                                               String fileExt = m.group(1);
+                                               Format format = ctx.eep.getFormatByExt(fileExt);
+                                               Boolean value = (Boolean) rao.getFieldValue(i, Bindings.BOOLEAN);
+                                               String key = format.id()+"_merge";
+                                               contentScopePrefs.putBoolean(key, value);
+                                               workspaceScopePrefs.putBoolean(key, value);
+                                       }
+                                       
+                                       m = include_pattern.matcher(name);
+                                       if ( m.matches() ) {
+                                               String fileExt = m.group(1);
+                                               Format format = ctx.eep.getFormatByExt(fileExt);
+                                               Boolean value = (Boolean) rao.getFieldValue(i, Bindings.BOOLEAN);
+                                               String key = format.id()+"_include_attachments";
+                                               contentScopePrefs.putBoolean(key, value);
+                                               workspaceScopePrefs.putBoolean(key, value);
+                                       }
+                                       
+                                       m = export_pattern.matcher(name);
+                                       if ( m.matches() ) {
+                                               String fileExt = m.group(1);
+                                               Format format = ctx.eep.getFormatByExt(fileExt);
+                                               Boolean value = (Boolean) rao.getFieldValue(i, Bindings.BOOLEAN);
+                                               String key = format.id()+"_export_attachments";
+                                               contentScopePrefs.putBoolean(key, value);
+                                               workspaceScopePrefs.putBoolean(key, value);
+                                       }
+                               }
+                       }
+                       
+               } catch (AccessorConstructionException e) {
+                       throw new ExportException(e);
+               } catch (AccessorException e) {
+                       throw new ExportException(e);
+               }
+               
+       }
+
+       /**
+        * Load wizard preferences
+        * 
+        * @param binding
+        * @param options
+        * @param contentScopePrefs
+        * @param workspaceScopePrefs
+        * @throws ExportException
+        */
+       void loadPref(Variant options, Preferences contentScopePrefs, Preferences workspaceScopePrefs) throws ExportException
+       {
+               //Preferences workspaceScopePrefs = ctx.store;
+        try {
+                       RecordAccessor ra = Accessors.getAccessor(options);             
+                       String publisherId = workspaceScopePrefs.get("publisherId", "");
+                       Publisher publisher = ctx.eep.getPublisher(publisherId);
+                       if ( publisher != null ) ExporterUtils.setUnionValue(ra, P_PUBLISH, publisher.label());
+                       
+                       if ( ra.type().hasComponent(P_OUTPUT_OPTIONS.label) ) {
+                               RecordAccessor rao = ra.getComponent( P_OUTPUT_OPTIONS );
+                                                               
+                               for (Format format : ctx.eep.formats()) {
+                                       if ( format.isGroupFormat() ) {
+                                               String key = format.id()+"_merge";
+                                               Boolean value = null;
+                                               if ( containsKey(contentScopePrefs, key) ) {
+                                                       value = contentScopePrefs.getBoolean(key, false); 
+                                               } else if ( containsKey(workspaceScopePrefs, key) ) {
+                                                       value = workspaceScopePrefs.getBoolean(key, false);
+                                               }
+                                                                                       
+                                               if ( value != null ) {
+                                                       String key2 = "Merge "+format.fileext()+" content into one file";
+                                                       int index = rao.type().getComponentIndex2(key2);
+                                                       if ( index>=0 ) {
+                                                               rao.setFieldValue(index, Bindings.BOOLEAN, value);
+                                                       }
+                                               }                                       
+                                       }
+                                       
+                                       if ( format.isContainerFormat() ) {
+                                               String key = format.id()+"_include_attachments";
+                                               Boolean value = null;
+                                               if ( containsKey(contentScopePrefs, key) ) {
+                                                       value = contentScopePrefs.getBoolean(key, false); 
+                                               } else if ( containsKey(workspaceScopePrefs, key) ) {
+                                                       value = workspaceScopePrefs.getBoolean(key, false);
+                                               }
+                                                                                       
+                                               if ( value != null ) {
+                                                       String key2 = "Include attachments to "+format.fileext();
+                                                       int index = rao.type().getComponentIndex2(key2);
+                                                       if ( index>=0 ) {
+                                                               rao.setFieldValue(index, Bindings.BOOLEAN, value);
+                                                       }
+                                               }                                       
+                                       }
+                                       
+                                       if ( format.isContainerFormat() ) {
+                                               String key = format.id()+"_export_attachments";
+                                               Boolean value = null;
+                                               if ( containsKey(contentScopePrefs, key) ) {
+                                                       value = contentScopePrefs.getBoolean(key, false); 
+                                               } else if ( containsKey(workspaceScopePrefs, key) ) {
+                                                       value = workspaceScopePrefs.getBoolean(key, false);
+                                               }
+                                                                                       
+                                               if ( value != null ) {
+                                                       String key2 = "Export attachments of "+format.fileext()+" to separate files";
+                                                       int index = rao.type().getComponentIndex2(key2);
+                                                       if ( index>=0 ) {
+                                                               rao.setFieldValue(index, Bindings.BOOLEAN, value);
+                                                       }
+                                               }                                       
+                                       }
+                                       
+                               }
+                       }
+                                       
+               } catch (AccessorConstructionException e) {
+                       throw new ExportException(e);
+               } catch (AccessorException e) {
+                       throw new ExportException(e);
+               }               
+       }
+       
+       /**
+        * Save selections from previous form
+        */
+       public void savePrefs() throws ExportException {
+               try {
+                       int oldSelectionHash = selectionHash;                   
+                       Preferences contentScopePrefs = ctx.store.node( "Selection-"+oldSelectionHash );
+                       Preferences workspaceScopePrefs = ctx.store;
+                                       
+                       RecordBinding binding = ctx.databoard.getMutableBinding( form.type() );
+                       Object obj = binding.createDefault();
+                       Variant options = new Variant(binding, obj);
+                       form.readFields(composite, binding, obj);
+                                               
+                       Combo publishTo = (Combo) form.getControl(composite, P_PUBLISH);                
+                       String publisherLabel = publishTo==null?null:publishTo.getText();
+                       if ( publisherLabel != null ) {
+                               Publisher publisher = ctx.eep.getPublisherByLabel( publisherLabel );
+                               if ( publisher!=null ) {
+                                       try {
+                                               ChildReference locationOptionsRef = new LabelReference(publisher.label());
+                                               Variant locationOptions = options.getComponent( locationOptionsRef );
+                                               publisher.publisherClass().savePref(locationOptions, contentScopePrefs, workspaceScopePrefs);
+                                       } catch ( AccessorConstructionException e ) {} 
+                               }
+                       }
+                       
+                       for (Exporter exporter : ctx.eep.exporters()) {
+                               exporter.exportAction().savePref(options, contentScopePrefs, workspaceScopePrefs);
+                       }
+                                       
+                       for (Format format : ctx.eep.formats()) {
+                               format.formatActions().savePref(options, contentScopePrefs, workspaceScopePrefs);
+                       }                               
+                                       
+                       savePref(options, contentScopePrefs, workspaceScopePrefs);
+               } catch (BindingException e) {
+                       throw new ExportException( e ); 
+               } catch (AccessorConstructionException e) {
+                       throw new ExportException( e ); 
+               } catch (AccessorException e) {
+                       throw new ExportException( e ); 
+               } catch (ExportException e) {
+                       throw new ExportException( e ); 
+               }
+       }
+
+       public void fillDefaultPrefs(Variant options) throws ExportException {
+        try {
+                       RecordAccessor ra = Accessors.getAccessor(options);
+
+                       if ( ra.type().hasComponent(P_OUTPUT_OPTIONS.label) ) {
+                               RecordAccessor rao = ra.getComponent( P_OUTPUT_OPTIONS );
+                               
+                               for (Format format : ctx.eep.formats()) {
+                                       if ( format.isContainerFormat() ) {
+                                               String key = "Include attachments to "+format.fileext();
+                                               int index = rao.type().getComponentIndex2(key);
+                                               if ( index>=0 ) rao.setFieldValue(index, Bindings.BOOLEAN, true);
+                                               
+                                               key = "Export attachments of "+format.fileext()+" to separate files";;
+                                               index = rao.type().getComponentIndex2(key);
+                                               if ( index>=0 ) rao.setFieldValue(index, Bindings.BOOLEAN, true);
+                                       }                       
+                               }
+                               
+                       }
+               
+               } catch (AccessorConstructionException e) {
+                       throw new ExportException(e);
+               } catch (AccessorException e) {
+                       throw new ExportException(e);
+               }
+       }       
+       
+       static boolean containsKey(Preferences pref, String key) {
+               try {
+                       for (String x : pref.keys()) if ( x.equals(key) ) return true;
+               } catch (BackingStoreException e) {
+                       e.printStackTrace();
+               } 
+               return false;           
+       }
+               
+}