diff --git a/product/Formulator/CREDITS.txt b/product/Formulator/CREDITS.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cdfa5d1c7cb3cf7fee16090f52e9555adfed40a8
--- /dev/null
+++ b/product/Formulator/CREDITS.txt
@@ -0,0 +1,77 @@
+Formulator Credits
+
+  Martijn Faassen (faassen@vet.uu.nl) -- Main developer, design and
+                              implementation. 
+
+
+Many thanks go to:
+      
+  Kit Blake (kitblake at v2.nl) -- UI help and design help.
+
+  Yury Don (yura at vpcit.ru) -- contributed EmailField and FloatField,
+                              design and implementation help.
+
+  Stephan Richter (srichter at iuveno-net.de) -- contributed LinkField and
+                              FileField. Contributed PatternChecker
+                              module used by PatternField. Other 
+                              design and implementation help.
+
+  Nicola Larosa (nico at tekNico.net) -- feedback and bugfixes.
+
+  Magnus Heino (magus.heino at rivermen.se) -- feedback and bugfixes.
+
+  Joel Burton (jburton at scw.org) -- feedback and bugfixes.
+
+  Ulrich Eck (ueck at net-labs.de) -- much help and patience with the
+                                   TALES tab.
+  
+  Dirk Datzert (Dirk.Datzert at rasselstein-hoesch.de) -- feedback and bugfixes.
+
+  Max Petrich (petrich.max at kis-solution.de) -- feedback and bugfixes.
+
+  Matt Behrens (matt.behrens at kohler.com) -- feedback and bugfixes.
+
+  Nikolay Kim (fafhrd at datacom.kz) -- code inspiration for
+                                     XMLToForm/FormToXML.
+
+  Godefroid Chapelle (gotcha at swing.be) -- Bugfixes.
+
+  Alan Runyan (runyaga at runyaga.com) -- Fix to email regular expression.
+
+  Sascha Welter (welter at network-ag.com) -- Extensive help with email
+                                           regular expression.
+
+  Clemens Klein-Robbenhaar (robbenhaar at espresto.com) -- Many bugfixes
+                                                        and feature
+                                                        additions.
+              
+  Christian Zagrodnick (cz at gocept.com) -- Unicode awareness fixes and 
+                                          XML entry form.
+
+  Iutin Vyacheslav (iutin at whirix.com) -- am/pm feature for DateTime
+                                         fields.
+
+  Kapil Thangavelu (k_vertigo at objectrealms.net) -- Enabled ':record' rendering.
+
+  Pierre-Julien Grizel (grizel at ingeniweb.com) -- ProductForm.
+
+  Sébastien Robin (seb at nexedi.com) -- more consistent ordering in XML
+                                      serialization.
+
+  Guido Wesdorp (guido at infrae.com) -- Added extra_item attribute on
+                                      compound fields.
+
+  Yura Petrov (ypetrov at naumen.ru) -- Various FSForm related
+                                     improvements.
+
+  Vladimir Voznesensky (vovic at smtp.ru) -- Enabling/disabling of fields.
+            
+  Special thanks also goes to Rik Hoekstra.
+
+  Also a thank you to those few valiant souls who suffered through the
+  bugs of ZFormulator, the previous implementation. Let's hope this
+  one's better!
+
+
+
+
diff --git a/product/Formulator/DummyField.py b/product/Formulator/DummyField.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e01259693c60332df73eadd221b6d1873957b5e
--- /dev/null
+++ b/product/Formulator/DummyField.py
@@ -0,0 +1,38 @@
+"""
+This module contains some magic glue to make it seem as if we
+can refer to field classes before they've been defined, through the
+'fields' class.
+
+This way, they can be used to create properties on fields.
+When the field classes have been defined, get_field()
+can be used on FieldProperty objects to get an
+actual field object.
+"""
+
+from FieldRegistry import FieldRegistry
+
+class DummyFieldFactory:
+    def __getattr__(self, name):
+        return DummyField(name)
+
+fields = DummyFieldFactory()
+    
+class DummyField:
+    def __init__(self, desired_meta_class):
+        self.desired_meta_class = desired_meta_class
+
+    def __call__(self, id, **kw):
+        self.id = id
+        self.kw = kw
+        return self
+
+    def get_value(self, name):
+        return self.kw.get(name, "")
+          
+    def get_real_field(self):
+        """Get an actual field for this property.
+        """
+        return apply(FieldRegistry.get_field_class(self.desired_meta_class),
+                     (self.id,), self.kw)
+
+                                                  
diff --git a/product/Formulator/Errors.py b/product/Formulator/Errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ad8a15e1abb37536921299e4bb20025eb938681
--- /dev/null
+++ b/product/Formulator/Errors.py
@@ -0,0 +1,36 @@
+"""Exception Classes for Formulator"""
+
+# These classes are placed here so that they can be imported into TTW Python
+# scripts. To do so, add the following line to your Py script:
+# from Products.Formulator.Errors import ValidationError, FormValidationError
+
+from Products.PythonScripts.Utility import allow_class
+
+class FormValidationError(Exception):
+
+    def __init__(self, errors, result):
+        Exception.__init__(self,"Form Validation Error")
+        self.errors = errors
+        self.result = result
+        
+allow_class(FormValidationError)
+
+class ValidationError(Exception):
+    
+    def __init__(self, error_key, field):
+        Exception.__init__(self, error_key)
+        self.error_key = error_key
+        self.field_id = field.id
+        self.field = field
+        self.error_text = field.get_error_message(error_key)
+
+allow_class(ValidationError)
+
+class FieldDisabledError(AttributeError):
+
+    def __init__(self, error_key, field):
+        AttributeError.__init__(self, error_key)
+        self.field_id = field.id
+        self.field = field
+
+allow_class(FieldDisabledError)
diff --git a/product/Formulator/FSForm.py b/product/Formulator/FSForm.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c9358d8a21286703f4f9f6b20504b5f200b9ae0
--- /dev/null
+++ b/product/Formulator/FSForm.py
@@ -0,0 +1,114 @@
+import Globals
+from AccessControl import ClassSecurityInfo
+
+try:
+    import Products.FileSystemSite
+except ImportError:
+    # use CMF product
+    from Products.CMFCore.CMFCorePermissions import View
+    from Products.CMFCore.FSObject import FSObject
+    from Products.CMFCore.DirectoryView import registerFileExtension,\
+                                               registerMetaType, expandpath
+else:
+    # use FileSystemSite product
+    from Products.FileSystemSite.Permissions import View
+    from Products.FileSystemSite.FSObject import FSObject
+    from Products.FileSystemSite.DirectoryView import registerFileExtension,\
+                                                      registerMetaType, expandpath
+
+from Products.Formulator.Form import ZMIForm
+from Products.Formulator.XMLToForm import XMLToForm
+
+class FSForm(FSObject, ZMIForm):
+    """FSForm."""
+
+    meta_type = 'Filesystem Formulator Form'
+
+    manage_options = (
+        (
+        {'label':'Customize', 'action':'manage_main'},
+        {'label':'Test', 'action':'formTest'},
+        )
+        )
+
+    _updateFromFS = FSObject._updateFromFS
+
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(View)
+
+    def __init__(self, id, filepath, fullname=None, properties=None):
+        FSObject.__init__(self, id, filepath, fullname, properties)
+
+    def _createZODBClone(self):
+        # not implemented yet
+        return None
+
+    def _readFile(self, reparse):
+        file = open(expandpath(self._filepath), 'rb')
+        try:
+            data = file.read()
+        finally:
+            file.close()
+
+        # update the form with the xml data
+        try:
+            XMLToForm(data, self)
+        except:
+            # bare except here, but I hope this is ok, as the
+            # exception should be reraised
+            # (except if the LOG raises another one ...
+            # should we be more paranoid here?)
+            import zLOG
+            zLOG.LOG(
+                'Formulator.FSForm', zLOG.ERROR,
+                'error reading form from file ' +
+                expandpath(self._filepath))
+            raise
+
+    #### The following is mainly taken from Form.py ACCESSORS section ###
+
+##     def get_field_ids(self):
+##         self._updateFromFS()
+##         return ZMIForm.get_field_ids(self)
+
+##     def get_fields_in_group(self, group):
+##         self._updateFromFS()
+##         return ZMIForm.get_fields_in_group(self, group)
+
+##     def has_field(self, id):
+##         self._updateFromFS()
+##         return ZMIForm.has_field(self, id)
+
+##     def get_field(self, id):
+##         self._updateFromFS()
+##         return ZMIForm.get_field(self, id)
+
+##     def get_groups(self):
+##         self._updateFromFS()
+##         return ZMIForm.get_groups(self)
+
+##     def get_form_encoding(self):
+##         self._updateFromFS()
+##         return ZMIForm.get_form_encoding(self)
+
+##     def header(self):
+##         self._updateFromFS()
+##         return ZMIForm.header(self)
+
+##     def get_xml(self):
+##         self._updateFromFS()
+##         return ZMIForm.get_xml(self)
+
+##     def all_meta_types(self):
+##         self._updateFromFS()
+##         return ZMIForm.all_meta_types(self)
+
+##     security.declareProtected('View management screens', 'get_group_rows')
+##     def get_group_rows(self):
+##         self._updateFromFS()
+##         return ZMIForm.get_group_rows(self)
+
+Globals.InitializeClass(FSForm)
+
+registerFileExtension('form', FSForm)
+registerMetaType('FSForm', FSForm)
diff --git a/product/Formulator/Field.py b/product/Formulator/Field.py
new file mode 100644
index 0000000000000000000000000000000000000000..e93af811ebd4f3a9ea8acd86f97392ff4aaae6d0
--- /dev/null
+++ b/product/Formulator/Field.py
@@ -0,0 +1,633 @@
+import Globals
+import Acquisition
+from Globals import Persistent, DTMLFile
+from AccessControl import ClassSecurityInfo
+import OFS
+from Shared.DC.Scripts.Bindings import Bindings
+from Errors import ValidationError
+from Products.Formulator.Widget import MultiItemsWidget
+from zLOG import LOG
+
+class Field:
+    """Base class of all fields.
+    A field is an object consisting of a widget and a validator.
+    """
+    security = ClassSecurityInfo()
+
+    # this is a field
+    is_field = 1
+    # this is not an internal field (can be overridden by subclass)
+    internal_field = 0
+    # can alternatively render this field with Zope's :record syntax
+    # this will be the record's name
+    field_record = None
+    
+    def __init__(self, id, **kw):
+        self.id = id
+        # initialize values of fields in form
+        self.initialize_values(kw)
+        # initialize tales expression for fields in form
+        self.initialize_tales()
+        # initialize overrides of fields in form
+        self.initialize_overrides()
+        
+        # initialize message values with defaults
+        message_values = {}
+        for message_name in self.validator.message_names:
+            message_values[message_name] = getattr(self.validator,
+                                                   message_name)
+        self.message_values = message_values
+
+    security.declareProtected('Change Formulator Fields', 'initialize_values')
+    def initialize_values(self, dict):
+        """Initialize values for properties, defined by fields in
+        associated form.
+        """
+        values = {}
+        for field in self.form.get_fields(include_disabled=1):
+            id = field.id
+            value = dict.get(id, field.get_value('default'))
+            values[id] = value
+        self.values = values
+
+    security.declareProtected('Change Formulator Fields',
+                              'initialize_tales')
+    def initialize_tales(self):
+        """Initialize tales expressions for properties (to nothing).
+        """
+        tales = {}
+        for field in self.form.get_fields():
+            id = field.id
+            tales[id] = ""
+        self.tales = tales
+    
+    security.declareProtected('Change Formulator Fields',
+                              'initialize_overrides')
+    def initialize_overrides(self):
+        """Initialize overrides for properties (to nothing).
+        """
+        overrides = {}
+        for field in self.form.get_fields():
+            id = field.id
+            overrides[id] = ""
+        self.overrides = overrides
+        
+    security.declareProtected('Access contents information', 'has_value')
+    def has_value(self, id):
+        """Return true if the field defines such a value.
+        """
+        if self.values.has_key(id) or self.form.has_field(id):
+            return 1
+        else:
+            return 0
+
+    security.declareProtected('Access contents information', 'get_orig_value')
+    def get_orig_value(self, id):
+        """Get value for id; don't do any override calculation.
+        """
+        if self.values.has_key(id):
+            return self.values[id]
+        else:
+            return self.form.get_field(id).get_value('default')
+        
+    security.declareProtected('Access contents information', 'get_value')
+    def get_value(self, id, **kw):
+        """Get value for id.
+
+        Optionally pass keyword arguments that get passed to TALES
+        expression.
+        """
+        tales_expr = self.tales.get(id, "")
+        
+        if tales_expr:
+            # For some reason, path expressions expect 'here' and 'request'
+            # to exist, otherwise they seem to fail. python expressions
+            # don't seem to have this problem.
+            
+            # add 'here' if not in kw
+            if not kw.has_key('here'):
+                kw['here'] = self.aq_parent
+            kw['request'] = self.REQUEST
+            value = tales_expr.__of__(self)(
+                field=self,
+                form=self.aq_parent, **kw)
+        else:   
+            override = self.overrides.get(id, "")
+            if override:
+                # call wrapped method to get answer
+                value = override.__of__(self)()
+            else:
+                # get normal value
+                value = self.get_orig_value(id)
+
+        # if normal value is a callable itself, wrap it
+        if callable(value):
+            return value.__of__(self)
+        else:
+            return value
+        
+    security.declareProtected('View management screens', 'get_override')
+    def get_override(self, id):
+        """Get override method for id (not wrapped)."""
+        return self.overrides.get(id, "")
+
+    security.declareProtected('View management screens', 'get_tales')
+    def get_tales(self, id):
+        """Get tales expression method for id."""
+        return self.tales.get(id, "")
+    
+    security.declareProtected('Access contents information', 'is_required')
+    def is_required(self):
+        """Check whether this field is required (utility function)
+        """
+        return self.has_value('required') and self.get_value('required')
+    
+    security.declareProtected('View management screens', 'get_error_names')
+    def get_error_names(self):
+        """Get error messages.
+        """
+        return self.validator.message_names
+
+    security.declareProtected('Access contents information',
+                              'generate_field_key')
+    def generate_field_key(self, validation=0, key=None):
+      """Generate the key Silva uses to render the field in the form.
+      """
+      # Patched by JPS for ERP5 in order to
+      # dynamically change the name
+      if key is not None:
+        return 'field_%s' % key
+      if self.field_record is None:
+        return 'field_%s' % self.id
+      elif validation:
+        return self.id
+      elif isinstance(self.widget, MultiItemsWidget):
+        return "%s.%s:record:list" % (self.field_record, self.id)
+      else:
+        return '%s.%s:record' % (self.field_record, self.id)
+
+    def generate_subfield_key(self, id, validation=0, key=None):
+      """Generate the key Silva uses to render a sub field.
+         Added key parameter for ERP5
+         Added key parameter for ERP5 in order to be compatible with listbox/matrixbox
+      """
+      if key is None: key = self.id
+      if self.field_record is None or validation:
+          return 'subfield_%s_%s' % (key, id)
+      return '%s.subfield_%s_%s:record' % (self.field_record, key, id)
+
+    security.declareProtected('View management screens', 'get_error_message')
+    def get_error_message(self, name):
+        try:
+            return self.message_values[name]
+        except KeyError:
+            if name in self.validator.message_names:
+                return getattr(self.validator, name)
+            else:
+                return "Unknown error: %s" % name
+
+    security.declarePrivate('_render_helper')
+    def _render_helper(self, key, value, REQUEST, render_prefix=None):
+      value = self._get_default(key, value, REQUEST)
+      __traceback_info__ = ('key=%s value=%r' % (key, value))
+      if self.get_value('hidden', REQUEST=REQUEST):
+        return self.widget.render_hidden(self, key, value, REQUEST)
+      elif (not self.get_value('editable', REQUEST=REQUEST)):
+        return self.widget.render_view(self, value, REQUEST=REQUEST,
+                                       render_prefix=render_prefix)
+      else:
+        return self.widget.render(self, key, value, REQUEST,
+                                  render_prefix=render_prefix)
+
+    security.declarePrivate('_get_default')
+    def _get_default(self, key, value, REQUEST):
+        if value is not None:
+            return value
+        try:
+            value = REQUEST.form[key]
+        except (KeyError, AttributeError):
+            # fall back on default
+            return self.get_value('default')
+
+        # if we enter a string value while the field expects unicode,
+        # convert to unicode first
+        # this solves a problem when re-rendering a sticky form with
+        # values from request
+        if (self.has_value('unicode') and self.get_value('unicode') and
+            type(value) == type('')):
+            return unicode(value, self.get_form_encoding())
+        else:
+            return value
+
+    security.declarePrivate('_get_user_input_value')
+    def _get_user_input_value(self, key, REQUEST):
+      """
+      Try to get a value of the field from the REQUEST
+      """
+      return REQUEST.form[key]
+
+    security.declareProtected('View', 'render')
+    def render(self, value=None, REQUEST=None, key=None, render_prefix=None):
+      """Render the field widget.
+      value -- the value the field should have (for instance
+                from validation).
+      REQUEST -- REQUEST can contain raw (unvalidated) field
+                information. If value is None, REQUEST is searched
+                for this value.
+      if value and REQUEST are both None, the 'default' property of
+      the field will be used for the value.
+      """
+      return self._render_helper(self.generate_field_key(key=key), value, REQUEST,
+                                 render_prefix)
+
+    security.declareProtected('View', 'render_view')
+    def render_view(self, value=None, REQUEST=None, render_prefix=None):
+      """Render value to be viewed.
+      """
+      return self.widget.render_view(self, value, REQUEST=REQUEST)
+
+    security.declareProtected('View', 'render_pdf')
+    def render_pdf(self, value=None, REQUEST=None, key=None, **kw):
+      """
+      render_pdf renders the field for reportlab
+      """
+      return self.widget.render_pdf(self, value)
+
+    security.declareProtected('View', 'render_html')
+    def render_html(self, *args, **kw):
+      """
+      render_html is used to as definition of render method in Formulator.
+      """
+      return self.render(*args, **kw)
+
+    security.declareProtected('View', 'render_htmlgrid')
+    def render_htmlgrid(self, value=None, REQUEST=None, key=None, render_prefix=None):
+      """
+      render_htmlgrid returns a list of tuple (title, html render)
+      """
+      # What about CSS ? What about description ? What about error ?
+      widget_key = self.generate_field_key(key=key)
+      value = self._get_default(widget_key, value, REQUEST)
+      __traceback_info__ = ('key=%s value=%r' % (key, value))
+      return self.widget.render_htmlgrid(self, widget_key, value, REQUEST, render_prefix=render_prefix)
+
+    security.declareProtected('View', 'render_odf')
+    def render_odf(self, field=None, key=None, value=None, REQUEST=None,
+                     render_format='ooo', render_prefix=None):
+      return self.widget.render_odf(self, key, value, REQUEST, render_format,
+                                render_prefix)
+
+    security.declareProtected('View', 'render_css')
+    def render_css(self, REQUEST=None):
+      """
+      Generate css content which will be added inline.
+
+      XXX key parameter may be needed.
+      """
+      return self.widget.render_css(self, REQUEST)
+
+    security.declareProtected('View', 'get_css_list')
+    def get_css_list(self, REQUEST=None):
+      """
+        Returns list of css sheets needed by the field
+        to be included in global css imports
+      """
+      return self.widget.get_css_list(self, REQUEST)
+
+    security.declareProtected('View', 'get_javascript_list')
+    def get_javascript_list(self, REQUEST=None):
+      """
+        Returns list of javascript needed by the field
+        to be included in global js imports
+      """
+      return self.widget.get_javascript_list(self, REQUEST)
+
+    security.declareProtected('View', 'render_dict')
+    def render_dict(self, value=None, REQUEST=None, key=None, **kw):
+      """
+      This is yet another field rendering. It is designed to allow code to
+      understand field's value data by providing its type and format when
+      applicable.
+      """
+      return self.widget.render_dict(self, value)
+
+    def render_from_request(self, REQUEST):
+        """Convenience method; render the field widget from REQUEST
+        (unvalidated data), or default if no raw data is found.
+        """
+        return self._render_helper(self.generate_field_key(), None, REQUEST)
+
+    security.declareProtected('View', 'render_sub_field')
+    def render_sub_field(self, id, value=None, REQUEST=None, key=None, render_prefix=None):
+      """Render a sub field, as part of complete rendering of widget in
+      a form. Works like render() but for sub field.
+          Added key parameter for ERP5 in order to be compatible with listbox/matrixbox
+      """
+      return self.sub_form.get_field(id)._render_helper(
+          self.generate_subfield_key(id, key=key), value, REQUEST, render_prefix)
+
+    security.declareProtected('View', 'render_sub_field_from_request')
+    def render_sub_field_from_request(self, id, REQUEST):
+        """Convenience method; render the field widget from REQUEST
+        (unvalidated data), or default if no raw data is found.
+        """
+        return self.sub_form.get_field(id)._render_helper(
+            self.generate_subfield_key(id), None, REQUEST)
+    
+    security.declarePrivate('_validate_helper')
+    def _validate_helper(self, key, REQUEST):
+        value = self.validator.validate(self, key, REQUEST)
+        # now call external validator after all other validation
+        external_validator = self.get_value('external_validator')
+        if external_validator and not external_validator(value, REQUEST):
+            self.validator.raise_error('external_validator_failed', self)
+        return value
+
+    security.declareProtected('View', 'validate')
+    def validate(self, REQUEST):
+        """Validate/transform the field.
+        """
+        return self._validate_helper(
+            self.generate_field_key(validation=1), REQUEST)
+
+    security.declareProtected('View', 'need_validate')
+    def need_validate(self, REQUEST):
+        """Return true if validation is needed for this field.
+        """
+        return self.validator.need_validate(
+            self, self.generate_field_key(validation=1), REQUEST)
+
+    security.declareProtected('View', 'validate_sub_field')
+    def validate_sub_field(self, id, REQUEST, key=None):
+      """Validates a subfield (as part of field validation).
+      """
+      return self.sub_form.get_field(id)._validate_helper(
+      self.generate_subfield_key(id, validation=1, key=key), REQUEST)
+
+    def PrincipiaSearchSource(self):
+      def getSearchSource(obj):
+        obj_type = type(obj)
+        if obj_type is MethodField.Method:
+          return obj.method_name
+        elif obj_type is TALESField.TALESMethod:
+          return obj._text
+        return str(obj)
+      return ''.join(map(getSearchSource,        (self.values.values()+self.tales.values()+self.overrides.values())))
+
+Globals.InitializeClass(Field)
+    
+class ZMIField(
+    Acquisition.Implicit,
+    Persistent,
+    OFS.SimpleItem.Item,
+    Field,
+    ):
+    """Base class for a field implemented as a Python (file) product.
+    """
+    security = ClassSecurityInfo()
+
+    security.declareObjectProtected('View')
+   
+    # the various tabs of a field
+    manage_options = (
+        {'label':'Edit',       'action':'manage_main',
+         'help':('Formulator', 'fieldEdit.txt')},
+        {'label':'TALES',      'action':'manage_talesForm',
+         'help':('Formulator', 'fieldTales.txt')},
+        {'label':'Override',    'action':'manage_overrideForm',
+         'help':('Formulator', 'fieldOverride.txt')},
+        {'label':'Messages',   'action':'manage_messagesForm',
+         'help':('Formulator', 'fieldMessages.txt')},
+        {'label':'Test',       'action':'fieldTest',
+         'help':('Formulator', 'fieldTest.txt')},
+        ) + OFS.SimpleItem.SimpleItem.manage_options
+         
+    security.declareProtected('View', 'title')
+    def title(self):
+        """The title of this field."""
+        return self.get_value('title')
+
+    # display edit screen as main management screen
+    security.declareProtected('View management screens', 'manage_main')
+    manage_main = DTMLFile('dtml/fieldEdit', globals())
+
+    security.declareProtected('Change Formulator Fields', 'manage_edit')
+    def manage_edit(self, REQUEST):
+        """Submit Field edit form.
+        """
+        try:
+            # validate the form and get results
+            result = self.form.validate(REQUEST)
+        except ValidationError, err:
+            if REQUEST:
+                message = "Error: %s - %s" % (err.field.get_value('title'),
+                                              err.error_text)
+                return self.manage_main(self,REQUEST,
+                                        manage_tabs_message=message)
+            else:
+                raise
+
+        self._edit(result)
+        
+        if REQUEST:
+            message="Content changed."
+            return self.manage_main(self,REQUEST,
+                                    manage_tabs_message=message)
+
+    security.declareProtected('Change Formulator Fields', 'manage_edit_xmlrpc')
+    def manage_edit_xmlrpc(self, map):
+        """Edit Field Properties through XMLRPC
+        """
+        # BEWARE: there is no validation on the values passed through the map
+        self._edit(map)
+
+    def _edit(self, result):
+        # first check for any changes  
+        values = self.values
+        # if we are in unicode mode, convert result to unicode
+        # acquire get_unicode_mode and get_stored_encoding from form..
+        if self.get_unicode_mode():
+            new_result = {}
+            for key, value in result.items():
+                if type(value) == type(''):
+                    # in unicode mode, Formulator UI always uses UTF-8
+                    value = unicode(value, 'UTF-8')
+                new_result[key] = value
+            result = new_result
+
+        changed = []
+        for key, value in result.items():
+            # store keys for which we want to notify change
+            if not values.has_key(key) or values[key] != value:
+                changed.append(key)
+                          
+        # now do actual update of values
+        values.update(result)
+        self.values = values
+
+        # finally notify field of all changed values if necessary
+        for key in changed:
+            method_name = "on_value_%s_changed" % key
+            if hasattr(self, method_name):
+                getattr(self, method_name)(values[key])
+
+
+    security.declareProtected('Change Formulator Forms', 'manage_beforeDelete')
+    def manage_beforeDelete(self, item, container):
+        """Remove name from list if object is deleted.
+        """
+        # update group info in form
+        if hasattr(item.aq_explicit, 'is_field'):
+            container.field_removed(item.id)
+
+    security.declareProtected('Change Formulator Forms', 'manage_afterAdd')
+    def manage_afterAdd(self, item, container):
+        """What happens when we add a field.
+        """
+        # update group info in form
+        if hasattr(item.aq_explicit, 'is_field'):
+            container.field_added(item.id)
+
+    # methods screen
+    security.declareProtected('View management screens',
+                              'manage_overrideForm')
+    manage_overrideForm = DTMLFile('dtml/fieldOverride', globals())
+
+    security.declareProtected('Change Formulator Forms', 'manage_override')
+    def manage_override(self, REQUEST):
+        """Change override methods.
+        """
+        try:
+            # validate the form and get results
+            result = self.override_form.validate(REQUEST)
+        except ValidationError, err:
+            if REQUEST:
+                message = "Error: %s - %s" % (err.field.get_value('title'),
+                                              err.error_text)
+                return self.manage_overrideForm(self,REQUEST,
+                                                manage_tabs_message=message)
+            else:
+                raise
+
+        # update overrides of field with results
+        if not hasattr(self, "overrides"):
+            self.overrides = result
+        else:
+            self.overrides.update(result)
+            self.overrides = self.overrides
+        
+        if REQUEST:
+            message="Content changed."
+            return self.manage_overrideForm(self,REQUEST,
+                                            manage_tabs_message=message)
+
+    # tales screen
+    security.declareProtected('View management screens',
+                              'manage_talesForm')
+    manage_talesForm = DTMLFile('dtml/fieldTales', globals())
+
+    security.declareProtected('Change Formulator Forms', 'manage_tales')
+    def manage_tales(self, REQUEST):
+        """Change TALES expressions.
+        """
+        try:
+            # validate the form and get results
+            result = self.tales_form.validate(REQUEST)
+        except ValidationError, err:
+            if REQUEST:
+                message = "Error: %s - %s" % (err.field.get_value('title'),
+                                              err.error_text)
+                return self.manage_talesForm(self,REQUEST,
+                                             manage_tabs_message=message)
+            else:
+                raise
+
+        self._edit_tales(result)
+
+        if REQUEST:
+            message="Content changed."
+            return self.manage_talesForm(self, REQUEST,
+                                         manage_tabs_message=message)
+
+    def _edit_tales(self, result):
+        if not hasattr(self, 'tales'):
+            self.tales = result
+        else:
+            self.tales.update(result)
+            self.tales = self.tales
+
+    security.declareProtected('Change Formulator Forms', 'manage_tales_xmlrpc')
+    def manage_tales_xmlrpc(self, map):
+        """Change TALES expressions through XMLRPC.
+        """
+        # BEWARE: there is no validation on the values passed through the map
+        from TALESField import TALESMethod
+        result = {}
+        for key, value in map.items():
+            result[key] = TALESMethod(value)
+        self._edit_tales(result)
+
+    # display test screen
+    security.declareProtected('View management screens', 'fieldTest')
+    fieldTest = DTMLFile('dtml/fieldTest', globals())
+
+    # messages screen
+    security.declareProtected('View management screens', 'manage_messagesForm')
+    manage_messagesForm = DTMLFile('dtml/fieldMessages', globals())
+
+    # field list header
+    security.declareProtected('View management screens', 'fieldListHeader')
+    fieldListHeader = DTMLFile('dtml/fieldListHeader', globals())
+
+    # field description display
+    security.declareProtected('View management screens', 'fieldDescription')
+    fieldDescription = DTMLFile('dtml/fieldDescription', globals())
+    
+    security.declareProtected('Change Formulator Fields', 'manage_messages')
+    def manage_messages(self, REQUEST):
+        """Change message texts.
+        """
+        messages = self.message_values
+        unicode_mode = self.get_unicode_mode()
+        for message_key in self.get_error_names():
+            message = REQUEST[message_key]
+            if unicode_mode:
+                message = unicode(message, 'UTF-8')
+            messages[message_key] = message
+
+        self.message_values = messages
+        if REQUEST:
+            message="Content changed."
+            return self.manage_messagesForm(self,REQUEST,
+                                            manage_tabs_message=message)
+        
+    security.declareProtected('View', 'index_html')
+    def index_html(self, REQUEST):
+        """Render this field.
+        """
+        return self.render(REQUEST=REQUEST)
+
+    security.declareProtected('Access contents information', '__getitem__')
+    def __getitem__(self, key):
+        return self.get_value(key)
+    
+    security.declareProtected('View management screens', 'isTALESAvailable')
+    def isTALESAvailable(self):
+        """Return true only if TALES is available.
+        """
+        try:
+            from Products.PageTemplates.Expressions import getEngine
+            return 1
+        except ImportError:
+            return 0
+        
+Globals.InitializeClass(ZMIField)
+PythonField = ZMIField # NOTE: for backwards compatibility
+
+class ZClassField(Field):
+    """Base class for a field implemented as a ZClass.
+    """
+    pass
+
+
+
diff --git a/product/Formulator/FieldHelpTopic.py b/product/Formulator/FieldHelpTopic.py
new file mode 100644
index 0000000000000000000000000000000000000000..77a47ecf613f991e556155b2bdc95a06a3642161
--- /dev/null
+++ b/product/Formulator/FieldHelpTopic.py
@@ -0,0 +1,35 @@
+from Globals import DTMLFile
+from HelpSys import HelpTopic
+
+class FieldHelpTopic(HelpTopic.HelpTopic):
+    """A special help topic for fields.
+    """
+    meta_type = 'Help Topic'
+    
+    def __init__(self, id, title, field_class,
+                 permissions=None, categories=None):
+        self.id = id
+        self.title = title
+        self.field_class = field_class
+                                              
+        if permissions is not None:
+            self.permissions = permissions
+        if categories is not None:
+            self.categories = categories
+            
+    index_html = DTMLFile('dtml/FieldHelpTopic', globals())
+    
+    def SearchableText(self):
+        """Full text of the Help Topic, for indexing purposes."""
+        return "" # return self.index_html()
+
+    def get_groups(self):
+        """Get form groups of this field.
+        """
+        return self.field_class.form.get_groups()
+
+    def get_fields_in_group(self, group):
+        """Get the fields in the group.
+        """
+        return self.field_class.form.get_fields_in_group(group)
+    
diff --git a/product/Formulator/FieldRegistry.py b/product/Formulator/FieldRegistry.py
new file mode 100644
index 0000000000000000000000000000000000000000..e29e6139ab8536fa3790b53c4b518630c02dd754
--- /dev/null
+++ b/product/Formulator/FieldRegistry.py
@@ -0,0 +1,142 @@
+import os
+import OFS
+from Globals import ImageFile
+from FieldHelpTopic import FieldHelpTopic
+
+class FieldRegistry:
+    """A registry of fields, maintaining a dictionary with
+    the meta_type of the field classes as key and the field class as
+    values. Updates the Form as necessary as well.
+    """
+    def __init__(self):
+        """Initializer of FieldRegistry.
+        """
+        self._fields = {}
+
+    def get_field_class(self, fieldname):
+        """Get a certain field class by its name (meta_type)
+        fieldname -- the name of the field to get from the registry
+        """
+        return self._fields[fieldname]
+
+    def get_field_classes(self):
+        """Return all fields.
+        """
+        return self._fields
+    
+    def registerField(self, field_class, icon=None):
+        """Register field with Formulator.
+        field_class -- the class of the field to be registered
+        icon        -- optional filename of the icon
+        """
+        # put it in registry dictionary
+        self._fields[field_class.meta_type] = field_class
+        # set up dummy fields in field's form
+        initializeFieldForm(field_class)
+        # set up the icon if a filename is supplied
+        if icon:
+            setupIcon(field_class, icon, 'Formulator')
+
+    def registerFieldHelp(self, *args, **kw):
+        """XXX: this is a quick fix to avoid bloating the ZODB.
+           Proper fix should only add FieldHelp when it's missing.
+        """
+        pass
+
+    def initializeFields(self):
+        """Initialize all field classes in field forms to use actual field
+        objects so we can finally eat our own dogfood.
+        """
+        # for each field, realize fields in form
+        # this is finally possible as all field classes are now
+        # fully defined.
+        for field_class in self._fields.values():
+            field_class.form._realize_fields()
+            field_class.override_form._realize_fields()
+            field_class.tales_form._realize_fields()
+            
+# initialize registry as a singleton
+FieldRegistry = FieldRegistry()
+        
+def initializeFieldForm(field_class):
+    """Initialize the properties (fields and values) on a particular
+    field class. Also add the tales and override methods.
+    """
+    from Form import BasicForm
+    from DummyField import fields
+    
+    form = BasicForm()
+    override_form = BasicForm()
+    tales_form = BasicForm()
+    for field in getPropertyFields(field_class.widget):
+        form.add_field(field, "widget")
+        tales_field = fields.TALESField(field.id,
+                                        title=field.get_value('title'),
+                                        description="",
+                                        default="",
+                                        display_width=40,
+                                        required=0)
+        tales_form.add_field(tales_field, "widget")
+        
+        method_field = fields.MethodField(field.id,
+                                          title=field.get_value("title"),
+                                          description="",
+                                          default="",
+                                          required=0)
+        override_form.add_field(method_field, "widget")
+        
+    for field in getPropertyFields(field_class.validator): 
+        form.add_field(field, "validator")
+        tales_field = fields.TALESField(field.id,
+                                        title=field.get_value('title'),
+                                        description="",
+                                        default="",
+                                        display_with=40,
+                                        required=0)
+        tales_form.add_field(tales_field, "validator")
+        
+        method_field = fields.MethodField(field.id,
+                                          title=field.get_value("title"),
+                                          description="",
+                                          default="",
+                                          required=0)
+        override_form.add_field(method_field, "validator")
+        
+    field_class.form = form         
+    field_class.override_form = override_form
+    field_class.tales_form = tales_form
+    
+def getPropertyFields(obj):
+    """Get property fields from a particular widget/validator.
+    """
+    fields = []
+    for property_name in obj.property_names:
+        fields.append(getattr(obj, property_name))
+    return fields
+
+def setupIcon(klass, icon, repository):
+    """Load icon into Zope image object and put it in Zope's
+    repository for use by the ZMI, for a particular class.
+    klass -- the class of the field we're adding
+    icon  -- the icon
+    """
+    # set up misc_ respository if not existing yet
+    if not hasattr(OFS.misc_.misc_, repository):
+        setattr(OFS.misc_.misc_, 
+                repository, 
+                OFS.misc_.Misc_(repository, {}))
+        
+    # get name of icon in the misc_ directory
+    icon_name = os.path.split(icon)[1]
+        
+    # set up image object from icon file
+    icon_image = ImageFile(icon, globals())
+    icon_image.__roles__ = None
+
+    # put icon image object in misc_/Formulator/
+    getattr(OFS.misc_.misc_, repository)[icon_name] = icon_image
+
+    # set icon attribute in field_class to point to this image obj
+    setattr(klass, 'icon', 'misc_/%s/%s' %
+            (repository, icon_name))     
+
diff --git a/product/Formulator/Form.py b/product/Formulator/Form.py
new file mode 100644
index 0000000000000000000000000000000000000000..e50a3f590a4749b49b7f1c66eeb1c4cb66613e67
--- /dev/null
+++ b/product/Formulator/Form.py
@@ -0,0 +1,1046 @@
+import Globals, AccessControl
+import OFS
+from Acquisition import aq_base
+from Globals import DTMLFile, Persistent
+from AccessControl import ClassSecurityInfo
+from AccessControl.Role import RoleManager
+from OFS.ObjectManager import ObjectManager
+from OFS.PropertyManager import PropertyManager
+from OFS.SimpleItem import Item
+import Acquisition
+from urllib import quote
+import os
+import string
+from StringIO import StringIO
+
+from Errors import ValidationError, FormValidationError, FieldDisabledError
+from FieldRegistry import FieldRegistry
+from Widget import render_tag
+from DummyField import fields
+from FormToXML import formToXML
+from XMLToForm import XMLToForm
+
+from ComputedAttribute import ComputedAttribute
+
+# FIXME: manage_renameObject hack needs these imports
+from Acquisition import aq_base
+from App.Dialogs import MessageDialog
+from OFS.CopySupport import CopyError, eNotSupported
+import sys
+
+class Form:
+    """Form base class.
+    """
+    security = ClassSecurityInfo()
+
+    # need to make settings form upgrade
+    encoding = 'UTF-8'
+    stored_encoding = 'ISO-8859-1'
+    unicode_mode = 0
+    
+    # CONSTRUCTORS    
+    def __init__(self, action, method, enctype, name,
+                 encoding, stored_encoding, unicode_mode):
+        """Initialize form.
+        """
+        # make groups dict with entry for default group
+        self.groups = {"Default": []}
+        # the display order of the groups
+        self.group_list = ["Default"]
+        # form submit info
+        self.name = name     # for use by javascript
+        self.action = action
+        self.method = method
+        self.enctype = enctype
+        self.encoding = encoding
+        self.stored_encoding = stored_encoding
+        self.unicode_mode = unicode_mode
+        
+    # MANIPULATORS
+    security.declareProtected('Change Formulator Forms', 'field_added')
+    def field_added(self, field_id, group=None):
+        """A field was added to the form.
+        """
+        # get indicated group or the first group if none was indicated
+        group = group or self.group_list[0]
+        # add it to the indicated group (create group if nonexistent)
+        groups = self.groups
+        field_list = groups.get(group, [])
+        field_list.append(field_id)
+        groups[group] = field_list
+        if group not in self.group_list:
+            self.group_list.append(group)
+            self.group_list = self.group_list
+        self.groups = groups
+        
+    security.declareProtected('Change Formulator Forms', 'field_removed')
+    def field_removed(self, field_id):
+        """A field was removed from the form.
+        """
+        for field_list in self.groups.values():
+            if field_id in field_list:
+                field_list.remove(field_id)
+                break # should be done as soon as we found it once
+        self.groups = self.groups
+
+    security.declareProtected('Change Formulator Forms', 'move_field_up')
+    def move_field_up(self, field_id, group):
+        groups = self.groups
+        field_list = groups[group]
+        i = field_list.index(field_id)
+        if i == 0:
+            return 0 # can't move further up, so we're done
+        # swap fields, moving i up
+        field_list[i], field_list[i - 1] = field_list[i - 1], field_list[i]
+        self.groups = groups
+        return 1
+    
+    security.declareProtected('Change Formulator Forms', 'move_field_down')
+    def move_field_down(self, field_id, group):
+        groups = self.groups
+        field_list = groups[group]
+        i = field_list.index(field_id)
+        if i == len(field_list) - 1:
+            return 0 # can't move further down, so we're done
+        # swap fields, moving i down
+        field_list[i], field_list[i + 1] = field_list[i + 1], field_list[i]
+        self.groups = groups
+        return 1
+
+    security.declareProtected('Change Formulator Forms', 'move_field_group')
+    def move_field_group(self, field_ids, from_group, to_group):
+        """Moves a fields from one group to the other.
+        """
+        if len(field_ids) == 0:
+            return 0
+        if from_group == to_group:
+            return 0
+        groups = self.groups
+        from_list = groups[from_group]
+        to_list = groups[to_group]
+        for field in self.get_fields_in_group(from_group, include_disabled=1)[:]:
+            if field.id in field_ids:
+                from_list.remove(field.id)
+                to_list.append(field.id)
+        self.groups = groups
+        return 1
+    
+    security.declareProtected('Change Formulator Forms', 'add_group')
+    def add_group(self, group):
+        """Add a new group.
+        """
+        groups = self.groups
+        if groups.has_key(group):
+            return 0 # group already exists (NOTE: should we raise instead?)
+        groups[group] = []
+        # add the group to the bottom of the list of groups
+        self.group_list.append(group)
+        
+        self.group_list = self.group_list
+        self.groups = groups
+        return 1
+    
+    security.declareProtected('Change Formulator Forms', 'remove_group')
+    def remove_group(self, group):
+        """Remove a group.
+        """
+        groups = self.groups
+        if group == self.group_list[0]:
+            return 0 # can't remove first group
+        if not groups.has_key(group):
+            return 0 # group does not exist (NOTE: should we raise instead?)
+        # move whatever is in the group now to the end of the first group
+        groups[self.group_list[0]].extend(groups[group])
+        # now remove the key
+        del groups[group]
+        # remove it from the group order list as well
+        self.group_list.remove(group)
+        
+        self.group_list = self.group_list
+        self.groups = groups
+        return 1
+    
+    security.declareProtected('Change Formulator Forms', 'rename_group')
+    def rename_group(self, group, name):
+        """Rename a group.
+        """
+        group_list = self.group_list
+        groups = self.groups
+        if not groups.has_key(group):
+            return 0 # can't rename unexisting group
+        if groups.has_key(name):
+            return 0 # can't rename into existing name
+        i = group_list.index(group)
+        group_list[i] = name
+        groups[name] = groups[group]
+        del groups[group]
+        self.group_list = group_list
+        self.groups = groups
+        return 1
+
+    security.declareProtected('Change Formulator Forms', 'move_group_up')
+    def move_group_up(self, group):
+        """Move a group up in the group list.
+        """
+        group_list = self.group_list
+        i = group_list.index(group)
+        if i == 1:
+            return 0 # can't move further up, so we're done
+        # swap groups, moving i up
+        group_list[i], group_list[i - 1] = group_list[i - 1], group_list[i]
+        self.group_list = group_list
+        return 1
+    
+    security.declareProtected('Change Formulator Forms', 'move_group_down')  
+    def move_group_down(self, group):
+        """Move a group down in the group list.
+        """
+        group_list = self.group_list
+        i = group_list.index(group)
+        if i  == len(group_list) - 1:
+            return 0 # can't move further up, so we're done
+        # swap groups, moving i down
+        group_list[i], group_list[i + 1] = group_list[i + 1], group_list[i]
+        self.group_list = group_list
+        return 1
+    
+    # ACCESSORS
+    security.declareProtected('View', 'get_fields')
+    def get_fields(self, include_disabled=0):
+        """Get all fields for all groups (in the display order).
+        """
+        result = []
+        for group in self.get_groups(include_empty=1):
+            result.extend(self.get_fields_in_group(group, include_disabled))
+        return result
+
+    security.declareProtected('View', 'get_field_ids')
+    def get_field_ids(self, include_disabled=0):
+        """Get all the ids of the fields in the form.
+        """
+        result = []
+        for field in self.get_fields(include_disabled):
+            result.append(field.id)
+        return result
+    
+    security.declareProtected('View', 'get_fields_in_group')
+    def get_fields_in_group(self, group, include_disabled=0):
+        """Get all fields in a group (in the display order).
+        """
+        result = []
+        for field_id in self.groups.get(group, []):
+            try:
+                field = self.get_field(field_id, include_disabled)
+            except FieldDisabledError:
+                pass
+            else:
+                result.append(field)
+        return result
+
+    security.declareProtected('View', 'has_field')
+    def has_field(self, id, include_disabled):
+        """Check whether the form has a field of a certain id.
+        """
+        # define in subclass
+        pass
+    
+    security.declareProtected('View', 'get_field')
+    def get_field(self, id):
+        """Get a field of a certain id.
+        """
+        # define in subclass
+        pass
+    
+    security.declareProtected('View', 'get_groups')
+    def get_groups(self, include_empty=0):
+        """Get a list of all groups, in display order.
+
+        If include_empty is false, suppress groups that do not have
+        enabled fields.
+        """
+        if include_empty:
+            return self.group_list
+        return [group for group in self.group_list
+                if self.get_fields_in_group(group)]
+ 
+    security.declareProtected('View', 'get_form_encoding')
+    def get_form_encoding(self):
+        """Get the encoding the form is in. Should be the same as the
+        encoding of the page, if specified, for unicode to work. Default
+        is 'UTF-8'.
+        """
+        return getattr(self, 'encoding', 'UTF-8')
+    
+    security.declareProtected('View', 'get_stored_encoding')
+    def get_stored_encoding(self):
+        """Get the encoding of the stored field properties.
+        """
+        return getattr(self, 'stored_encoding', 'ISO-8859-1')
+    
+    security.declareProtected('View', 'get_unicode_mode')
+    def get_unicode_mode(self):
+        """Get unicode mode information.
+        """
+        return getattr(self, 'unicode_mode', 0)
+    
+    security.declareProtected('View', 'render')
+    def render(self, dict=None, REQUEST=None):
+        """Render form in a default way.
+        """
+        dict = dict or {}
+        result = StringIO()
+        w = result.write
+        w(self.header())
+        for group in self.get_groups():
+            w('<h2>%s</h2>\n' % group)
+            w('<table border="0" cellspacing="0" cellpadding="2">\n')
+            for field in self.get_fields_in_group(group):
+                if dict.has_key(field.id):
+                    value = dict[field.id]
+                else:
+                    value = None
+                w('<tr>\n')
+                if not field.get_value('hidden'):
+                    w('<td>%s</td>\n' % field.get_value('title'))
+                else:
+                    w('<td></td>')
+                w('<td>%s</td>\n' % field.render(value, REQUEST))
+                w('</tr>\n')
+            w('</table>\n')
+        w('<input type="submit" value=" OK ">\n')
+        w(self.footer())
+        return result.getvalue()
+
+    security.declareProtected('View', 'render_view')
+    def render_view(self, dict=None):
+        """Render contents (default simplistic way).
+        """
+        dict = dict or {}
+        result = StringIO()
+        w = result.write
+        for group in self.get_groups():
+            w('<h2>%s</h2>\n' % group)
+            w('<table border="0" cellspacing="0" cellpadding="2">\n')
+            for field in self.get_fields_in_group(group):
+                if dict.has_key(field.id):
+                    value = dict[field.id]
+                else:
+                    value = None
+                w('<tr>\n')
+                w('<td>%s</td>\n' % field.get_value('title'))
+                w('<td>%s</td>\n' % field.render_view(value))
+                w('</tr>\n')
+            w('</table>\n')
+        return result.getvalue()
+    
+    security.declareProtected('View', 'validate')
+    def validate(self, REQUEST):
+        """Validate all enabled fields in this form. Stop validating and
+        pass up ValidationError if any occurs.
+        """
+        result = {}
+        for field in self.get_fields():
+            # skip any fields we don't need to validate
+            if not field.need_validate(REQUEST):
+                continue
+            value = field.validate(REQUEST)
+            # store under id
+            result[field.id] = value
+            # store as alternate name as well if necessary
+            alternate_name = field.get_value('alternate_name')
+            if alternate_name:
+                result[alternate_name] = value   
+        return result
+
+    security.declareProtected('View', 'validate_to_request')
+    def validate_to_request(self, REQUEST):
+        """Validation, stop validating as soon as error.
+        """
+        result = self.validate(REQUEST)
+        for key, value in result.items():
+            REQUEST.set(key, value)
+        return result
+    
+    security.declareProtected('View', 'validate_all')
+    def validate_all(self, REQUEST):
+        """Validate all enabled fields in this form, catch any ValidationErrors
+        if they occur and raise a FormValidationError in the end if any
+        Validation Errors occured.
+        """
+        result = {}
+        errors = []
+        for field in self.get_fields():
+            # skip any field we don't need to validate
+            if not field.need_validate(REQUEST):
+                continue
+            try:
+                value = field.validate(REQUEST)
+                # store under id
+                result[field.id] = value
+                # store as alternate name as well if necessary
+                alternate_name = field.get_value('alternate_name')
+                if alternate_name:
+                    result[alternate_name] = value
+            except ValidationError, err:
+                errors.append(err)
+        if len(errors) > 0:
+            raise FormValidationError(errors, result) 
+        return result
+
+    security.declareProtected('View', 'validate_all_to_request')
+    def validate_all_to_request(self, REQUEST):
+        """Validation, continue validating all fields, catch errors.
+        Everything that could be validated will be added to REQUEST.
+        """
+        try:
+            result = self.validate_all(REQUEST)
+        except FormValidationError, e:
+            # put whatever result we have in REQUEST
+            for key, value in e.result.items():
+                REQUEST.set(key, value)
+            # reraise exception
+            raise
+        for key, value in result.items():
+            REQUEST.set(key, value)
+        return result
+
+    security.declareProtected('View', 'session_store')
+    def session_store(self, session, REQUEST):
+        """Store form data in REQUEST into session.
+        """
+        data = session.getSessionData()
+        for field in self.get_fields():
+            id = field.id
+            data.set(id, REQUEST[id])
+
+    security.declareProtected('View', 'session_retrieve')
+    def session_retrieve(self, session, REQUEST):
+        """Retrieve form data from session into REQUEST.
+        """
+        data = session.getSessionData()
+        for field in self.get_fields():
+            id = field.id
+            REQUEST.set(id, data.get(id))
+
+    security.declareProtected('View', 'header')
+    def header(self):
+        """Starting form tag.
+        """
+        # FIXME: backwards compatibility; name attr may not be present
+        if not hasattr(self, "name"):
+            self.name = ""
+        name = self.name
+
+        if self.enctype is not "":
+            if name:
+                return render_tag("form",
+                                  name=name,
+                                  action=self.action,
+                                  method=self.method,
+                                  enctype=self.enctype) + ">"
+            else:
+                return render_tag("form",
+                                  action=self.action,
+                                  method=self.method,
+                                  enctype=self.enctype) + ">"
+        else:
+            if name:
+                return render_tag("form",
+                                  name=name,
+                                  action=self.action,
+                                  method=self.method) + ">"
+            else:
+                return render_tag("form",
+                                  action=self.action,
+                                  method=self.method) + ">"
+
+    security.declareProtected('View', 'footer')
+    def footer(self):
+        """Closing form tag.
+        """
+        return "</form>"
+
+    security.declareProtected('Change Formulator Forms', 'get_xml')
+    def get_xml(self):
+        """Get this form in XML serialization.
+        """
+        return formToXML(self)
+
+    security.declareProtected('Change Formulator Forms', 'set_xml')
+    def set_xml(self, xml, override_encoding=None):
+        """change form according to xml"""
+        XMLToForm(xml, self, override_encoding)
+
+    def _management_page_charset(self):
+        if not self.unicode_mode:
+            return self.stored_encoding
+        else:
+            return 'UTF-8'
+
+    security.declareProtected('Access contents information',
+                              'management_page_charset')
+    management_page_charset = ComputedAttribute(_management_page_charset)
+        
+    security.declareProtected('View', 'set_encoding_header')
+    def set_encoding_header(self):
+        """Set the encoding in the RESPONSE object.
+
+        This can be used to make sure a page is in the same encoding the
+        textual form contents is in.
+        """
+        if not self.unicode_mode:
+            encoding = self.stored_encoding
+        else:
+            encoding = 'UTF-8'
+        self.REQUEST.RESPONSE.setHeader(
+            'Content-Type',
+            'text/html;charset=%s' % encoding)
+    
+Globals.InitializeClass(Form)
+
+class BasicForm(Persistent, Acquisition.Implicit, Form):
+    """A form that manages its own fields, not using ObjectManager.
+    Can contain dummy fields defined by DummyField.
+    """
+    security = ClassSecurityInfo()
+       
+    def __init__(self, action="", method="POST", enctype="", name="",
+                 encoding="UTF-8", stored_encoding='ISO-8859-1',
+                 unicode_mode=0):
+        BasicForm.inheritedAttribute('__init__')(
+            self, action, method, enctype,
+            name, encoding, stored_encoding, unicode_mode)
+        self.title = 'Basic Form' # XXX to please FormToXML..
+        self.fields = {}
+
+    security.declareProtected('Change Formulator Forms', 'add_field')
+    def add_field(self, field, group=None):
+        """Add a field to the form to a certain group. 
+        """
+        # update group info
+        self.field_added(field.id, group)
+        # add field to list
+        self.fields[field.id] = field 
+        self.fields = self.fields
+
+    security.declareProtected('Change Formulator Forms', 'add_fields')
+    def add_fields(self, fields, group=None):
+        """Add a number of fields to the form at once (in a group).
+        """
+        for field in fields:
+            self.add_field(field, group)
+            
+    security.declareProtected('Change Formulator Forms', 'remove_field')
+    def remove_field(self, field):
+        """Remove field from form.
+        """
+        # update group info
+        self.field_removed(field.id)
+        # remove field from list
+        del self.fields[field.id]
+        self.fields = self.fields
+
+    security.declareProtected('View', 'has_field')
+    def has_field(self, id, include_disabled=0):
+        """Check whether the form has a field of a certain id.
+        If disabled fields are not included, pretend they're not there.
+        """
+        field = self.fields.get(id, None)
+        if field is None:
+            return 0
+        return include_disabled or field.get_value('enabled')
+    
+    security.declareProtected('View', 'get_field')
+    def get_field(self, id, include_disabled=0):
+        """get a field of a certain id."""
+        field = self.fields[id]
+        if include_disabled or field.get_value('enabled'):
+            return field
+        raise FieldDisabledError("Field %s is disabled" % id, field)
+    
+    def _realize_fields(self):
+        """Make the fields in this form actual fields, not just dummy fields.
+        """
+        for field in self.get_fields(include_disabled=1):
+            if hasattr(field, 'get_real_field'):
+                field = field.get_real_field()
+            self.fields[field.id] = field
+        self.fields = self.fields
+
+Globals.InitializeClass(BasicForm)
+
+def create_settings_form():
+    """Create settings form for ZMIForm.
+    """
+    form = BasicForm('manage_settings')
+
+    title = fields.StringField('title',
+                               title="Title",
+                               required=0,
+                               default="")
+    row_length = fields.IntegerField('row_length',
+                                     title='Number of groups in row (in order tab)',
+                                     required=1,
+                                     default=4)
+    name = fields.StringField('name',
+                              title="Form name",
+                              required=0,
+                              default="")
+    action = fields.StringField('action',
+                                title='Form action',
+                                required=0,
+                                default="")
+    method = fields.ListField('method',
+                              title='Form method',
+                              items=[('POST', 'POST'),
+                                     ('GET', 'GET')],
+                              required=1,
+                              size=1,
+                              default='POST')
+    enctype = fields.ListField('enctype',
+                               title='Form enctype',
+                               items=[('No enctype', ""),
+                                      ('application/x-www-form-urlencoded',
+                                       'application/x-www-form-urlencoded'),
+                                      ('multipart/form-data',
+                                       'multipart/form-data')],
+                               required=0,
+                               size=1,
+                               default=None) 
+
+    encoding = fields.StringField('encoding',
+                                  title='Encoding of pages the form is in',
+                                  default="UTF-8",
+                                  required=1)
+
+    stored_encoding = fields.StringField('stored_encoding',
+                                      title='Encoding of form properties',
+                                      default='ISO-8859-1',
+                                      required=1)
+    unicode_mode = fields.CheckBoxField('unicode_mode',
+                                        title='Form properties are unicode',
+                                        default=0,
+                                        required=1)
+    
+    form.add_fields([title, row_length, name, action, method,
+                     enctype, encoding, stored_encoding, unicode_mode])
+    return form
+
+class ZMIForm(ObjectManager, PropertyManager, RoleManager, Item, Form):
+    """
+    A Formulator Form, fields are managed by ObjectManager.
+    """
+    meta_type = "Formulator Form"
+
+    security = ClassSecurityInfo()
+
+    # should be helpful with ZClasses, but not sure why I
+    # had it in here as a comment in the first place..
+    security.declareObjectProtected('View')
+    
+    # the tabs we want to show
+    manage_options = (
+        (
+        {'label':'Contents', 'action':'manage_main',
+         'help':('Formulator', 'formContents.txt')},
+        {'label':'Test', 'action':'formTest',
+         'help':('Formulator', 'formTest.txt')},
+        {'label':'Order', 'action':'formOrder',
+         'help':('Formulator', 'formOrder.txt')},
+        {'label':'Settings', 'action':'formSettings',
+         'help':('Formulator', 'formSettings.txt')},
+        {'label':'XML', 'action':'formXML',
+         'help':('Formulator', 'formXML.txt')},
+        ) +
+        PropertyManager.manage_options +
+        RoleManager.manage_options +
+        Item.manage_options
+        )
+
+    def __init__(self, id, title, unicode_mode=0):
+        """Initialize form.
+        id    -- id of form
+        title -- the title of the form
+        """
+        ZMIForm.inheritedAttribute('__init__')(self, "", "POST", "", id,
+                                               'UTF-8', 'ISO-8859-1',
+                                               unicode_mode)
+        self.id = id
+        self.title = title
+        self.row_length = 4
+        
+    def all_meta_types(self):
+        """Get all meta types addable to this field. The ZMI uses
+        this method (original defined in ObjectManager).
+        """
+        return self._meta_types
+
+    def manage_renameObject(self, id, new_id, REQUEST=None):
+        """Rename a particular sub-object, the *old* way.
+        FIXME: hack that could be removed once Zope 2.4.x
+        goes back to a useful semantics..."""
+        try: self._checkId(new_id)
+        except: raise CopyError, MessageDialog(
+                      title='Invalid Id',
+                      message=sys.exc_info()[1],
+                      action ='manage_main')
+        ob=self._getOb(id)
+        if not ob.cb_isMoveable():
+            raise CopyError, eNotSupported % id            
+        self._verifyObjectPaste(ob)
+        try:    ob._notifyOfCopyTo(self, op=1)
+        except: raise CopyError, MessageDialog(
+                      title='Rename Error',
+                      message=sys.exc_info()[1],
+                      action ='manage_main')
+        self._delObject(id)
+        ob = aq_base(ob)
+        ob._setId(new_id)
+        
+        # Note - because a rename always keeps the same context, we
+        # can just leave the ownership info unchanged.
+        self._setObject(new_id, ob, set_owner=0)
+
+        if REQUEST is not None:
+            return self.manage_main(self, REQUEST, update_menu=1)
+        return None
+
+    #security.declareProtected('View', 'get_fields_raw')
+    #def get_fields_raw(self):
+    #    """Get all fields, in arbitrary order.
+    #    """
+    #    return filter(lambda obj: hasattr(obj.aq_explicit, 'is_field'),
+    #                  self.objectValues())
+
+    security.declareProtected('View', 'has_field')
+    def has_field(self, id, include_disabled=0):
+        """Check whether the form has a field of a certain id.
+        """
+        field = self._getOb(id, None)
+        if field is None or not hasattr(aq_base(field), 'is_field'):
+            return 0
+        return include_disabled or field.get_value('enabled')
+    
+    security.declareProtected('View', 'get_field')
+    def get_field(self, id, include_disabled=0):
+        """Get a field of a certain id
+        """
+        field = self._getOb(id, None)
+        if field is None or not hasattr(aq_base(field), 'is_field'):
+            raise AttributeError, "No field %s" % id
+        if include_disabled or field.get_value('enabled'):
+            return field
+        raise FieldDisabledError("Field %s disabled" % id, field)
+
+    security.declareProtected('Change Formulator Forms', 'manage_addField')
+    def manage_addField(self, id, title, fieldname, REQUEST=None):
+        """Add a new field to the form.
+        id        -- the id of the field to add
+        title     -- the title of the field to add; this will be used in
+                     displays of the field on forms
+        fieldname -- the name of the field (meta_type) to add
+        Result    -- empty string
+        """
+        title = string.strip(title)
+        if not title:
+            title = id # title is always required, use id if not provided
+        # get the field class we want to add
+        field_class = FieldRegistry.get_field_class(fieldname)
+        # create field instance
+        field = field_class(id, title=title, description="")
+        # add the field to the form
+        id = self._setObject(id, field)
+        # respond to add_and_edit button if necessary
+        add_and_edit(self, id, REQUEST)
+        return ''
+
+    security.declareProtected('View management screens', 'formTest')
+    formTest = DTMLFile('dtml/formTest', globals())
+
+    settings_form = create_settings_form()
+
+    security.declareProtected('View management screens', 'formSettings')
+    formSettings = DTMLFile('dtml/formSettings', globals())
+
+    security.declareProtected('View management screens', 'formOrder')
+    formOrder = DTMLFile('dtml/formOrder', globals())
+
+    security.declareProtected('View management screens', 'formXML')
+    formXML = DTMLFile('dtml/formXML', globals())
+
+    security.declareProtected('Change Formulator Forms', 'manage_editXML')
+    def manage_editXML(self, form_data, REQUEST):
+        """Change form using XML.
+        """
+        self.set_xml(form_data)
+        return self.formXML(self, REQUEST,
+                            manage_tabs_message="Changed form")
+        
+    security.declareProtected('Change Formulator Forms', 'manage_settings')
+    def manage_settings(self, REQUEST):
+        """Change settings in settings screen.
+        """
+        try:
+            result = self.settings_form.validate_all(REQUEST)
+        except FormValidationError, e:
+            message = "Validation error(s).<br />" + string.join(
+                map(lambda error: "%s: %s" % (error.field.get_value('title'),
+                                              error.error_text), e.errors), "<br />")
+            return self.formSettings(self, REQUEST,
+                                     manage_tabs_message=message)
+        # if we need to switch encoding, get xml representation before setting
+        if result['unicode_mode'] != self.unicode_mode:
+            xml = self.get_xml()
+        # now set the form settings
+        
+        # convert XML to or from unicode mode if necessary
+        unicode_message = None
+        if result['unicode_mode'] != self.unicode_mode:
+            # get XML (using current stored_encoding)
+            xml = self.get_xml()
+
+            # now save XML data again using specified encoding
+            if result['unicode_mode']:
+                encoding = 'unicode'
+                unicode_message = "Converted to unicode."
+            else:
+                encoding = result['stored_encoding']
+                unicode_message = ("Converted from unicode to %s encoding" %
+                                   encoding)
+            self.set_xml(xml, encoding)
+            
+        # now set the form settings
+        for key, value in result.items():
+            setattr(self, key, value)
+        message="Settings changed."
+        if unicode_message is not None:
+            message = message + ' ' + unicode_message
+        return self.formSettings(self, REQUEST,
+                                 manage_tabs_message=message)
+    
+    security.declareProtected('Change Formulator Forms', 'manage_refresh')
+    def manage_refresh(self, REQUEST):
+        """Refresh internal data structures of this form.
+        FIXME: this doesn't work right now
+        """
+        # self.update_groups()
+        REQUEST.RESPONSE.redirect('manage_main')
+
+    security.declarePrivate('_get_field_ids')
+    def _get_field_ids(self, group, REQUEST):
+        """Get the checked field_ids that we're operating on
+        """
+        field_ids = []
+        for field in self.get_fields_in_group(group, include_disabled=1):
+            if REQUEST.form.has_key(field.id):
+                field_ids.append(field.id)
+        return field_ids
+
+    security.declareProtected('View management screens',
+                              'get_group_rows')
+    def get_group_rows(self):
+        """Get the groups in rows (for the order screen).
+        """
+        row_length = self.row_length
+        groups = self.get_groups(include_empty=1)
+        # get the amount of rows
+        rows = len(groups) / row_length
+        # if we would have extra groups not in a row, add a row
+        if len(groups) % self.row_length != 0:
+            rows = rows + 1
+        # now create a list of group lists and return it
+        result = []
+        for i in range(rows):
+            start = i * row_length
+            result.append(groups[start: start + row_length])
+        return result
+
+    security.declareProtected('View', 'get_largest_group_length')
+    def get_largest_group_length(self):
+        """Get the largest group length available; necessary for
+        'order' screen user interface.
+        """
+        max = 0
+        for group in self.get_groups(include_empty=1):
+            fields = self.get_fields_in_group(group)
+            if len(fields) > max:
+                max = len(fields)
+        return max
+    
+    security.declareProtected('Change Formulator Forms',
+                              'manage_move_field_up')
+    def manage_move_field_up(self, group, REQUEST):
+        """Moves up a field in a group.
+        """
+        field_ids = self._get_field_ids(group, REQUEST)
+        if (len(field_ids) == 1 and
+            self.move_field_up(field_ids[0], group)):
+            message = "Field %s moved up." % field_ids[0]
+        else:
+            message = "Can't move field up."
+        return self.formOrder(self, REQUEST,
+                              manage_tabs_message=message)
+    
+    security.declareProtected('Change Formulator Forms',
+                              'manage_move_field_down')
+    def manage_move_field_down(self, group, REQUEST):
+        """Moves down a field in a group.
+        """
+        field_ids = self._get_field_ids(group, REQUEST)
+        if (len(field_ids) == 1 and
+            self.move_field_down(field_ids[0], group)):
+            message = "Field %s moved down." % field_ids[0]
+        else:
+            message = "Can't move field down."
+        return self.formOrder(self, REQUEST,
+                              manage_tabs_message=message)
+
+    security.declareProtected('Change Formulator Forms',
+                              'manage_move_group')
+    def manage_move_group(self, group, to_group, REQUEST):
+        """Moves fields to a different group.
+        """
+        field_ids = self._get_field_ids(group, REQUEST)
+        if (to_group != 'Move to:' and
+            self.move_field_group(field_ids, group, to_group)):
+            fields = string.join(field_ids, ", ")
+            message = "Fields %s transferred from %s to %s." % (fields,
+                                                                group,
+                                                                to_group)
+        else:
+            message = "Can't transfer fields."
+        return self.formOrder(self, REQUEST,
+                              manage_tabs_message=message)
+        
+    security.declareProtected('Change Formulator Forms',
+                              'manage_add_group')
+    def manage_add_group(self, new_group, REQUEST):
+        """Adds a new group.
+        """
+        group = string.strip(new_group)
+        if (group and group != 'Select group' and
+            self.add_group(group)):
+            message = "Group %s created." % (group)
+        else:
+            message = "Can't create group."
+        return self.formOrder(self, REQUEST,
+                              manage_tabs_message=message)
+
+    security.declareProtected('Change Formulator Forms',
+                              'manage_remove_group')
+    def manage_remove_group(self, group, REQUEST):
+        """Removes group.
+        """
+        if self.remove_group(group):
+            message = "Group %s removed." % (group)
+        else:
+            message = "Can't remove group."
+        return self.formOrder(self, REQUEST,
+                              manage_tabs_message=message)
+
+    security.declareProtected('Change Formulator Forms',
+                              'manage_rename_group')
+    def manage_rename_group(self, group, REQUEST):
+        """Renames group.
+        """
+        if REQUEST.has_key('new_name'):
+            new_name = string.strip(REQUEST['new_name'])
+            if self.rename_group(group, new_name):
+                message = "Group %s renamed to %s." % (group, new_name)
+            else:
+                message = "Can't rename group."
+        else:
+            message = "No new name supplied."
+
+        return self.formOrder(self, REQUEST,
+                              manage_tabs_message=message)
+    
+    security.declareProtected('Change Formulator Forms',
+                              'manage_move_group_up')
+    def manage_move_group_up(self, group, REQUEST):
+        """Move a group up.
+        """
+        if self.move_group_up(group):
+            message = "Group %s moved up." % group
+        else:
+            message = "Can't move group %s up" % group
+        return self.formOrder(self, REQUEST,
+                              manage_tabs_message=message)
+        
+    security.declareProtected('Change Formulator Forms',
+                              'manage_move_group_down')
+    def manage_move_group_down(self, group, REQUEST):
+        """Move a group down.
+        """
+        if self.move_group_down(group):
+            message = "Group %s moved down." % group
+        else:
+            message = "Can't move group %s down" % group
+        return self.formOrder(self, REQUEST,
+                              manage_tabs_message=message)
+
+PythonForm = ZMIForm # NOTE: backwards compatibility
+Globals.InitializeClass(ZMIForm)
+        
+manage_addForm = DTMLFile("dtml/formAdd", globals())
+
+def manage_add(self, id, title="", unicode_mode=0, REQUEST=None):
+    """Add form to folder.
+    id     -- the id of the new form to add
+    title  -- the title of the form to add
+    Result -- empty string
+    """
+    # add actual object
+    id = self._setObject(id, ZMIForm(id, title, unicode_mode))
+    # respond to the add_and_edit button if necessary
+    add_and_edit(self, id, REQUEST)
+    return ''
+
+def add_and_edit(self, id, REQUEST):
+    """Helper method to point to the object's management screen if
+    'Add and Edit' button is pressed.
+    id -- id of the object we just added
+    """
+    if REQUEST is None:
+        return
+    try:
+        u = self.DestinationURL()
+    except:
+        u = REQUEST['URL1']
+    if hasattr(REQUEST, 'submit_add_and_edit'):
+        u = "%s/%s" % (u, quote(id))
+    REQUEST.RESPONSE.redirect(u+'/manage_main')
+
+def initializeForm(field_registry):
+    """Sets up ZMIForm with fields from field_registry.
+    """
+    form_class = ZMIForm
+    
+    meta_types = []
+    for meta_type, field in field_registry.get_field_classes().items():
+        # don't set up in form if this is a field for internal use only
+        if field.internal_field:
+            continue
+        
+        # set up individual add dictionaries for meta_types
+        dict = { 'name': field.meta_type,
+                 'action':
+                 'manage_addProduct/Formulator/manage_add%sForm' % meta_type }
+        meta_types.append(dict)
+        # set up add method
+        setattr(form_class,
+                'manage_add%sForm' % meta_type,
+                DTMLFile('dtml/fieldAdd', globals(), fieldname=meta_type))
+        
+    # set up meta_types that can be added to form
+    form_class._meta_types = tuple(meta_types)
+
+    # set up settings form
+    form_class.settings_form._realize_fields()
+
+
+
+ 
+
+
+
diff --git a/product/Formulator/FormToXML.py b/product/Formulator/FormToXML.py
new file mode 100644
index 0000000000000000000000000000000000000000..cae794308240e616f88d88dbf3800e92ad1b451a
--- /dev/null
+++ b/product/Formulator/FormToXML.py
@@ -0,0 +1,85 @@
+from StringIO import StringIO
+from cgi import escape
+import types
+
+#def write(s):
+#    if type(s) == type(u''):
+#        print "Unicode:", repr(s)
+    
+def formToXML(form, prologue=1):
+    """Takes a formulator form and serializes it to an XML representation.
+    """
+    f = StringIO()
+    write = f.write
+
+    if prologue:
+        write('<?xml version="1.0"?>\n\n')
+    write('<form>\n')
+    # export form settings
+    for field in form.settings_form.get_fields(include_disabled=1):
+        id = field.id
+        value = getattr(form, id)
+        if id == 'title':
+            value = escape(value)
+        if id == 'unicode_mode':
+            if value:
+                value = 'true'
+            else:
+                value = 'false'
+        write('  <%s>%s</%s>\n' % (id, value, id))
+    # export form groups
+    write('  <groups>\n')
+    for group in form.get_groups(include_empty=1):
+        write('    <group>\n')
+        write('      <title>%s</title>\n' % escape(group))
+        write('      <fields>\n\n')
+        for field in form.get_fields_in_group(group, include_disabled=1):
+            write('      <field><id>%s</id> <type>%s</type>\n' % (field.id, field.meta_type))
+            write('        <values>\n')
+            items = field.values.items()
+            items.sort()
+            for key, value in items:
+                if value is None:
+                    continue
+                if value==True: # XXX Patch
+                    value = 1 # XXX Patch
+                if value==False: # XXX Patch
+                    value = 0 # XXX Patch
+                if callable(value): # XXX Patch
+                    write('          <%s type="method">%s</%s>\n' % # XXX Patch
+                        (key, escape(str(value.method_name)), key)) # XXX Patch
+                elif type(value) == type(1.1):
+                    write('          <%s type="float">%s</%s>\n' % (key, escape(str(value)), key))
+                elif type(value) == type(1):
+                    write('          <%s type="int">%s</%s>\n' % (key, escape(str(value)), key))
+                elif type(value) == type([]):
+                    write('          <%s type="list">%s</%s>\n' % (key, escape(str(value)), key))
+                else:
+                    if type(value) not in (types.StringType, types.UnicodeType):
+                        value = str(value)
+                    write('          <%s>%s</%s>\n' % (key, escape(value), key))
+            write('        </values>\n')
+
+            write('        <tales>\n')
+            items = field.tales.items()
+            items.sort()
+            for key, value in items:
+                if value:
+                    write('          <%s>%s</%s>\n' % (key, escape(str(value._text)), key))
+            write('        </tales>\n')
+
+            write('        <messages>\n')
+            for message_key in field.get_error_names():
+                write('          <message name="%s">%s</message>\n' %
+                      (escape(message_key), escape(field.get_error_message(message_key))))
+            write('        </messages>\n')
+            write('      </field>\n')
+        write('      </fields>\n')
+        write('    </group>\n')
+    write('  </groups>\n')
+    write('</form>')
+
+    if form.unicode_mode:
+        return f.getvalue().encode('UTF-8')
+    else:
+        return unicode(f.getvalue(), form.stored_encoding).encode('UTF-8')
diff --git a/product/Formulator/HISTORY.txt b/product/Formulator/HISTORY.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cd9803eb1612557c2cde23674e9c3d96fc2724c0
--- /dev/null
+++ b/product/Formulator/HISTORY.txt
@@ -0,0 +1,429 @@
+Formulator changes
+
+  1.6.1
+  
+    Bugs Fixed
+ 
+      - Adding Fields to empty Groups had not been possible
+
+      - ZMI "Order" tab of an empty form did raise an exception
+
+  1.6.0
+  
+    Features Added
+
+      - FileSystemSite/DirectoryView improvements:
+
+         * XML filesystem representation of Formulator forms can now
+           also be used with CMF (if FileSystemSite is not installed).
+
+         * FSForm gets automatically registered with the directory
+           view system if CMF or FileSystemSite is installed.
+
+      - Infrastructure for Validators not to get taken into account in
+        validation procedures (need_validate).
+      
+      - A new label field. Doesn't participate in validation. It shows
+        its text as a label in the form.
+
+      - Unicode mode. A form can now be put in 'unicode mode', which
+        means it stores all its textual data as unicode strings. This
+        allows for easier integration with Zope systems that use unicode
+        internally, such as Silva.
+ 
+      - Disabling of fields. A field can now be disabled from being
+        displayed or validated by unchecking the 'Enabled' validator
+        property. This can be done dynamically as well using TALES
+        overrides.
+
+    Bugs Fixed
+
+      - The css_class value of a DateTime field had been ignored.  It
+        is now properly passed down to its subfields, so all subfield
+        elements are rendered with the given css_class value.
+
+  1.5.0
+
+    Features Added
+
+      - Added ProductForm, which provides a wrapping around
+        Formulator.BasicForm, allowing it to be created inside a
+        product but used outside it.
+
+      - Allow turning off of XML prologue section.
+
+      - Optimization of TALESMethod by caching compiled expression.
+        This speeds SilvaMetadata indexing up by a lot if a fallback
+        on default is made, especially in the case of Python
+        expressions, as it avoids lots of compilation overhead.
+
+      - Extra attribute defined for list/multicheckbox/radio fields
+        called 'extra_item', which allows setting HTML attributes to
+        individual list item/checkbox/radio button.
+
+    Bugs Fixed
+
+      - XML serialization should be more consistent now; field properties
+        are now ordered by name upon serialization.
+ 
+      - Allow XML export of BasicForm.
+
+  1.4.2
+  
+    Bugs Fixed
+
+      - Sticky forms should now work correctly in the presence of unicode.
+        Encoded data is automatically converted to unicode if the information
+        is pulled from the REQUEST form.
+
+  1.4.1
+
+    Bugs Fixed
+
+      - It was not possible to make DateTime fields not required when
+        'allow_empty_time' was enabled. Fixed.
+
+  1.4.0
+
+    Features Added
+
+      - Added limited ability to output unicode for selected
+        fields. Only works properly in Zope 2.6.x, and the HTML pages
+        these forms are in need an output encoding set (such as
+        UTF-8, which is also Formulator's default encoding). If
+        'unicode' checkbox is checked Formulator will try to interpret
+        its input in the Form's encoding (default is UTF-8). It will
+        also try to display its values in that encoding. Note that
+        only field values and items currently work with unicode -- the
+        rest of the textual properties of a field are still stored as
+        8-bits. If you make sure that these properties are encoded as
+        UTF-8 (or whatever encoding you choose for the form) things
+        should be okay, however.
+
+      - Can now also change forms using XML (not just view it).
+
+      - DateTime fields can now optionally input AM/PM.
+
+      - DateTime fields can now optionally be set to allow time to 
+        be left empty.
+
+      - 'whitespace_preserve' option on string type fields. If turned on,
+        whitespace will not be automatically stripped and will count as
+        input.
+
+      - 'render_view' method on fields to render the value outside a
+        widget.
+
+      - Added some code support used by SilvaMetadata to enable rendering
+        of fields with Zope's ':record' syntax.
+
+    Bugs Fixed
+   
+      - Fixed a Python2.2 compatibility bug in XMLObjects.py
+
+      - DateTimeField now picks up default values from REQUEST
+        properly if necessary.
+
+      - XML representation of the LinkField "check_timeout" value
+        messed the type="float" attribute.
+
+      - Additional unit tests.
+
+  1.3.1 (2002/12/20)
+
+    Features Added
+
+      - Error messages can now be included in the XML serialization.
+
+      - Ability to encode lists as a special type in values.
+
+    Bugs Fixed
+
+      - Some more proper encodings.
+
+      - Handle case where group has no field.
+
+      - Handle DateTime field better.
+
+  1.3.0 (2002/11/26)
+ 
+    Features Added
+
+      - FormToXML and XMLToForm modules have functions to serialize
+        (most of) form to XML and read it in again (over an existing
+        form).
+
+      - New XML tab for forms which shows the XML serialization (no
+        saving option yet).
+
+      - FSForm.py uses XML serialization to provide a formulator form
+        version for FileSystemSite. It does not get imported by
+        default.
+
+    Bugs Fixed
+
+      - The email validator has an improved regular expression.
+
+      - Fix error that occured when trying to render DateTimeField as
+        hidden.
+
+  1.2.0 (2002/03/06)
+
+    Features Added
+
+      - Changes to exception infrastructure so errors can now be
+        imported and caught in a through the web Python script. Example::
+
+          from Products.Formulator.Errors import ValidationError, FormValidationError
+
+      - added __getitem__ to Field so instead of using get_value() you can
+        also do this in Python: form.field['title'], and in ZPT you can
+        use this in path expressions: form/field/title
+
+      - made a start with Formulator unit tests; some validators get
+        automatically tested now.
+
+    Bugs Fixed
+
+      - Removed dependencies of the name of 'Add and Edit' button to make
+        internationalization of the management interface easier.
+
+      - added permission to make ZClasses work a bit better (but they
+        still don't cooperate well with Formulator, I think. I don't use
+        ZClasses, so I hope to hear from this from ZClass users)
+ 
+      - Form's properties tab now visible and form tabs stopped
+        misbehaving.
+
+      - Lists and such should handle multiple items with the same value 
+        a bit better, selecting only one.
+
+      - the LinkField now checks site-internal links better.
+
+  1.1.0 (2001/10/26)
+   
+    Bugs Fixed
+
+       - Fixed bug in form settings tab.
+
+      - the LinkField now checks site-internal links better.
+
+  1.0.9 (2001/10/05)
+
+    Features Added
+
+       - New TALES tab for fields as a more powerful Override tab; 
+         PageTemplates needs to be installed to make it work.
+ 
+       - added 'name' attribute for forms. When the form header is
+         rendered, name will be an attribute. This can be used to
+         control forms with Javascript.
+
+    Bugs Fixed
+
+       - More compliance with Zope product guidelines; moved dtml
+	 files from www dir to dtml dir.
+
+       - Fixed a bug in that form titles would not work. Forms now have
+         titles, and you can change them in the settings tab. (Formulator
+         does not use the title property internally though)
+
+  1.0.1 (2001/07/27)
+
+    Bugs Fixed
+
+       - Fixed bug with renaming groups. Previously, renamed groups were not
+         properly stored in the ZODB.
+
+       - Made MultiSelectionValidator (used by MultiListField among others)
+         deal better with integer values.
+
+       - Hacked around CopySupport changes in Zope 2.4.0; renames work
+         again now.
+            
+  1.0 (2001/07/10)
+
+    Features Added
+
+       - New field: RawTextAreaField. A textarea field that doesn't 
+         do a lot of processing on the text input.
+
+       - Checked in BSD license text.
+
+    Bugs Fixed
+
+       - Fixed minor bug in year handling of DateTimeField.
+
+       - Now hidden fields also take text from 'extra' property.
+
+       - Fixed bug in MultiItemsWidget; would not deal with only a
+         single item being selected.
+
+  0.9.5 (2001/06/27)
+
+    Features Added
+ 
+      - Added FileField (with browse button). Can be used to upload
+        files if form is set to multipart/form-data.
+
+      - Added LinkField for URLs.
+
+      - Made ListField and RadioField more tolerant of integer
+        (and possibly other) values, not only strings.
+
+      - Made ListField and RadioField happy to deal with non-tuples too in the 
+        items list. In this case, the item text and value will be identical.
+
+      - Refactored ListWidget and RadioWidget so they share code; they both
+        inherit from SingleItemsWidget now.
+
+      - Added LinesField to submit a list of lines in a textarea.
+
+      - Added MultiListField and MultiCheckBoxField, both use new
+        MultiItemsWidget and MultiSelectionValidator.
+
+      - Added EXPERIMENTAL PatternField.
+
+  0.9.4 (2001/06/20)
+
+    Features Added
+
+      - Added API docs for Form, BasicForm and ZMIForm.
+ 
+      - Renamed the confusingly named PythonForm and PythonField to
+        ZMIForm and ZMIField, as they are used from the Zope Management
+        Interface and not from Python.
+
+      - Added render() method to form for basic form rendering.
+
+      - Added Formulator HOWTO document.
+
+    Bugs Fixed
+
+      - Removed some validation code that wasn't in use anymore (items_method).
+
+      - Removed 'has_field_id' in Form as this duplicated
+        the functionality of 'has_field'.
+
+      - Turned <br> in Python sources to <br /> for XHTML compliance.
+
+      - Tweaked radiobutton; text is now closer to the button itself,
+        different buttons are further apart.
+
+  0.9.3 (2001/06/12)
+
+    Features Added
+      
+      - added RadioField for simple display of radio buttons.
+
+      - added action, method and enctype property to form settings.
+        These are displayed using the special form.header() and form.footer()
+        methods.
+ 
+      - added override tab to allow all properties to be overridden by
+        method calls instead. 'items_method' in ListField went
+        away.
+
+      - added ability to display DateTimeFields using drop down lists
+        instead of text input. Added some other bells and whistles to
+        DateTimeField. Changed some of the inner workings of composite
+        fields; component fields are now unique per field instance
+        instead of shared between them.
+ 
+      - is_required() utility method on field to check whether a field
+        is required.
+
+      - some internal features, such the ability to have a method
+        called as soon as a property has changed.
+
+    Bugs Fixed
+
+      - Fixed typos in security assertions.
+
+      - use REQUEST.form instead of REQUEST where possible.
+
+      - display month and day with initial zero in DateTimeField.
+
+      - Fixed bug in validate_all_to_request(); what can be validated
+        will now be added to REQUEST if possible, even if a
+        FormValidationError is raised.
+
+  0.9.2 (2001/05/23)
+
+    Features Added
+
+      - Ability to rename groups, including the first 'Default' group.
+
+      - Improved support for sticky forms; form.render() can now
+        take an optional second argument, REQUEST, which can come
+        from a previous form submit. Even unvalidated fields will
+        then be sticky.
+
+      - fields can call an extra optional external validation 
+        function (such as a Python script).
+
+      - New alternate name property: the alternate name is added to
+        the result dictionary or REQUEST object after validation. This
+        can be useful to support field names which wouldn't be valid
+        field names, which can occur in some locales.
+
+      - New extra property; can be used to add extra attributes to
+        a HTML tag.
+
+      - Some IntegerField properties can now be left empty if 
+        no value is required, instead of having to set them to 0.
+
+      - Merged functionality of RangedIntegerField into IntegerField.
+        RangedIntegerField is not addable anymore, though supported
+        as a clone of IntegerField for backwards compatibility. Leaving
+        'start' and 'end' empty in the new IntegerField will mean those
+        checks will not be performed.
+
+    Bugs Fixed
+
+      - Added more missing security declarations.
+
+      - html_quote added in various places to make fields display
+        various HTML entities the right way.
+
+  0.9.1 (2001/05/13)
+ 
+    Features Added
+
+      - Widgets now have a 'hidden' property. If set, the widget is
+        drawn as a 'hidden' field. 'hidden' fields do get validated
+        normally, however.
+ 
+      - Changed API of Widget and Validator slightly; render() and
+        validate() methods now take an extra 'key' argument indicating
+        the name the field should have in the form. This is necessarily
+        to handle sub fields of composite fields.
+
+      - Added EmailField and FloatField.
+ 
+      - Added some infrastructure to support 'composite fields'; fields 
+        composed out of multiple sub fields.
+
+      - Added DateTimeField, the first example of a composite field
+        (field made of other fields).
+
+    Bugs Fixed  
+
+      - General code cleanups; removed some unused methods.
+
+      - Fixed security assertion for validate_all_to_request() method.
+
+      - MethodFields now check whether they have 'View' permission to
+        execute listed Python Script or DTML Method.
+
+      - RangedInteger is now < end, instead of <=, compatible with the
+        documentation.
+      
+  0.9 (2001/04/30)
+
+    Initial Release
+
+      - Initial public release of Formulator.
+
+
+
diff --git a/product/Formulator/HelperFields.py b/product/Formulator/HelperFields.py
new file mode 100644
index 0000000000000000000000000000000000000000..02d7d6b3edbec325f63abe3f8772e65d4a264394
--- /dev/null
+++ b/product/Formulator/HelperFields.py
@@ -0,0 +1,6 @@
+# include some helper fields which are in their own files
+from MethodField import MethodField
+from ListTextAreaField import ListTextAreaField
+from TALESField import TALESField
+
+
diff --git a/product/Formulator/INSTALL.txt b/product/Formulator/INSTALL.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b702911dd89b088f154699305cd896abe563e894
--- /dev/null
+++ b/product/Formulator/INSTALL.txt
@@ -0,0 +1,139 @@
+Installing Formulator
+
+  Requirements
+
+    Formulator should work with Zope versions 2.6 or higher:
+
+      http://www.zope.org/Products/Zope
+
+    For reading in forms as XML you need to have minidom installed;
+    this should come with a normal python 2.1 distribution. This is
+    not required to use Formulator, however.
+
+  Upgrading
+
+    to 1.6.0 from earlier versions
+
+      There should be no problems.
+
+    to 1.4.2 from earlier versions
+       
+      There should be no problems.
+
+    to 1.4.1 from earlier versions
+ 
+      There should be no problems.
+
+    to 1.4.0 from earlier versions
+
+      There should be no problems.
+
+    to 1.3.1 from earlier versions
+
+      There should be no problems (see note for 0.9.2 though in the
+      unusual case you're upgrading from that..this is the last time
+      I'll mention it :).
+
+    to 1.3.0 from earlier versions
+
+      There should be no problems, but see the note if you're
+      upgrading from 0.9.2 or below (but I'd be surprised if you
+      were!).
+
+    to 1.2.0 from earlier versions
+
+      There should be no problems, but see the note if you're upgrading
+      from version 0.9.2 or below.
+
+    to 1.1.0 from earlier versions
+
+      There should be no problems. If you're upgrading from 0.9.2 or 
+      below however, please see the upgrading note for 0.9.3. Do note
+      that the Override tab is scheduled to be phased out eventually in 
+      favor of the TALES tab. This will take a while yet, though.
+
+    to 1.0.9 from earlier versions
+
+      There should be no problems. If you're upgrading from 0.9.2 or 
+      below however, please see the upgrading note for 0.9.3. Do note
+      that the Override tab is scheduled to be phased out eventually in 
+      favor of the TALES tab. This will take a while yet, though.
+
+    to 1.0.1 from earlier versions
+
+      There should be no problems. If you're upgrading from 0.9.2 or
+      below, please see the upgrading note for 0.9.3.
+
+    to 1.0 from earlier versions
+
+      There should be no problems. If you're upgrading from 0.9.2 or
+      below, please see the upgrading note for 0.9.3.
+
+    to 0.9.5 from earlier versions
+ 
+      There should be no problems in upgrading from 0.9.4 or 0.9.3.
+      If you're upgrading from 0.9.2 or below, see the upgrading note
+      for 0.9.3.
+
+    to 0.9.4 from earlier versions
+
+      There should be no problems in upgrading from 0.9.3.
+
+      If you're upgrading from 0.9.2 or below, see the upgrading
+      note for 0.9.3.
+
+    to 0.9.3 from earlier versions
+     
+      'items_method' in ListField is gone; you'll have to adjust make
+      your forms use 'items' in the override tab now instead. Sorry
+      about that, it *was* marked experimental. :)
+
+      There should be no other problems in upgrading.
+
+    to 0.9.2 from earlier versions 
+
+      There should be no significant upgrade problems; your forms
+      should still work. RangedIntegerFields should show up as
+      IntegerFields, which subsume their functionality.
+
+    to 0.9.1 from earlier versions
+
+      There should be no significant upgrade problems; your forms
+      should still work.
+
+  Quickstart
+
+    Formulator follows the normal Zope filesystem product installation
+    procedure; just unpack the tarball to your products directory and
+    restart Zope.
+
+    Now the same at a more leisurely pace.
+
+  Unpacking
+
+    Formulator comes as a 'Formulator-x.x.tgz' file, where 'x.x'
+    stands for the Formulator version number. On Unix, you can use::
+
+      tar xvzf Formulator-x.x.tgz 
+
+    to unpack the file. On Windows you can use your favorite archiving
+    software, such as WinZip.
+
+    This will create a Formulator directory. 
+
+  Installing the Product 
+
+    Move this directory to your Zope's Products directory. Normally
+    this is 'yourzope/lib/python/Products'. 
+
+    Now restart your Zope.
+
+  Verifying Installation
+
+    If all went well, Formulator should now be visible in Zope in the
+    Products screen ('/Control_Panel/Products'). In a Zope folder, you
+    should now see a 'Formulator Form' in your 'Add' list. You should
+    be able to add a form to a folder now.
+
+
+
diff --git a/product/Formulator/LICENSE.txt b/product/Formulator/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ae1ac136dd720e5d39639c51e633b5f32e3ca256
--- /dev/null
+++ b/product/Formulator/LICENSE.txt
@@ -0,0 +1,29 @@
+Copyright (c) 2001, 2002, 2003 Infrae. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+   
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in
+     the documentation and/or other materials provided with the
+     distribution.
+
+  3. Neither the name of Infrae nor the names of its contributors may
+     be used to endorse or promote products derived from this software
+     without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INFRAE OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/product/Formulator/ListTextAreaField.py b/product/Formulator/ListTextAreaField.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7599d8ecc702395e45a6e51eeb4bf5e5f411f10
--- /dev/null
+++ b/product/Formulator/ListTextAreaField.py
@@ -0,0 +1,56 @@
+import string
+from DummyField import fields
+import Widget, Validator
+from Field import ZMIField
+
+class ListTextAreaWidget(Widget.TextAreaWidget):
+    default = fields.ListTextAreaField('default',
+                                       title='Default',
+                                       default=[],
+                                       required=0)
+
+    def render(self, field, key, value, REQUEST, render_prefix=None):
+        if value is None:
+            value = field.get_value('default')
+        lines = []
+        for element_text, element_value in value:
+            lines.append("%s | %s" % (element_text, element_value))
+        return Widget.TextAreaWidget.render(self, field, key,
+                                            string.join(lines, '\n'),
+                                            REQUEST)
+
+ListTextAreaWidgetInstance = ListTextAreaWidget()
+
+class ListLinesValidator(Validator.LinesValidator):
+    """A validator that can deal with lines that have a | separator
+    in them to split between text and value of list items.
+    """
+    def validate(self, field, key, REQUEST):
+        value = Validator.LinesValidator.validate(self, field, key, REQUEST)
+        result = []
+        for line in value:
+            elements = string.split(line, "|")
+            if len(elements) >= 2:
+                text, value = elements[:2]
+            else:
+                text = line
+                value = line
+            text = string.strip(text)
+            value = string.strip(value)
+            result.append((text, value))
+        return result
+
+ListLinesValidatorInstance = ListLinesValidator()
+
+class ListTextAreaField(ZMIField):
+    meta_type = "ListTextAreaField"
+
+    # field only has internal use
+    internal_field = 1
+
+    widget = ListTextAreaWidgetInstance
+    validator = ListLinesValidatorInstance
+    
+
+
+
diff --git a/product/Formulator/MethodField.py b/product/Formulator/MethodField.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d30f2f038d96b1ae511910fa9136fcccff7664f
--- /dev/null
+++ b/product/Formulator/MethodField.py
@@ -0,0 +1,75 @@
+import string
+from DummyField import fields
+import Widget, Validator
+from Globals import Persistent
+import Acquisition
+from Field import ZMIField
+from AccessControl import getSecurityManager
+
+class MethodWidget(Widget.TextWidget):
+    default = fields.MethodField('default',
+                                 title='Default',
+                                 default="",
+                                 required=0)
+    
+    def render(self, field, key, value, REQUEST, render_prefix=None):
+        if value == None:
+            method_name = field.get_value('default')
+        else:
+            if value != "":
+                method_name = value.method_name
+            else:
+                method_name = ""
+        return Widget.TextWidget.render(self, field, key, method_name, REQUEST)
+
+MethodWidgetInstance = MethodWidget()
+
+class Method(Persistent, Acquisition.Implicit):
+    """A method object; calls method name in acquisition context.
+    """
+    def __init__(self, method_name):
+        self.method_name = method_name
+        
+    def __call__(self, *arg, **kw):
+        # get method from acquisition path
+        method = getattr(self, self.method_name)
+        # check if we have 'View' permission for this method
+        # (raises error if not)
+        getSecurityManager().checkPermission('View', method)
+        # okay, execute it with supplied arguments
+        return apply(method, arg, kw)
+
+class BoundMethod(Method):
+    """A bound method calls a method on a particular object.
+    Should be used internally only.
+    """
+    def __init__(self, object, method_name):
+        BoundMethod.inheritedAttribute('__init__')(self, method_name)
+        self.object = object
+          
+    def __call__(self, *arg, **kw):
+        method = getattr(self.object, self.method_name)
+        return apply(method, arg, kw)
+    
+class MethodValidator(Validator.StringBaseValidator):
+
+    def validate(self, field, key, REQUEST):
+        value = Validator.StringBaseValidator.validate(self, field, key,
+                                                       REQUEST)
+
+        if value == "" and not field.get_value('required'):
+            return value
+
+        return Method(value)
+    
+MethodValidatorInstance = MethodValidator()
+
+class MethodField(ZMIField):
+    meta_type = 'MethodField'
+
+    internal_field = 1
+
+    widget = MethodWidgetInstance
+    validator = MethodValidatorInstance
+    
+    
diff --git a/product/Formulator/PatternChecker.py b/product/Formulator/PatternChecker.py
new file mode 100644
index 0000000000000000000000000000000000000000..3bc139b6c56158a6a1f025271fc0a31a86b22d51
--- /dev/null
+++ b/product/Formulator/PatternChecker.py
@@ -0,0 +1,151 @@
+import re
+
+# Symbols that are used to represent groups of characters
+
+NUMBERSYMBOL  = 'd'             # 0-9
+CHARSYMBOL    = 'e'             # a-zA-Z
+NUMCHARSYMBOL = 'f'             # a-zA-Z0-9
+
+# List of characters, that are special to Regex. Listing them here and
+# therefore escaping them will help making the Validator secure.
+# NOTE: Please do not add '*', since it is used to determine inifinite
+# long char symbol rows. (See examples at the of the file.)
+
+DANGEROUSCHARS = '\\()+?.$'
+
+class PatternChecker:
+    """
+    This class defines a basic user friendly checker and processor of
+    string values according to pattern.
+    It can verify whether a string value fits a certain pattern of
+    digits and letters and possible special characters.
+    """
+    # a dictionary that converts an array of symbols to regex expressions
+    symbol_regex_dict = {NUMBERSYMBOL  : '([0-9]{%i,%s})',
+                         CHARSYMBOL    : '([a-zA-Z]{%i,%s})',
+                         NUMCHARSYMBOL : '([0-9a-zA-Z]{%i,%s})'}
+    
+    def _escape(self, match_object):
+        """Escape a single character.
+        """ 
+        return '\\' + match_object.group(0)
+    
+    def _escape_special_characters(self, s):
+        """Escape the characters that have a special meaning in regex.
+        """
+        return re.sub('[' + DANGEROUSCHARS + ']', self._escape, s)
+
+    def _unescape_special_characters(self, s):
+        """Reverse the escaping, so that the final string is as close as
+        possible to the original one.
+        """
+        return re.sub('\\\\', '', s)
+
+    def _replace_symbol_by_regex(self, match_object):
+        """Replace the character symbol with their respective regex.
+        """
+        length = len(match_object.group(0))
+
+        # Yikes, what a hack! But I could not come up with something better.
+        if match_object.group(0)[-1] == '*':
+            min = length - 1
+            max = ''
+        else:
+            min = length
+            max = str(min)
+            
+        return self.symbol_regex_dict[match_object.group(0)[0]] %(min, max)
+
+    def make_regex_from_pattern(self, pattern):
+        """Replaces all symbol occurences and creates a complete regex
+        string.
+        """
+        regex = self._escape_special_characters(pattern)
+        for symbol in [NUMBERSYMBOL, CHARSYMBOL, NUMCHARSYMBOL]:
+            regex = re.sub(symbol+'{1,}\*?', self._replace_symbol_by_regex, regex)
+        return '^ *' + regex + ' *$'
+    
+    def construct_value_from_match(self, result, pattern):
+        """After we validated the string, we put it back together; this is
+        good, since we can easily clean up the data this way.
+        """
+        value = self._escape_special_characters(pattern)
+        _symbols = '['+NUMBERSYMBOL + CHARSYMBOL + NUMCHARSYMBOL + ']'
+        re_obj = re.compile(_symbols+'{1,}\*?')
+        for res in result.groups():
+            match = re_obj.search(value)
+            value = value[:match.start()] + res + value[match.end():]
+        return value
+
+    def clean_value(self, value):
+        """Clean up unnecessary white characters.
+        """
+        # same as string.strip, but since I am using re everywhere here,
+        # why not use it now too?
+        value = re.sub('^\s*', '', value)
+        value = re.sub('\s*$', '', value)
+        # make out of several white spaces, one whitespace...
+        value = re.sub('  *', ' ', value)
+        return value
+    
+    def validate_value(self, patterns, value):
+        """Validate method that manges the entire validation process.
+        
+        The validator goes through each pattern and
+        tries to get a match to the value (second parameter). At the end, the
+        first pattern of the list is taken to construct the value again; this
+        ensures data cleansing and a common data look.
+        """
+        value = self.clean_value(value)
+
+        result = None
+        for pattern in patterns:
+            regex = self.make_regex_from_pattern(pattern)
+            re_obj = re.compile(regex)
+            result = re_obj.search(value)
+            if result:
+                break
+
+        if not result:
+            return None
+
+        value = self.construct_value_from_match(result, patterns[0])
+        return self._unescape_special_characters(value)
+
+if __name__ == '__main__':
+
+    val = PatternChecker()
+
+    # American long ZIP
+    print val.validate_value(['ddddd-dddd'], '34567-1298')
+    print val.validate_value(['ddddd-dddd'], '  34567-1298  \t  ')
+
+    # American phone number
+    print val.validate_value(['(ddd) ddd-dddd', 'ddd-ddd-dddd',
+                              'ddd ddd-dddd'],
+                             '(345) 678-1298')
+    print val.validate_value(['(ddd) ddd-dddd', 'ddd-ddd-dddd',
+                              'ddd ddd-dddd'],
+                             '345-678-1298')
+
+    # American money
+    print val.validate_value(['$ d*.dd'], '$ 1345345.00')
+
+    # German money
+    print val.validate_value(['d*.dd DM'], '267.98 DM')
+
+    # German license plate 
+    print val.validate_value(['eee ee-ddd'], 'OSL HR-683')
+
+    # German phone number (international)
+    print val.validate_value(['+49 (d*) d*'], '+49 (3574) 7253')
+    print val.validate_value(['+49 (d*) d*'], '+49  (3574)  7253')
+
+
+
+
+
+
+
+
+
diff --git a/product/Formulator/ProductForm.py b/product/Formulator/ProductForm.py
new file mode 100644
index 0000000000000000000000000000000000000000..1de030c0c29c1141abac0d069db933f39f15e766
--- /dev/null
+++ b/product/Formulator/ProductForm.py
@@ -0,0 +1,138 @@
+"""
+ProductForm.py
+
+This file is an adaptation from part of Plone's FormTool.py tool.
+It provides a wrapping around Formulator.BasicForm, allowing it
+to be created inside a product but used outside it.
+"""
+
+import string
+
+from AccessControl import ClassSecurityInfo
+
+from Globals import InitializeClass
+import FormValidationError, BasicForm
+import StandardFields
+
+class ProductForm(BasicForm):
+    """Wraps Formulator.BasicForm and provides some convenience methods that
+       make BasicForms easier to work with from external methods."""
+    security = ClassSecurityInfo()
+    security.declareObjectPublic()
+
+    security.declareProtected('View', 'get_field')
+    def get_field(self, id):
+        """Get a field of a certain id, wrapping in context of self
+        """
+        return self.fields[id].__of__(self)
+
+    security.declarePublic('addField')
+    def addField(self, field_id, fieldType, group=None, **kwargs):
+        """
+        Adds a Formulator Field to the wrapped BasicForm.
+
+        fieldType: An abbreviation for the Field type.
+            'String' generates a StringField, 'Int' generates an IntField, etc.
+            Uses a StringField if no suitable Field type is found.
+        field_id: Name of the variable in question.  Note that Formulator adds
+            'field_' to variable names, so you will need to refer to the variable
+            foo as field_foo in form page templates.
+        group: Formulator group for the field.
+
+        Additional arguments: addField passes all other arguments on to the
+            new Field object.  In addition, it allows you to modify the
+            Field's error messages by passing in arguments of the form
+            name_of_message = 'New error message'
+
+        See Formulator.StandardFields for details.
+        """
+
+        if fieldType[-5:]!='Field':
+            fieldType = fieldType+'Field'
+
+        formulatorFieldClass = None
+
+        if hasattr(StandardFields, fieldType):
+            formulatorFieldClass = getattr(StandardFields, fieldType)
+        else:
+            formulatorFieldClass = getattr(StandardFields, 'StringField')
+
+        # pass a title parameter to the Field
+        kwargs['title'] = field_id
+
+        fieldObject = apply(formulatorFieldClass, (field_id, ), kwargs)
+
+        # alter Field error messages
+        # Note: This messes with Formulator innards and may break in the future.
+        # Unfortunately, Formulator doesn't do this already in Field.__init__
+        # and there isn't a Python-oriented method for altering message values
+        # so at present it's the only option.
+        for arg in kwargs.keys():
+            if fieldObject.message_values.has_key(arg):
+                fieldObject.message_values[arg] = kwargs[arg]
+
+        # Add the new Field to the wrapped BasicForm object
+        BasicForm.add_field(self, fieldObject, group)
+
+
+    security.declarePublic('validate')
+    def validate(self, REQUEST, errors=None):
+        """
+        Executes the validator for each field in the wrapped BasicForm.add_field
+        Returns the results in a dictionary.
+        """
+
+        if errors is None:
+            errors = REQUEST.get('errors', {})
+
+        # This is a bit of a hack to make some of Formulator's quirks
+        # transparent to developers.  Formulator expects form fields to be
+        # prefixed by 'field_' in the request.  To remove this restriction,
+        # we mangle the REQUEST, renaming keys from key to 'field_' + key
+        # before handing off to Formulator's validators.  We will undo the
+        # mangling afterwards.
+        for field in self.get_fields():
+            key = field.id
+            value = REQUEST.get(key)
+            if value:
+                # get rid of the old key
+                try:
+                    del REQUEST[key]
+                except:
+                    pass
+                # move the old value to 'field_' + key
+                # if there is already a value at 'field_' + key,
+                #    move it to 'field_field_' + key, and repeat
+                #    to prevent key collisions
+                newKey = 'field_' + key
+                newValue = REQUEST.get(newKey)
+                while newValue:
+                    REQUEST[newKey] = value
+                    newKey = 'field_' + newKey
+                    value = newValue
+                    newValue = REQUEST.get(newKey)
+                REQUEST[newKey] = value
+
+        try:
+            result=self.validate_all(REQUEST)
+        except FormValidationError, e:
+            for error in e.errors:
+                errors[error.field.get_value('title')]=error.error_text
+
+        # unmangle the REQUEST
+        for field in self.get_fields():
+            key = field.id
+            value = 1
+            while value:
+                key = 'field_' + key
+                value = REQUEST.get(key)
+                if value:
+                    REQUEST[key[6:]] = value
+                    try:
+                        del REQUEST[key]
+                    except:
+                        pass
+
+        return errors
+
+InitializeClass(ProductForm)
diff --git a/product/Formulator/README.txt b/product/Formulator/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..280465f60821132ce4121044faf5d12fc460c54b
--- /dev/null
+++ b/product/Formulator/README.txt
@@ -0,0 +1,43 @@
+Formulator
+
+  Formulator is a tool to help with the creation and validation of web
+  forms. Form fields are stored as objects in Zope, in a special Form
+  folder.
+
+Features
+
+  * manage form fields through the Zope management interface.
+
+  * manage field look & feel as well as validation and processing
+    behavior.
+
+  * automatic field validation.
+
+  * determine field order and group fields together.
+
+  * easy extensibility with new field types.
+
+  * online help.
+
+  * serialization of form to XML and back.
+
+Installation and Requirements
+
+  See INSTALL.txt for more information on installing Formulator.
+
+Information
+
+  Formulator comes with online help, so click on 'Help!' in the Zope
+  management screens. If you want your brain to explode, read the
+  'How Formulator Eats its Own Dogfood' help topic.
+
+  Information is also available at the Formulator web site:
+
+     http://www.zope.org/Members/faassen/Formulator
+
+  There are also instructions to join the Formulator mailing list there. 
+  Discussion about Formulator should preferably happen on the mailing list
+  first, though you can always mail me as well. But please consider the
+  list if you have questions or suggestions.
+  
+  Even more info can be found by reading the source. :)
diff --git a/product/Formulator/StandardFields.py b/product/Formulator/StandardFields.py
new file mode 100644
index 0000000000000000000000000000000000000000..c78af20f3df61306acaa1ab87a97b9dfc1a84294
--- /dev/null
+++ b/product/Formulator/StandardFields.py
@@ -0,0 +1,311 @@
+from Form import BasicForm
+from Field import ZMIField
+from DummyField import fields
+from MethodField import BoundMethod
+from DateTime import DateTime
+import Validator, Widget
+import OFS
+
+class StringField(ZMIField):
+  meta_type = "StringField"
+
+  widget = Widget.TextWidgetInstance
+  validator = Validator.StringValidatorInstance
+
+class PasswordField(ZMIField):
+  meta_type = "PasswordField"
+
+  widget = Widget.PasswordWidgetInstance
+  validator = Validator.StringValidatorInstance
+
+class EmailField(ZMIField):
+  meta_type = "EmailField"
+
+  widget = Widget.TextWidgetInstance
+  validator = Validator.EmailValidatorInstance
+
+class PatternField(ZMIField):
+  meta_type = "PatternField"
+
+  widget = Widget.TextWidgetInstance
+  validator = Validator.PatternValidatorInstance
+
+class CheckBoxField(ZMIField):
+  meta_type = "CheckBoxField"
+
+  widget = Widget.CheckBoxWidgetInstance
+  validator = Validator.BooleanValidatorInstance
+
+class IntegerField(ZMIField):
+  meta_type = "IntegerField"
+
+  widget = Widget.IntegerWidgetInstance
+  validator = Validator.IntegerValidatorInstance
+
+class RangedIntegerField(ZMIField):
+  meta_type = "RangedIntegerField"
+
+  # this field is not addable anymore and deprecated. For
+  # backwards compatibility it's a clone of IntegerField,
+  # though it may go away in the future.
+  internal_field = 1 
+
+  widget = Widget.TextWidgetInstance
+  validator = Validator.IntegerValidatorInstance
+
+class FloatField(ZMIField):
+  meta_type = "FloatField"
+
+  widget = Widget.FloatWidgetInstance
+  validator = Validator.FloatValidatorInstance
+
+class TextAreaField(ZMIField):
+  meta_type = "TextAreaField"
+
+  widget = Widget.TextAreaWidgetInstance
+  validator = Validator.TextValidatorInstance
+
+class RawTextAreaField(ZMIField):
+  meta_type = "RawTextAreaField"
+
+  widget = Widget.TextAreaWidgetInstance
+  validator = Validator.StringValidatorInstance
+
+class ListField(ZMIField):
+  meta_type = "ListField"
+
+  widget = Widget.ListWidgetInstance
+  validator = Validator.SelectionValidatorInstance
+
+class MultiListField(ZMIField):
+  meta_type = "MultiListField"
+
+  widget = Widget.MultiListWidgetInstance
+  validator = Validator.MultiSelectionValidatorInstance
+
+class LinesField(ZMIField):
+  meta_type = "LinesField"
+
+  widget = Widget.LinesTextAreaWidgetInstance
+  validator = Validator.LinesValidatorInstance
+
+class RadioField(ZMIField):
+  meta_type = "RadioField"
+
+  widget = Widget.RadioWidgetInstance
+  validator = Validator.SelectionValidatorInstance
+
+class MultiCheckBoxField(ZMIField):
+  meta_type = "MultiCheckBoxField"
+
+  widget = Widget.MultiCheckBoxWidgetInstance
+  validator = Validator.MultiSelectionValidatorInstance
+
+class FileField(ZMIField):
+  meta_type = "FileField"
+
+  widget = Widget.FileWidgetInstance
+  validator = Validator.FileValidatorInstance
+
+class LinkField(ZMIField):
+  meta_type = "LinkField"
+
+  widget = Widget.LinkWidgetInstance
+  validator = Validator.LinkValidatorInstance
+
+class LabelField(ZMIField):
+  """Just a label, doesn't really validate.
+  """
+  meta_type = "LabelField"
+
+  widget = Widget.LabelWidgetInstance
+  validator = Validator.SuppressValidatorInstance
+
+class DateTimeField(ZMIField):
+  meta_type = "DateTimeField"
+
+  widget = Widget.DateTimeWidgetInstance
+  validator = Validator.DateTimeValidatorInstance
+
+  def __init__(self, id, **kw):
+    # icky but necessary...
+    apply(ZMIField.__init__, (self, id), kw)
+
+    input_style = self.get_value('input_style')
+    if input_style == 'text':
+      self.sub_form = create_datetime_text_sub_form()
+    elif input_style == 'list':
+      self.sub_form = create_datetime_list_sub_form()
+    else:
+      assert 0, "Unknown input_style '%s'" % input_style
+
+  def on_value_input_style_changed(self, value):
+    if value == 'text':
+      self.sub_form = create_datetime_text_sub_form()
+    elif value == 'list':
+      self.sub_form = create_datetime_list_sub_form()
+      year_field = self.sub_form.get_field('year', include_disabled=1)
+      year_field.overrides['items'] = BoundMethod(self,
+                                                  'override_year_items')
+    else:
+      assert 0, "Unknown input_style."
+    self.on_value_css_class_changed(self.values['css_class'])
+
+  def on_value_css_class_changed(self, value):
+    for field in self.sub_form.get_fields():
+      field.values['css_class'] = value
+      field._p_changed = 1
+
+  def override_year_items(self):
+    """The method gets called to get the right amount of years.
+    """
+    start_datetime = self.get_value('start_datetime')
+    end_datetime = self.get_value('end_datetime')
+    current_year = DateTime().year()
+    if start_datetime:
+      first_year = start_datetime.year()
+    else:
+      first_year = current_year
+    if end_datetime:
+      last_year = end_datetime.year() + 1
+    else:
+      last_year = first_year + 11
+    return create_items(first_year, last_year, digits=4)
+
+  def _get_user_input_value(self, key, REQUEST):
+    """
+    Try to get a value of the field from the REQUEST
+    """
+    if REQUEST.form['subfield_%s_%s' % (key, 'year')]:
+      return None
+
+gmt_timezones =  [('GMT%s' %zone, 'GMT%s' %zone,) for zone in range(-12, 0)]\
+                  + [('GMT', 'GMT',),] \
+                  + [('GMT+%s' %zone, 'GMT+%s' %zone,) for zone in range(1, 13)]
+
+def create_datetime_text_sub_form():
+  sub_form = BasicForm()
+
+  year = IntegerField('year',
+                      title="Year",
+                      required=0,
+                      display_width=4,
+                      display_maxwidth=4,
+                      max_length=4)
+
+  month = IntegerField('month',
+                        title="Month",
+                        required=0,
+                        display_width=2,
+                        display_maxwidth=2,
+                        max_length=2)
+
+  day = IntegerField('day',
+                      title="Day",
+                      required=0,
+                      display_width=2,
+                      display_maxwidth=2,
+                      max_length=2)
+  sub_form.add_group("date")
+  sub_form.add_fields([year, month, day], "date")
+
+  hour = IntegerField('hour',
+                      title="Hour",
+                      required=0,
+                      display_width=2,
+                      display_maxwidth=2,
+                      max_length=2)
+
+  minute = IntegerField('minute',
+                        title="Minute",
+                        required=0,
+                        display_width=2,
+                        display_maxwidth=2,
+                        max_length=2)
+
+  ampm = StringField('ampm',
+                      title="am/pm",
+                      required=0,
+                      display_width=2,
+                      display_maxwidth=2,
+                      max_length=2)
+  timezone = ListField('timezone',
+                        title = "Timezone",
+                        required = 0,
+                        default = 'GMT',
+                        items = gmt_timezones,
+                        size = 1)
+  sub_form.add_fields([hour, minute, ampm, timezone], "time")
+  return sub_form
+
+def create_datetime_list_sub_form():
+  """ Patch Products.Formulator.StandardFields so we can add timezone subfield """
+  sub_form = BasicForm()
+
+  year = ListField('year',
+                    title="Year",
+                    required=0,
+                    default="",
+                    items=create_items(2000, 2010, digits=4),
+                    size=1)
+
+  month = ListField('month',
+                    title="Month",
+                    required=0,
+                    default="",
+                    items=create_items(1, 13, digits=2),
+                    size=1)
+
+  day = ListField('day',
+                  title="Day",
+                  required=0,
+                  default="",
+                  items=create_items(1, 32, digits=2),
+                  size=1)
+
+  sub_form.add_group("date")
+  sub_form.add_fields([year, month, day], "date")
+
+  hour = IntegerField('hour',
+                      title="Hour",
+                      required=0,
+                      display_width=2,
+                      display_maxwidth=2,
+                      max_length=2)
+
+  minute = IntegerField('minute',
+                        title="Minute",
+                        required=0,
+                        display_width=2,
+                        display_maxwidth=2,
+                        max_length=2)
+
+  ampm = ListField('ampm',
+                    title="am/pm",
+                    required=0,
+                    default="am",
+                    items=[("am","am"),
+                          ("pm","pm")],
+                    size=1)
+  timezone = ListField('timezone',
+                        title = "Timezone",
+                        required = 0,
+                        default = 'GMT',
+                        items = gmt_timezones,
+                        size = 1)
+  sub_form.add_group("time")
+
+  sub_form.add_fields([hour, minute, ampm, timezone], "time")
+  return sub_form
+
+def create_items(start, end, digits=0):
+  result = [("-", "")]
+  if digits:
+    format_string = "%0" + str(digits) + "d"
+  else:
+    format_string = "%s"
+  for i in range(start, end):
+    s = format_string % i
+    result.append((s, s))
+  return result
+
diff --git a/product/Formulator/TALESField.py b/product/Formulator/TALESField.py
new file mode 100644
index 0000000000000000000000000000000000000000..186737a93a873dd2345eef040ef8ae52edf34950
--- /dev/null
+++ b/product/Formulator/TALESField.py
@@ -0,0 +1,98 @@
+import string
+from DummyField import fields
+import Widget, Validator
+from Globals import Persistent
+import Acquisition
+from Field import ZMIField
+from AccessControl import getSecurityManager
+   
+class TALESWidget(Widget.TextWidget):
+  default = fields.MethodField('default',
+                                title='Default',
+                                default="",
+                                required=0)
+
+  def render(self, field, key, value, REQUEST, render_prefix=None):
+    if value == None:
+      text = field.get_value('default')
+    else:
+      if value != "":
+        text = value._text
+      else:
+        text = ""
+    return Widget.TextWidget.render(self, field, key, text, REQUEST)
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    """
+    Render TALES as read only
+    """
+    if value == None:
+      text = field.get_value('default', REQUEST=REQUEST)
+    else:
+      if value != "":
+        text = value._text
+      else:
+        text = ""
+    return text
+
+TALESWidgetInstance = TALESWidget()
+
+class TALESNotAvailable(Exception):
+    pass
+
+try:
+    # try to import getEngine from TALES
+    from Products.PageTemplates.Expressions import getEngine
+    
+    class TALESMethod(Persistent, Acquisition.Implicit):
+        """A method object; calls method name in acquisition context.
+        """
+        def __init__(self, text):
+            self._text = text
+            
+        def __call__(self, **kw):
+            expr = getattr(self, '_v_expr', None)
+            if expr is None:
+                self._v_expr = expr = getEngine().compile(self._text)
+            return getEngine().getContext(kw).evaluate(expr)
+
+            # check if we have 'View' permission for this method
+            # (raises error if not)
+            # getSecurityManager().checkPermission('View', method)
+
+    TALES_AVAILABLE = 1
+    
+except ImportError:
+    # cannot import TALES, so supply dummy TALESMethod
+    class TALESMethod(Persistent, Acquisition.Implicit):
+        """A dummy method in case TALES is not available.
+        """
+        def __init__(self, text):
+            self._text = text
+
+        def __call__(self, **kw):
+            raise TALESNotAvailable
+    TALES_AVAILABLE = 0
+    
+class TALESValidator(Validator.StringBaseValidator):
+
+    def validate(self, field, key, REQUEST):
+        value = Validator.StringBaseValidator.validate(self, field, key,
+                                                       REQUEST)
+
+        if value == "" and not field.get_value('required'):
+            return value
+
+        return TALESMethod(value)
+    
+TALESValidatorInstance = TALESValidator()
+
+class TALESField(ZMIField):
+    meta_type = 'TALESField'
+
+    internal_field = 1
+
+    widget = TALESWidgetInstance
+    validator = TALESValidatorInstance
+    
+    
diff --git a/product/Formulator/TODO.txt b/product/Formulator/TODO.txt
new file mode 100644
index 0000000000000000000000000000000000000000..77af1679d8ca67eee233066909a639240dddf41f
--- /dev/null
+++ b/product/Formulator/TODO.txt
@@ -0,0 +1,20 @@
+Formulator TODO
+
+  - When using a BasicForm a field cannot get to the form's 
+    get_form_encoding method (where the ZMIForm uses acquisistion 
+    to achieve this).
+  
+  - Make composite fields work well as hidden fields.
+
+  - Add combobox field
+
+  - Add various button fields
+
+  - Investigate duration and time only field.
+
+  - internationalisation (or error messages first)
+
+  - HTML filtering field?
+
+  - Add more unit tests.
+
diff --git a/product/Formulator/Validator.py b/product/Formulator/Validator.py
new file mode 100644
index 0000000000000000000000000000000000000000..66b2e1f0ec55d0a674899e473aacdcf3993a9dc9
--- /dev/null
+++ b/product/Formulator/Validator.py
@@ -0,0 +1,791 @@
+import string, re
+import PatternChecker
+from DummyField import fields
+from DateTime import DateTime
+from threading import Thread
+from urllib import urlopen
+from urlparse import urljoin
+from Errors import ValidationError
+from DateTime.DateTime import DateError, TimeError
+
+class ValidatorBase:
+    """Even more minimalistic base class for validators.
+    """
+    property_names = ['enabled','editable']
+
+    message_names = []
+
+    enabled = fields.CheckBoxField('enabled',
+                                   title="Enabled",
+                                   description=(
+        "If a field is not enabled, it will considered to be not "
+        "in the form during rendering or validation. Be careful "
+        "when you change this state dynamically (in the TALES tab): "
+        "a user could submit a field that since got disabled, or "
+        "get a validation error as a field suddenly got enabled that "
+        "wasn't there when the form was drawn."),
+                                   default=1)
+
+    editable = fields.CheckBoxField('editable',
+                                   title="Editable",
+                                   description=(
+        "If a field is not editable, then the user can only see"
+        "the value. This allows to drawn very different forms depending"
+        "on use permissions."),
+                                   default=1)
+
+    def raise_error(self, error_key, field):
+        raise ValidationError(error_key, field)
+
+    def validate(self, field, key, REQUEST):    
+        pass # override in subclass
+
+    def need_validate(self, field, key, REQUEST):
+        """Default behavior is always validation.
+        """
+        return 1
+    
+class Validator(ValidatorBase):
+    """Validates input and possibly transforms it to output.
+    """
+    property_names = ValidatorBase.property_names + ['external_validator']
+
+    external_validator = fields.MethodField('external_validator',
+                                            title="External Validator",
+                                            description=(
+        "When a method name is supplied, this method will be "
+        "called each time this field is being validated. All other "
+        "validation code is called first, however. The value (result of "
+        "previous validation) and the REQUEST object will be passed as "
+        "arguments to this method. Your method should return true if the "
+        "validation succeeded. Anything else will cause "
+        "'external_validator_failed' to be raised."),
+                                            default="",
+                                            required=0)
+    
+    message_names = ValidatorBase.message_names + ['external_validator_failed']
+
+    external_validator_failed = "The input failed the external validator."
+
+class StringBaseValidator(Validator):
+    """Simple string validator.
+    """
+    property_names = Validator.property_names + ['required', 'whitespace_preserve']
+
+    required = fields.CheckBoxField('required',
+                                title='Required',
+                                description=(
+    "Checked if the field is required; the user has to fill in some "
+    "data."),
+                                default=0)
+
+    whitespace_preserve = fields.CheckBoxField('whitespace_preserve',
+                                               title="Preserve whitespace",
+                                               description=(
+        "Checked if the field preserves whitespace. This means even "
+        "just whitespace input is considered to be data."),
+                                               default=0)
+
+    message_names = Validator.message_names + ['required_not_found']
+
+    required_not_found = 'Input is required but no input given.'
+
+    def validate(self, field, key, REQUEST):
+      # We had to add this patch for hidden fields of type "list"
+      value = REQUEST.get(key, REQUEST.get('default_%s' % (key, )))
+      if value is None:
+        if field.get_value('required'):
+          raise Exception, 'Required field %s has not been transmitted. Check that all required fields are in visible groups.' % (repr(field.id), )
+        else:
+          raise KeyError, 'Field %s is not present in request object.' % (repr(field.id), )
+      if isinstance(value, str):
+        if field.has_value('whitespace_preserve'):
+          if not field.get_value('whitespace_preserve'):
+            value = string.strip(value)
+        else:
+          # XXX Compatibility: use to prevent KeyError exception from get_value
+          value = string.strip(value)
+      if field.get_value('required') and value == "":
+        self.raise_error('required_not_found', field)
+
+      return value
+
+class StringValidator(StringBaseValidator):
+    property_names = StringBaseValidator.property_names +\
+                     ['unicode', 'max_length', 'truncate']
+
+    unicode = fields.CheckBoxField('unicode',
+                                   title='Unicode',
+                                   description=(
+        "Checked if the field delivers a unicode string instead of an "
+        "8-bit string."),
+                                   default=0)
+
+    max_length = fields.IntegerField('max_length',
+                                     title='Maximum length',
+                                     description=(
+        "The maximum amount of characters that can be entered in this "
+        "field. If set to 0 or is left empty, there is no maximum. "
+        "Note that this is server side validation."),
+                                     default="",
+                                     required=0)
+    
+    truncate = fields.CheckBoxField('truncate',
+                                    title='Truncate',
+                                    description=(
+        "If checked, truncate the field if it receives more input than is "
+        "allowed. The normal behavior in this case is to raise a validation "
+        "error, but the text can be silently truncated instead."),
+                                    default=0)
+
+    message_names = StringBaseValidator.message_names +\
+                    ['too_long']
+
+    too_long = 'Too much input was given.'
+
+    def validate(self, field, key, REQUEST):
+        value = StringBaseValidator.validate(self, field, key, REQUEST)
+        if field.get_value('unicode'):
+            # use acquisition to get encoding of form
+            value = unicode(value, field.get_form_encoding())
+            
+        max_length = field.get_value('max_length') or 0
+        truncate = field.get_value('truncate')
+        
+        if max_length > 0 and len(value) > max_length:
+            if truncate:
+                value = value[:max_length]
+            else:
+                self.raise_error('too_long', field)
+        return value
+
+StringValidatorInstance = StringValidator()
+
+class EmailValidator(StringValidator):
+    message_names = StringValidator.message_names + ['not_email']
+
+    not_email = 'You did not enter an email address.'
+
+    # This regex allows for a simple username or a username in a
+    # multi-dropbox (%). The host part has to be a normal fully
+    # qualified domain name, allowing for 6 characters (.museum) as a
+    # TLD.  No bang paths (uucp), no dotted-ip-addresses, no angle
+    # brackets around the address (we assume these would be added by
+    # some custom script if needed), and of course no characters that
+    # don't belong in an e-mail address.
+    pattern = re.compile('^[0-9a-zA-Z_\'&.%+-]+@([0-9a-zA-Z]([0-9a-zA-Z-]*[0-9a-zA-Z])?\.)+[a-zA-Z]{2,6}$')
+    
+    def validate(self, field, key, REQUEST):
+        value = StringValidator.validate(self, field, key, REQUEST)
+        if value == "" and not field.get_value('required'):
+            return value
+
+        if self.pattern.search(string.lower(value)) == None:
+            self.raise_error('not_email', field)
+        return value
+
+EmailValidatorInstance = EmailValidator()
+
+class PatternValidator(StringValidator):
+    # does the real work
+    checker = PatternChecker.PatternChecker()
+    
+    property_names = StringValidator.property_names +\
+                     ['pattern']
+
+    pattern = fields.StringField('pattern',
+                                 title="Pattern",
+                                 required=1,
+                                 default="",
+                                 description=(
+        "The pattern the value should conform to. Patterns are "
+        "composed of digits ('d'), alphabetic characters ('e') and "
+        "alphanumeric characters ('f'). Any other character in the pattern "
+        "should appear literally in the value in that place. Internal "
+        "whitespace is checked as well but may be included in any amount. "
+        "Example: 'dddd ee' is a Dutch zipcode (postcode). "
+        "NOTE: currently experimental and details may change!")
+                                 )
+
+    message_names = StringValidator.message_names +\
+                    ['pattern_not_matched']
+
+    pattern_not_matched = "The entered value did not match the pattern."
+
+    def validate(self, field, key, REQUEST):
+        value = StringValidator.validate(self, field, key, REQUEST)
+        if value == "" and not field.get_value('required'):
+            return value
+        value = self.checker.validate_value([field.get_value('pattern')],
+                                            value)
+        if value is None:
+            self.raise_error('pattern_not_matched', field)
+        return value
+
+PatternValidatorInstance = PatternValidator()
+
+class BooleanValidator(Validator):
+    def validate(self, field, key, REQUEST):
+      result = REQUEST.get(key, REQUEST.get('default_%s' % key))
+      if result is None:
+        raise KeyError('Field %r is not present in request object.' % field.id)
+      # XXX If the checkbox is hidden, Widget_render_hidden is used instead of
+      #     CheckBoxWidget_render, and ':int' suffix is missing.
+      return result and result != '0' and 1 or 0
+
+
+BooleanValidatorInstance = BooleanValidator()
+
+class IntegerValidator(StringBaseValidator):
+    property_names = StringBaseValidator.property_names +\
+                     ['start', 'end']
+
+    start = fields.IntegerField('start',
+                                title='Start',
+                                description=(
+        "The integer entered by the user must be larger than or equal to "
+        "this value. If left empty, there is no minimum."),
+                                default="",
+                                required=0)
+
+    end = fields.IntegerField('end',
+                              title='End',
+                              description=(
+        "The integer entered by the user must be smaller than this "
+        "value. If left empty, there is no maximum."),
+                              default="",
+                              required=0)
+
+    message_names = StringBaseValidator.message_names +\
+                    ['not_integer', 'integer_out_of_range']
+
+    not_integer = 'You did not enter an integer.'
+    integer_out_of_range = 'The integer you entered was out of range.'
+
+    def validate(self, field, key, REQUEST):
+      value = StringBaseValidator.validate(self, field, key, REQUEST)
+      # we need to add this check again
+      if value == "" and not field.get_value('required'):
+        return value
+      try:
+        if value.find(' ')>0:
+          value = value.replace(' ','')
+        value = int(value)
+      except ValueError:
+        self.raise_error('not_integer', field)
+
+      start = field.get_value('start')
+      end = field.get_value('end')
+      if start != "" and value < start:
+        self.raise_error('integer_out_of_range', field)
+      if end != "" and value >= end:
+        self.raise_error('integer_out_of_range', field)
+      return value
+
+IntegerValidatorInstance = IntegerValidator()
+
+class FloatValidator(StringBaseValidator):
+  message_names = StringBaseValidator.message_names + ['not_float']
+
+  not_float = "You did not enter a floating point number."
+
+  def validate(self, field, key, REQUEST):
+    value = StringBaseValidator.validate(self, field, key, REQUEST)
+    if value == "" and not field.get_value('required'):
+      return value
+    value = value.replace(' ','')
+    input_style = field.get_value('input_style')
+    if value.find(',') >= 0:
+      value = value.replace(',','.')
+    if value.find('%')>=0:
+      value = value.replace('%','')
+    try:
+      value = float(value)
+      if input_style.find('%')>=0:
+        value = value/100
+    except ValueError:
+      self.raise_error('not_float', field)
+    return value
+
+FloatValidatorInstance = FloatValidator()
+
+class LinesValidator(StringBaseValidator):
+  property_names = StringBaseValidator.property_names +\
+                    ['unicode', 'max_lines', 'max_linelength', 'max_length']
+
+  unicode = fields.CheckBoxField('unicode',
+                                  title='Unicode',
+                                  description=(
+      "Checked if the field delivers a unicode string instead of an "
+      "8-bit string."),
+                                  default=0)
+
+  max_lines = fields.IntegerField('max_lines',
+                                  title='Maximum lines',
+                                  description=(
+      "The maximum amount of lines a user can enter. If set to 0, "
+      "or is left empty, there is no maximum."),
+                                  default="",
+                                  required=0)
+
+  max_linelength = fields.IntegerField('max_linelength',
+                                        title="Maximum length of line",
+                                        description=(
+      "The maximum length of a line. If set to 0 or is left empty, there "
+      "is no maximum."),
+                                        default="",
+                                        required=0)
+
+  max_length = fields.IntegerField('max_length',
+                                    title="Maximum length (in characters)",
+                                    description=(
+      "The maximum total length in characters that the user may enter. "
+      "If set to 0 or is left empty, there is no maximum."),
+                                    default="",
+                                    required=0)
+
+  message_names = StringBaseValidator.message_names +\
+                  ['too_many_lines', 'line_too_long', 'too_long']
+
+  too_many_lines = 'You entered too many lines.'
+  line_too_long = 'A line was too long.'
+  too_long = 'You entered too many characters.'
+
+  def validate(self, field, key, REQUEST):
+    value = StringBaseValidator.validate(self, field, key, REQUEST)
+    # Added as a patch for hidden values
+    if isinstance(value, (list, tuple)):
+      value = string.join(value, "\n")
+    # we need to add this check again
+    if value == "" and not field.get_value('required'):
+      return []
+    if field.get_value('unicode'):
+        value = unicode(value, field.get_form_encoding())
+    # check whether the entire input is too long
+    max_length = field.get_value('max_length') or 0
+    if max_length and len(value) > max_length:
+      self.raise_error('too_long', field)
+    # split input into separate lines
+    lines = string.split(value, "\n")
+
+    # check whether we have too many lines
+    max_lines = field.get_value('max_lines') or 0
+    if max_lines and len(lines) > max_lines:
+      self.raise_error('too_many_lines', field)
+
+    # strip extraneous data from lines and check whether each line is
+    # short enough
+    max_linelength = field.get_value('max_linelength') or 0
+    result = []
+    whitespace_preserve = field.get_value('whitespace_preserve')
+    for line in lines:
+      if not whitespace_preserve:
+        line = string.strip(line)
+      if max_linelength and len(line) > max_linelength:
+        self.raise_error('line_too_long', field)
+      result.append(line)
+
+    return result
+
+LinesValidatorInstance = LinesValidator()
+
+class TextValidator(LinesValidator):
+    def validate(self, field, key, REQUEST):
+        value = LinesValidator.validate(self, field, key, REQUEST)
+        # we need to add this check again
+        if value == [] and not field.get_value('required'):
+            return ""
+
+        # join everything into string again with \n and return
+        return string.join(value, "\n")
+
+TextValidatorInstance = TextValidator()
+
+class SelectionValidator(StringBaseValidator):
+
+    property_names = StringBaseValidator.property_names +\
+                     ['unicode']
+
+    unicode = fields.CheckBoxField('unicode',
+                                   title='Unicode',
+                                   description=(
+        "Checked if the field delivers a unicode string instead of an "
+        "8-bit string."),
+                                   default=0)
+    
+    message_names = StringBaseValidator.message_names +\
+                    ['unknown_selection']
+
+    unknown_selection = 'You selected an item that was not in the list.'
+    
+    def validate(self, field, key, REQUEST):
+      value = StringBaseValidator.validate(self, field, key, REQUEST)
+
+      if value == "" and not field.get_value('required'):
+        return value
+
+      # get the text and the value from the list of items
+      for item in list(field.get_value('items', cell=getattr(REQUEST,'cell',None))) + [field.get_value('default', cell=getattr(REQUEST,'cell',None))]:
+        try:
+          item_text, item_value = item
+        except (ValueError, TypeError):
+          item_text = item
+          item_value = item
+
+        # check if the value is equal to the string/unicode version of
+        # item_value; if that's the case, we can return the *original*
+        # value in the list (not the submitted value). This way, integers
+        # will remain integers.
+        # XXX it is impossible with the UI currently to fill in unicode
+        # items, but it's possible to do it with the TALES tab
+        if field.get_value('unicode') and isinstance(item_value, unicode):
+          str_value = item_value.encode(field.get_form_encoding())
+        else:
+          str_value = str(item_value)
+
+        if str_value == value:
+          return item_value
+
+      # if we didn't find the value, return error
+      self.raise_error('unknown_selection', field)
+
+SelectionValidatorInstance = SelectionValidator()
+
+class MultiSelectionValidator(Validator):
+    property_names = Validator.property_names + ['required', 'unicode']
+
+    required = fields.CheckBoxField('required',
+                                    title='Required',
+                                    description=(
+        "Checked if the field is required; the user has to fill in some "
+        "data."),
+                                    default=1)
+
+    unicode = fields.CheckBoxField('unicode',
+                                   title='Unicode',
+                                   description=(
+        "Checked if the field delivers a unicode string instead of an "
+        "8-bit string."),
+                                   default=0)
+
+    message_names = Validator.message_names + ['required_not_found',
+                                               'unknown_selection']
+    
+    required_not_found = 'Input is required but no input given.'
+    unknown_selection = 'You selected an item that was not in the list.'
+    
+    def validate(self, field, key, REQUEST):
+      if REQUEST.get('default_%s' % (key, )) is None:
+        LOG('MultiSelectionValidator_validate', 0, 'Field %s is not present in request object (marker field default_%s not found).' % (repr(field.id), key))
+        raise KeyError, 'Field %s is not present in request object (marker field default_%s not found).' % (repr(field.id), key)
+      values = REQUEST.get(key, [])
+      # NOTE: a hack to deal with single item selections
+      if not isinstance(values, list):
+        # put whatever we got in a list
+        values = [values]
+      # if we selected nothing and entry is required, give error, otherwise
+      # give entry list
+      if len(values) == 0:
+        if field.get_value('required'):
+          self.raise_error('required_not_found', field)
+        else:
+          return values
+      # convert everything to unicode if necessary
+      if field.get_value('unicode'):
+        values = [unicode(value, field.get_form_encoding())
+                    for value in values]
+
+      # create a dictionary of possible values
+      value_dict = {}
+      for item in field.get_value('items', cell=getattr(REQUEST,'cell',None)): # Patch by JPS for Listbox
+        try:
+          item_text, item_value = item
+        except ValueError:
+          item_text = item
+          item_value = item
+        value_dict[item_value] = 0
+      default_value = field.get_value('default', cell=getattr(REQUEST,'cell',None))
+      if isinstance(default_value, (list, tuple)):
+        for v in default_value:
+          value_dict[v] = 0
+      else:
+        value_dict[default_value] = 0
+
+
+      # check whether all values are in dictionary
+      result = []
+      for value in values:
+        # FIXME: hack to accept int values as well
+        try:
+          int_value = int(value)
+        except ValueError:
+          int_value = None
+        if int_value is not None and value_dict.has_key(int_value):
+          result.append(int_value)
+          continue
+        if value_dict.has_key(value):
+          result.append(value)
+          continue
+        self.raise_error('unknown_selection', field)
+      # everything checks out
+      return result
+
+MultiSelectionValidatorInstance = MultiSelectionValidator()
+
+class FileValidator(Validator):
+    def validate(self, field, key, REQUEST):
+        return REQUEST.get(key, None)
+    
+FileValidatorInstance = FileValidator()
+
+class LinkHelper:
+    """A helper class to check if links are openable.
+    """
+    status = 0
+
+    def __init__(self, link):
+        self.link = link
+        
+    def open(self):
+        try:
+            urlopen(self.link)
+        except:
+            # all errors will definitely result in a failure
+            pass
+        else:
+            # FIXME: would like to check for 404 errors and such?
+            self.status = 1
+
+class LinkValidator(StringValidator):
+    property_names = StringValidator.property_names +\
+                     ['check_link', 'check_timeout', 'link_type']
+    
+    check_link = fields.CheckBoxField('check_link',
+                                      title='Check Link',
+                                      description=(
+        "Check whether the link is not broken."),
+                                      default=0)
+
+    check_timeout = fields.FloatField('check_timeout',
+                                      title='Check Timeout',
+                                      description=(
+        "Maximum amount of seconds to check link. Required"),
+                                      default=7.0,
+                                      required=1)
+    
+    link_type = fields.ListField('link_type',
+                                 title='Type of Link',
+                                 default="external",
+                                 size=1,
+                                 items=[('External Link', 'external'),
+                                        ('Internal Link', 'internal'),
+                                        ('Relative Link', 'relative')],
+                                 description=(
+        "Define the type of the link. Required."),
+                                 required=1)
+    
+    message_names = StringValidator.message_names + ['not_link']
+    
+    not_link = 'The specified link is broken.'
+    
+    def validate(self, field, key, REQUEST):
+        value = StringValidator.validate(self, field, key, REQUEST)
+        if value == "" and not field.get_value('required'):
+            return value
+        
+        link_type = field.get_value('link_type')
+        if link_type == 'internal':
+            value = urljoin(REQUEST['BASE0'], value)
+        elif link_type == 'relative':
+            value = urljoin(REQUEST['URL1'], value)
+        # otherwise must be external
+
+        # FIXME: should try regular expression to do some more checking here?
+        
+        # if we don't need to check the link, we're done now
+        if not field.get_value('check_link'):
+            return value
+
+        # resolve internal links using Zope's resolve_url
+        if link_type in ['internal', 'relative']:
+            try:
+                REQUEST.resolve_url(value)
+            except:
+                self.raise_error('not_link', field)
+                
+        # check whether we can open the link
+        link = LinkHelper(value)
+        thread = Thread(target=link.open)
+        thread.start()
+        thread.join(field.get_value('check_timeout'))
+        del thread
+        if not link.status:
+            self.raise_error('not_link', field)
+
+        return value
+
+LinkValidatorInstance = LinkValidator()
+
+class DateTimeValidator(Validator):
+  """
+    Added support for key in every call to validate_sub_field
+  """
+  property_names = Validator.property_names + ['required',
+                                                'start_datetime',
+                                                'end_datetime',
+                                                'allow_empty_time']
+
+  required = fields.CheckBoxField('required',
+                                  title='Required',
+                                  description=(
+      "Checked if the field is required; the user has to enter something "
+      "in the field."),
+                                  default=1)
+
+  start_datetime = fields.DateTimeField('start_datetime',
+                                        title="Start datetime",
+                                        description=(
+      "The date and time entered must be later than or equal to "
+      "this date/time. If left empty, no check is performed."),
+                                        default=None,
+                                        input_style="text",
+                                        required=0)
+
+  end_datetime = fields.DateTimeField('end_datetime',
+                                      title="End datetime",
+                                      description=(
+      "The date and time entered must be earlier than "
+      "this date/time. If left empty, no check is performed."),
+                                      default=None,
+                                      input_style="text",
+                                      required=0)
+
+  allow_empty_time = fields.CheckBoxField('allow_empty_time',
+                                          title="Allow empty time",
+                                          description=(
+      "Allow time to be left empty. Time will default to midnight "
+      "on that date."),
+                                          default=0)
+
+  message_names = Validator.message_names + ['required_not_found',
+                                              'not_datetime',
+                                              'datetime_out_of_range']
+
+  required_not_found = 'Input is required but no input given.'
+  not_datetime = 'You did not enter a valid date and time.'
+  datetime_out_of_range = 'The date and time you entered were out of range.'
+
+  def validate(self, field, key, REQUEST):
+    try:
+      year = field.validate_sub_field('year', REQUEST, key=key)
+      month = field.validate_sub_field('month', REQUEST, key=key)
+      if field.get_value('hide_day'):
+        day = 1
+      else:
+        day = field.validate_sub_field('day', REQUEST, key=key)
+
+      if field.get_value('date_only'):
+        hour = 0
+        minute = 0
+      elif field.get_value('allow_empty_time'):
+          hour = field.validate_sub_field('hour', REQUEST, key=key)
+          minute = field.validate_sub_field('minute', REQUEST, key=key)
+          if hour == '' and minute == '':
+            hour = 0
+            minute = 0
+          elif hour == '' or minute == '':
+            raise ValidationError('not_datetime', field)
+      else:
+        hour = field.validate_sub_field('hour', REQUEST, key=key)
+        minute = field.validate_sub_field('minute', REQUEST, key=key)
+    except ValidationError:
+      self.raise_error('not_datetime', field)
+
+    # handling of completely empty sub fields
+    if ((year == '' and month == '') and
+        (field.get_value('hide_day') or day == '') and
+        (field.get_value('date_only') or (hour == '' and minute == '')
+        or (hour == 0 and minute == 0))):
+      if field.get_value('required'):
+        self.raise_error('required_not_found', field)
+      else:
+        # field is not required, return None for no entry
+        return None
+    # handling of partially empty sub fields; invalid datetime
+    if ((year == '' or month == '') or
+        (not field.get_value('hide_day') and day == '') or
+        (not field.get_value('date_only') and
+        (hour == '' or minute == ''))):
+      self.raise_error('not_datetime', field)
+
+    if field.get_value('ampm_time_style'):
+      ampm = field.validate_sub_field('ampm', REQUEST, key=key)
+      if field.get_value('allow_empty_time'):
+        if ampm == '':
+          ampm = 'am'
+      hour = int(hour)
+      # handling not am or pm
+      # handling hour > 12
+      if ((ampm != 'am') and (ampm != 'pm')) or (hour > 12):
+        self.raise_error('not_datetime', field)
+      if (ampm == 'pm') and (hour == 0):
+        self.raise_error('not_datetime', field)
+      elif ampm == 'pm' and hour < 12:
+        hour += 12
+
+    # handle possible timezone input
+    timezone = ''
+    if field.get_value('timezone_style'):
+      timezone =  field.validate_sub_field('timezone', REQUEST, key=key)
+
+    try:
+      # handling of hidden day, which can be first or last day of the month:
+      if field.get_value('hidden_day_is_last_day'):
+        if int(month) == 12:
+          tmp_year = int(year) + 1
+          tmp_month = 1
+        else:
+          tmp_year = int(year)
+          tmp_month = int(month) + 1
+        tmp_day = DateTime(tmp_year, tmp_month, 1, hour, minute)
+        result = tmp_day - 1
+      else:
+        result = DateTime(int(year),
+                          int(month),
+                          int(day),
+                          hour,
+                          minute)
+        year = result.year()
+        result = DateTime('%s/%s/%s %s:%s %s' % (year,
+                            int(month),
+                            int(day),
+                            hour,
+                            minute, timezone))
+      # ugh, a host of string based exceptions (not since Zope 2.7)
+    except ('DateTimeError', 'Invalid Date Components', 'TimeError',
+            DateError, TimeError) :
+      self.raise_error('not_datetime', field)
+
+    # check if things are within range
+    start_datetime = field.get_value('start_datetime')
+    if (start_datetime not in (None, '') and
+      result < start_datetime):
+      self.raise_error('datetime_out_of_range', field)
+    end_datetime = field.get_value('end_datetime')
+    if (end_datetime not in (None, '') and
+      result >= end_datetime):
+      self.raise_error('datetime_out_of_range', field)
+
+    return result
+
+DateTimeValidatorInstance = DateTimeValidator()
+
+class SuppressValidator(ValidatorBase):
+    """A validator that is actually not used.
+    """ 
+    def need_validate(self, field, key, REQUEST):
+        """Don't ever validate; suppress result in output.
+        """
+        return 0
+    
+SuppressValidatorInstance = SuppressValidator()
diff --git a/product/Formulator/Widget.py b/product/Formulator/Widget.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc6d43428b4c8f055f2e1c37c98b1be734eefb1b
--- /dev/null
+++ b/product/Formulator/Widget.py
@@ -0,0 +1,1422 @@
+import string
+from DummyField import fields
+from DocumentTemplate.DT_Util import html_quote
+from DateTime import DateTime
+from cgi import escape
+import types
+from DocumentTemplate.ustr import ustr
+
+class Widget:
+  """A field widget that knows how to display itself as HTML.
+  """
+
+  property_names = ['title', 'description',
+                    'default', 'css_class', 'alternate_name',
+                    'hidden']
+
+  title = fields.StringField('title',
+                              title='Title',
+                              description=(
+      "The title of this field. This is the title of the field that "
+      "will appear in the form when it is displayed. Required."),
+                              default="",
+                              required=1)
+
+  description = fields.TextAreaField('description',
+                                      title='Description',
+                                      description=(
+      "Description of this field. The description property can be "
+      "used to add a short description of what a field does; such as "
+      "this one."),
+                                      default="",
+                                      width="20", height="3",
+                                      required=0)
+
+  css_class = fields.StringField('css_class',
+                                  title='CSS class',
+                                  description=(
+      "The CSS class of the field. This can be used to style your "
+      "formulator fields using cascading style sheets. Not required."),
+                                  default="",
+                                  required=0)
+
+  alternate_name = fields.StringField('alternate_name',
+                                      title='Alternate name',
+                                      description=(
+      "An alternative name for this field. This name will show up in "
+      "the result dictionary when doing validation, and in the REQUEST "
+      "if validation goes to request. This can be used to support names "
+      "that cannot be used as Zope ids."),
+                                      default="",
+                                      required=0)
+
+  hidden = fields.CheckBoxField('hidden',
+                                title="Hidden",
+                                description=(
+      "This field will be on the form, but as a hidden field. The "
+      "contents of the hidden field will be the default value. "
+      "Hidden fields are not visible but will be validated."),
+                                default=0)
+
+  # NOTE: for ordering reasons (we want extra at the end),
+  # this isn't in the base class property_names list, but
+  # instead will be referred to by the subclasses.
+  extra = fields.StringField('extra',
+                              title='Extra',
+                              description=(
+      "A string containing extra HTML code for attributes. This "
+      "string will be literally included in the rendered field."
+      "This property can be useful if you want "
+      "to add an onClick attribute to use with JavaScript, for instance."),
+                              default="",
+                              required=0)
+
+  def render(self, field, key, value, REQUEST):
+      """Renders this widget as HTML using property values in field.
+      """
+      return "[widget]"
+
+  def render_hidden(self, field, key, value, REQUEST, render_prefix=None):
+    """Renders this widget as a hidden field.
+    """
+    try:
+      extra = field.get_value('extra')
+    except KeyError:
+    # In case extra is not defined as in DateTimeWidget
+      extra = ''
+    result = ''
+    # We must adapt the rendering to the type of the value
+    # in order to get the correct type back
+    if isinstance(value, (tuple, list)):
+      for v in value:
+        result += render_element("input",
+                          type="hidden",
+                          name="%s:list" % key,
+                          value=v,
+                          extra=extra)
+    else:
+      result = render_element("input",
+                          type="hidden",
+                          name=key,
+                          value=value,
+                          extra=extra)
+    return result
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    """Renders this widget for public viewing.
+    """
+    # default implementation
+    if value is None:
+      return ''
+    return value
+
+  render_pdf = render_view
+
+  def render_html(self, *args, **kw):
+    return self.render(*args, **kw)
+
+  def render_htmlgrid(self, field, key, value, REQUEST, render_prefix=None):
+    """
+    render_htmlgrid returns a list of tuple (title, html render)
+    """
+    # XXX Calling _render_helper on the field is not optimized
+    return ((field.get_value('title'), 
+            field._render_helper(key, value, REQUEST, render_prefix=render_prefix)),)
+  def render_css(self, field, REQUEST):
+    """
+      Default render css for widget - to be overwritten in field classes.
+      Should return valid css code as string.
+      The value returned by this method will be used as inline style for a field.
+    """
+    pass
+
+  def get_css_list(self, field, REQUEST):
+    """
+      Return CSS needed by the widget - to be overwritten in field classes.
+      Should return a list of CSS file names.
+      These names will be appended to global css_list and included in a rendered page.
+    """
+    return []
+
+  def get_javascript_list(self, field, REQUEST):
+    """
+      Return JS needed by the widget - to be overwritten in field classes.
+      Should return a list of javascript file names.
+      These names will be appended to global js_list and included in a rendered page.
+    """
+    return []
+
+  def render_dict(self, field, value):
+    """
+    This is yet another field rendering. It is designed to allow code to
+    understand field's value data by providing its type and format when
+    applicable.
+    """
+    return None
+
+class TextWidget(Widget):
+  """Text widget
+  """
+  property_names = Widget.property_names +\
+                    ['display_width', 'display_maxwidth', 'extra']
+
+  default = fields.StringField('default',
+                                title='Default',
+                                description=(
+      "You can place text here that will be used as the default "
+      "value of the field, unless the programmer supplies an override "
+      "when the form is being generated."),
+                                default="",
+                                required=0)
+
+  display_width = fields.IntegerField('display_width',
+                                      title='Display width',
+                                      description=(
+      "The width in characters. Required."),
+                                      default=20,
+                                      required=1)
+
+  display_maxwidth = fields.IntegerField('display_maxwidth',
+                                          title='Maximum input',
+                                          description=(
+      "The maximum input in characters that the widget will allow. "
+      "Required. If set to 0 or is left empty, there is no maximum. "
+      "Note that is client side behavior only."),
+                                          default="",
+                                          required=0)
+
+  def render(self, field, key, value, REQUEST, render_prefix=None):
+    """Render text input field.
+    """
+    display_maxwidth = field.get_value('display_maxwidth') or 0
+    if display_maxwidth > 0:
+      return render_element("input",
+                            type="text",
+                            name=key,
+                            css_class=field.get_value('css_class'),
+                            value=value,
+                            size=field.get_value('display_width'),
+                            maxlength=display_maxwidth,
+                            extra=field.get_value('extra'))
+    else:
+      return render_element("input",
+                            type="text",
+                            name=key,
+                            css_class=field.get_value('css_class'),
+                            value=value,
+                            size=field.get_value('display_width'),
+                            extra=field.get_value('extra'))
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    """Render text as non-editable.
+      This renderer is designed to be type error resistant.
+      in we get a non string value. It does escape the result
+      and produces clean xhtml.
+      Patch the render_view of TextField to enclose the value within <span> html tags if css class defined
+    """
+    if value is None:
+      return ''
+    if isinstance(value, types.ListType) or isinstance(value, types.TupleType):
+      old_value = value
+    else:
+      old_value = [str(value)]
+    value = []
+    for line in old_value:
+      value.append(escape(line))
+    value = '<br/>'.join(value)
+    css_class = field.get_value('css_class')
+    if css_class not in ('', None):
+      # All strings should be escaped before rendering in HTML
+      # except for editor field
+      return "<span class='%s'>%s</span>" % (css_class, value)
+    return value
+
+TextWidgetInstance = TextWidget()
+
+class PasswordWidget(TextWidget):
+
+  def render(self, field, key, value, REQUEST, render_prefix=None):
+      """Render password input field.
+      """
+      display_maxwidth = field.get_value('display_maxwidth') or 0
+      if display_maxwidth > 0:
+          return render_element("input",
+                                type="password",
+                                name=key,
+                                css_class=field.get_value('css_class'),
+                                value=value,
+                                size=field.get_value('display_width'),
+                                maxlength=display_maxwidth,
+                                extra=field.get_value('extra'))
+      else:
+          return render_element("input",
+                                type="password",
+                                name=key,
+                                css_class=field.get_value('css_class'),
+                                value=value,
+                                size=field.get_value('display_width'),
+                                extra=field.get_value('extra'))
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+      return "[password]"
+
+PasswordWidgetInstance = PasswordWidget()
+
+class CheckBoxWidget(Widget):
+  property_names = Widget.property_names + ['extra']
+
+  default = fields.CheckBoxField('default',
+                                  title='Default',
+                                  description=(
+      "Default setting of the widget; either checked or unchecked. "
+      "(true or false)"),
+                                  default=0)
+
+  def render(self, field, key, value, REQUEST, render_prefix=None):
+    """Render checkbox.
+    """
+    rendered = [render_element("input",
+                              type="hidden",
+                              name="default_%s:int" % (key, ),
+                              value="0")
+              ]
+
+    if value:
+      rendered.append(render_element("input",
+                                    type="checkbox",
+                                    name=key,
+                                    css_class=field.get_value('css_class'),
+                                    checked=None,
+                                    extra=field.get_value('extra'))
+                    )
+    else:
+      rendered.append(render_element("input",
+                                    type="checkbox",
+                                    name=key,
+                                    css_class=field.get_value('css_class'),
+                                    extra=field.get_value('extra'))
+                    )
+    return "".join(rendered)
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    """Render checkbox in view mode.
+    """
+    if value:
+      return render_element("input",
+                            type="checkbox",
+                            css_class=field.get_value('css_class'),
+                            checked=1,
+                            extra=field.get_value('extra'),
+                            disabled='disabled')
+    else:
+      return render_element("input",
+                            type="checkbox",
+                            css_class=field.get_value('css_class'),
+                            extra=field.get_value('extra'),
+                            disabled='disabled')
+CheckBoxWidgetInstance = CheckBoxWidget()
+
+class TextAreaWidget(Widget):
+    """Textarea widget
+    """
+    property_names = Widget.property_names +\
+                     ['width', 'height', 'extra']
+    
+    default = fields.TextAreaField('default',
+                                   title='Default',
+                                   description=(
+        "Default value of the text in the widget."),
+                                   default="",
+                                   width=20, height=3,
+                                   required=0)
+    
+    width = fields.IntegerField('width',
+                                title='Width',
+                                description=(
+        "The width (columns) in characters. Required."),
+                                default=40,
+                                required=1)
+
+    height = fields.IntegerField('height',
+                                 title="Height",
+                                 description=(
+        "The height (rows) in characters. Required."),
+                                 default=5,
+                                 required=1)
+
+    def render(self, field, key, value, REQUEST, render_prefix=None):
+      width = field.get_value('width', REQUEST=REQUEST)
+      height = field.get_value('height', REQUEST=REQUEST)
+
+      return render_element("textarea",
+                            name=key,
+                            css_class=field.get_value('css_class'),
+                            cols=width,
+                            rows=height,
+                            contents=html_quote(value),
+                            extra=field.get_value('extra'))
+
+    def render_view(self, field, value, REQUEST, render_prefix=None):
+        if value is None:
+          return ''
+        return value
+
+TextAreaWidgetInstance = TextAreaWidget()
+
+class LinesTextAreaWidget(TextAreaWidget):
+  property_names = Widget.property_names +\
+                    ['width', 'height', 'view_separator', 'extra']
+
+  default = fields.LinesField('default',
+                              title='Default',
+                              description=(
+      "Default value of the lines in the widget."),
+                              default=[],
+                              width=20, height=3,
+                              required=0)
+
+  view_separator = fields.StringField('view_separator',
+                                      title='View separator',
+                                      description=(
+      "When called with render_view, this separator will be used to "
+      "render individual items."),
+                                      width=20,
+                                      default='<br />\n',
+                                      whitespace_preserve=1,
+                                      required=1)
+
+  def render(self, field, key, value, REQUEST, render_prefix=None):
+    """
+    If type definition is missing for LinesField, the input text will be
+    splitted into list like ['f', 'o', 'o'] with original Formulator's
+    implementation. So explicit conversion to list is required before
+    passing to LinesTextAreaWidget's render and render_view methods.
+    """
+    if isinstance(value, (str, unicode)):
+      value = [value]
+    value = string.join(value, "\n")
+    return TextAreaWidget.render(self, field, key, value, REQUEST)
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    if value is None:
+      return ''
+    elif isinstance(value, (str, unicode)):
+      value = [value]
+    return string.join(value, field.get_value('view_separator'))
+
+LinesTextAreaWidgetInstance = LinesTextAreaWidget()
+
+class FileWidget(TextWidget):
+
+    def render(self, field, key, value, REQUEST, render_prefix=None):
+        """Render text input field.
+        """
+        display_maxwidth = field.get_value('display_maxwidth') or 0
+        if display_maxwidth > 0:
+            return render_element("input",
+                                  type="file",
+                                  name=key,
+                                  css_class=field.get_value('css_class'),
+                                  value=value,
+                                  size=field.get_value('display_width'),
+                                  maxlength=display_maxwidth,
+                                  extra=field.get_value('extra'))
+        else:
+            return render_element("input",
+                                  type="file",
+                                  name=key,
+                                  css_class=field.get_value('css_class'),
+                                  value=value,
+                                  size=field.get_value('display_width'),
+                                  extra=field.get_value('extra'))
+
+    def render_view(self, field, value, REQUEST=None, render_prefix=None):
+        return "[File]"
+    
+FileWidgetInstance = FileWidget()
+
+class ItemsWidget(Widget):
+    """A widget that has a number of items in it.
+    """
+    
+    items = fields.ListTextAreaField('items',
+                                     title='Items',
+                                     description=(
+        "Items in the field. Each row should contain an "
+        "item. Use the | (pipe) character to separate what is shown "
+        "to the user from the submitted value. If no | is supplied, the "
+        "shown value for the item will be identical to the submitted value. "
+        "Internally the items property returns a list. If a list item "
+        "is a single value, that will be used for both the display and "
+        "the submitted value. A list item can also be a tuple consisting "
+        "of two elements. The first element of the tuple should be a string "
+        "that is name of the item that should be displayed. The second "
+        "element of the tuple should be the value that will be submitted. "
+        "If you want to override this property you will therefore have "
+        "to return such a list."),
+
+                                     default=[],
+                                     width=20,
+                                     height=5,
+                                     required=0)
+
+    # NOTE: for ordering reasons (we want extra at the end),
+    # this isn't in the base class property_names list, but
+    # instead will be referred to by the subclasses.
+    extra_item = fields.StringField('extra_item',
+                               title='Extra per item',
+                               description=(
+        "A string containing extra HTML code for attributes. This "
+        "string will be literally included each of the rendered items of the "
+        "field. This property can be useful if you want "
+        "to add a disabled attribute to disable all contained items, for "
+        "instance."),
+                               default="",
+                               required=0)
+
+class SingleItemsWidget(ItemsWidget):
+  """A widget with a number of items that has only a single
+  selectable item.
+  """
+  default = fields.StringField('default',
+                                title='Default',
+                                description=(
+      "The default value of the widget; this should be one of the "
+      "elements in the list of items."),
+                                default="",
+                                required=0)
+
+  first_item = fields.CheckBoxField('first_item',
+                                    title="Select First Item",
+                                    description=(
+      "If checked, the first item will always be selected if "
+      "no initial default value is supplied."),
+                                    default=0)
+
+  def render_items(self, field, key, value, REQUEST, render_prefix=None):
+    # get items
+    cell = getattr(REQUEST, 'cell', None)
+    items = field.get_value('items', REQUEST=REQUEST, cell=cell)
+    if not items:
+      # single item widget should have at least one child in order to produce
+      # valid XHTML; disable it so user can not select it
+      return [self.render_item('', '', '', '', 'disabled="disabled"')]
+
+    # check if we want to select first item
+    if not value and field.get_value('first_item', REQUEST=REQUEST,
+                                    cell=cell) and len(items) > 0:
+      try:
+        text, value = items[0]
+      except ValueError:
+        value = items[0]
+
+    css_class = field.get_value('css_class')
+    extra_item = field.get_value('extra_item')
+
+    # if we run into multiple items with same value, we select the
+    # first one only (for now, may be able to fix this better later)
+    selected_found = 0
+    rendered_items = []
+    for item in items:
+      try:
+        item_text, item_value = item
+      except ValueError:
+        item_text = item
+        item_value = item
+
+      if item_value == value and not selected_found:
+        rendered_item = self.render_selected_item(escape(ustr(item_text)),
+                                                  item_value,
+                                                  key,
+                                                  css_class,
+                                                  extra_item)
+        selected_found = 1
+      else:
+        rendered_item = self.render_item(escape(ustr(item_text)),
+                                          item_value,
+                                          key,
+                                          css_class,
+                                          extra_item)
+
+      rendered_items.append(rendered_item)
+
+    # XXX We want to make sure that we always have the current value in items. -yo
+    if not selected_found and value:
+      value = escape(ustr(value))
+      rendered_item = self.render_selected_item('??? (%s)' % value,
+                                                value,
+                                                key,
+                                                css_class,
+                                                extra_item)
+      rendered_items.append(rendered_item)
+
+    return rendered_items
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    """
+    This method is not as efficient as using a StringField in read only.
+    Always consider to change the field in your Form.
+    """
+    if value is None:
+        return ''
+    title_list = [x[0] for x in field.get_value("items", REQUEST=REQUEST) if x[1]==value]
+    if len(title_list) == 0:
+      return "??? (%s)" % escape(value)
+    else:
+      return title_list[0]
+    return value
+
+  render_pdf = render_view
+
+class MultiItemsWidget(ItemsWidget):
+  """A widget with a number of items that has multiple selectable
+  items.
+  """
+  default = fields.LinesField('default',
+                              title='Default',
+                              description=(
+      "The initial selections of the widget. This is a list of "
+      "zero or more values. If you override this property from Python "
+      "your code should return a Python list."),
+                              width=20, height=3,
+                              default=[],
+                              required=0)
+
+  view_separator = fields.StringField('view_separator',
+                                      title='View separator',
+                                      description=(
+      "When called with render_view, this separator will be used to "
+      "render individual items."),
+                                      width=20,
+                                      default='<br />\n',
+                                      whitespace_preserve=1,
+                                      required=1)
+
+  def render_items(self, field, key, value, REQUEST, render_prefix=None):
+    # list is needed, not a tuple
+    if isinstance(value, tuple):
+      value = list(value)
+    # need to deal with single item selects
+    if not isinstance(value, list):
+      value = [value]
+
+    # XXX -yo
+    selected_found = {}
+
+    items = field.get_value('items', REQUEST=REQUEST, cell=getattr(REQUEST, 'cell', None)) # Added request
+    from Products.ERP5Form.MultiLinkField import MultiLinkFieldWidget
+    if not items and not isinstance(self, MultiLinkFieldWidget):
+      # multi items widget should have at least one child in order to produce
+      # valid XHTML; disable it so user can not select it.
+      # This cannot be applied to MultiLinkFields, which are just some <a>
+      # links
+      return [self.render_item('', '', '', '', 'disabled="disabled"')]
+
+    css_class = field.get_value('css_class')
+    extra_item = field.get_value('extra_item')
+    rendered_items = []
+
+    for item in items:
+      try:
+        item_text, item_value = item
+      except ValueError:
+        item_text = item
+        item_value = item
+
+      if item_value in value:
+        rendered_item = self.render_selected_item(
+            escape(ustr(item_text)),
+            escape(ustr(item_value)),
+            key,
+            css_class,
+            extra_item)
+        # XXX -yo
+        index = value.index(item_value)
+        selected_found[index] = 1
+      else:
+        rendered_item = self.render_item(
+            escape(ustr(item_text)),
+            escape(ustr(item_value)),
+            key,
+            css_class,
+            extra_item)
+      rendered_items.append(rendered_item)
+
+    # XXX We want to make sure that we always have the current value in items. -yo
+    for index in range(len(value)):
+      v = value[index]
+      if index not in selected_found and v:
+        v = escape(v)
+        rendered_item = self.render_selected_item('??? (%s)' % v,
+                                                  v,
+                                                  key,
+                                                  css_class,
+                                                  extra_item)
+        rendered_items.append(rendered_item)
+
+    # Moved marked field to Render
+    # rendered_items.append(render_element('input', type='hidden', name="default_%s:int" % (key, ), value="0"))
+    return rendered_items
+
+  def render_items_view(self, field, value):
+      if type(value) is not type([]):
+          value = [value]
+
+      items = field.get_value('items')
+      d = {}
+      for item in items:
+          try:
+              item_text, item_value = item
+          except ValueError:
+              item_text = item
+              item_value = item
+          d[item_value] = item_text
+      result = []
+      for e in value:
+          result.append(d[e])
+      return result
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+      if value is None:
+          return ''
+      return string.join(self.render_items_view(field, value),
+                          field.get_value('view_separator'))
+
+class ListWidget(SingleItemsWidget):
+    """List widget.
+    """
+    property_names = Widget.property_names +\
+                     ['first_item', 'items', 'size', 'extra', 'extra_item']
+
+    size = fields.IntegerField('size',
+                               title='Size',
+                               description=(
+        "The display size in rows of the field. If set to 1, the "
+        "widget will be displayed as a drop down box by many browsers, "
+        "if set to something higher, a list will be shown. Required."),
+                               default=5,
+                               required=1)
+
+    def render(self, field, key, value, REQUEST, render_prefix=None):
+      rendered_items = self.render_items(field, key, value, REQUEST)
+      input_hidden = render_element('input', type='hidden', 
+                                    name="default_%s:int" % (key, ), value="0") 
+      list_widget = render_element(
+                    'select',
+                    name=key,
+                    css_class=field.get_value('css_class', REQUEST=REQUEST),
+                    size=field.get_value('size', REQUEST=REQUEST),
+                    contents=string.join(rendered_items, "\n"),
+                    extra=field.get_value('extra', REQUEST=REQUEST))
+
+      return "\n".join([list_widget, input_hidden])
+
+    def render_item(self, text, value, key, css_class, extra_item):
+        return render_element('option', contents=text, value=value, 
+                              extra=extra_item)
+
+    def render_selected_item(self, text, value, key, css_class, extra_item):
+        return render_element('option', contents=text, value=value,
+                              selected=None, extra=extra_item)
+
+ListWidgetInstance = ListWidget()
+
+class MultiListWidget(MultiItemsWidget):
+    """List widget with multiple select.
+    """
+    property_names = Widget.property_names +\
+                     ['items', 'size', 'view_separator', 'extra', 'extra_item']
+    
+    size = fields.IntegerField('size',
+                               title='Size',
+                               description=(
+        "The display size in rows of the field. If set to 1, the "
+        "widget will be displayed as a drop down box by many browsers, "
+        "if set to something higher, a list will be shown. Required."),
+                               default=5,
+                               required=1)
+
+    def render(self, field, key, value, REQUEST, render_prefix=None):
+      rendered_items = self.render_items(field, key, value, REQUEST)
+      input_hidden = render_element('input', type='hidden', name="default_%s:int" % (key, ), value="0")
+      multi_list = render_element(
+                    'select',
+                    name=key,
+                    multiple=None,
+                    css_class=field.get_value('css_class', REQUEST=REQUEST),
+                    size=field.get_value('size', REQUEST=REQUEST),
+                    contents=string.join(rendered_items, "\n"),
+                    extra=field.get_value('extra', REQUEST=REQUEST))
+
+      return "\n".join([multi_list,input_hidden])
+
+    def render_item(self, text, value, key, css_class, extra_item):
+        return render_element('option', contents=text, value=value, 
+                              extra=extra_item)
+
+    def render_selected_item(self, text, value, key, css_class, extra_item):
+        return render_element('option', contents=text, value=value,
+                              selected=None, extra=extra_item)
+    
+MultiListWidgetInstance = MultiListWidget()
+
+class RadioWidget(SingleItemsWidget):
+  """radio buttons widget.
+  """
+  property_names = Widget.property_names +\
+                    ['first_item', 'items', 'orientation', 'extra_item']
+  
+  orientation = fields.ListField('orientation',
+                                  title='Orientation',
+                                  description=(
+      "Orientation of the radio buttons. The radio buttons will "
+      "be drawn either vertically or horizontally."),
+                                  default="vertical",
+                                  required=1,
+                                  size=1,
+                                  items=[('Vertical', 'vertical'),
+                                        ('Horizontal', 'horizontal')])
+                                  
+  def render(self, field, key, value, REQUEST, render_prefix=None):
+    input_hidden = render_element('input', type='hidden',
+                                  name="default_%s" % (key, ), value="")
+    rendered_items = self.render_items(field, key, value, REQUEST)
+    rendered_items.append(input_hidden)
+    orientation = field.get_value('orientation')
+    if orientation == 'horizontal':
+      return string.join(rendered_items, "&nbsp;&nbsp;")
+    else:
+      return string.join(rendered_items, "<br />")
+
+  def render_item(self, text, value, key, css_class, extra_item):
+    return render_element('input',
+                          type="radio",
+                          css_class=css_class,
+                          name=key,
+                          value=value,
+                          extra=extra_item) + text
+
+  def render_selected_item(self, text, value, key, css_class, extra_item):
+    return render_element('input',
+                          type="radio",
+                          css_class=css_class,
+                          name=key,
+                          value=value,
+                          checked=None,
+                          extra=extra_item) + text
+
+RadioWidgetInstance = RadioWidget()
+
+class MultiCheckBoxWidget(MultiItemsWidget):
+    """multiple checkbox widget.
+    """
+    property_names = Widget.property_names +\
+                     ['items', 'orientation', 'view_separator', 'extra_item']
+
+    orientation = fields.ListField('orientation',
+                                   title='Orientation',
+                                   description=(
+        "Orientation of the check boxes. The check boxes will "
+        "be drawn either vertically or horizontally."),
+                                   default="vertical",
+                                   required=1,
+                                   size=1,
+                                   items=[('Vertical', 'vertical'),
+                                          ('Horizontal', 'horizontal')])
+
+    def render(self, field, key, value, REQUEST, render_prefix=None):
+      rendered_items = self.render_items(field, key, value, REQUEST)
+      rendered_items.append(render_element('input', type='hidden', name="default_%s:int" % (key, ), value="0"))
+      orientation = field.get_value('orientation')
+      if orientation == 'horizontal':
+        return string.join(rendered_items, "&nbsp;&nbsp;")
+      else:
+        return string.join(rendered_items, "<br />")
+
+    def render_item(self, text, value, key, css_class, extra_item):
+        return render_element('input',
+                              type="checkbox",
+                              css_class=css_class,
+                              name=key,
+                              value=value,
+                              extra=extra_item) + text
+    
+    def render_selected_item(self, text, value, key, css_class, extra_item):
+        return render_element('input',
+                              type="checkbox",
+                              css_class=css_class,
+                              name=key,
+                              value=value,
+                              checked=None,
+                              extra=extra_item) + text
+
+MultiCheckBoxWidgetInstance = MultiCheckBoxWidget()
+
+class DateTimeWidget(Widget):
+  """
+    Added support for key in every call to render_sub_field
+  """
+
+  sql_format_year  = '%Y'
+  sql_format_month = '%m'
+  sql_format_day   = '%d'
+  format_to_sql_format_dict = {'dmy': (sql_format_day  , sql_format_month, sql_format_year),
+                                'ymd': (sql_format_year , sql_format_month, sql_format_day ),
+                                'mdy': (sql_format_month, sql_format_day  , sql_format_year),
+                                'my' : (sql_format_month, sql_format_year ),
+                                'ym' : (sql_format_year , sql_format_month)
+                              }
+  sql_format_default = format_to_sql_format_dict['ymd']
+
+  hide_day = fields.CheckBoxField('hide_day',
+                                title="Hide Day",
+                                description=(
+      "The day will be hidden on the output. Instead the default"
+      "Day will be taken"),
+                                default=0)
+
+  hidden_day_is_last_day = fields.CheckBoxField('hidden_day_is_last_day',
+                                title="Hidden Day is last day of the Month",
+                                description=(
+      "Defines wether hidden day means, you want the last day of the month"
+      "Else it will be the first day"),
+                                default=0)
+
+  timezone_style = fields.CheckBoxField('timezone_style',
+                                    title="Display timezone",
+                                    description=("Display timezone"),
+                                    default=0)
+
+  default = fields.DateTimeField('default',
+                                   title="Default",
+                                   description=("The default datetime."),
+                                   default=None,
+                                   display_style="text",
+                                   display_order="ymd",
+                                   input_style="text",
+                                   required=0)
+
+  default_now = fields.CheckBoxField('default_now',
+                                      title="Default to now",
+                                      description=(
+      "Default date and time will be the date and time at showing of "
+      "the form (if the default is left empty)."),
+                                      default=0)
+
+  date_separator = fields.StringField('date_separator',
+                                      title='Date separator',
+                                      description=(
+      "Separator to appear between year, month, day."),
+                                      default="/",
+                                      required=0,
+                                      display_width=2,
+                                      display_maxwith=2,
+                                      max_length=2)
+
+  time_separator = fields.StringField('time_separator',
+                                      title='Time separator',
+                                      description=(
+      "Separator to appear between hour and minutes."),
+                                      default=":",
+                                      required=0,
+                                      display_width=2,
+                                      display_maxwith=2,
+                                      max_length=2)
+
+  input_style = fields.ListField('input_style',
+                                  title="Input style",
+                                  description=(
+      "The type of input used. 'text' will show the date part "
+      "as text, while 'list' will use dropdown lists instead."),
+                                  default="text",
+                                  items=[("text", "text"),
+                                        ("list", "list")],
+                                  size=1)
+
+  input_order = fields.ListField('input_order',
+                                  title="Input order",
+                                  description=(
+      "The order in which date input should take place. Either "
+      "year/month/day, day/month/year or month/day/year."),
+                                  default="ymd",
+                                  items=[("year/month/day", "ymd"),
+                                        ("day/month/year", "dmy"),
+                                        ("month/day/year", "mdy")],
+                                  required=1,
+                                  size=1)
+
+  date_only = fields.CheckBoxField('date_only',
+                                    title="Display date only",
+                                    description=(
+      "Display the date only, not the time."),
+                                    default=0)
+
+  ampm_time_style = fields.CheckBoxField('ampm_time_style',
+                                           title="AM/PM time style",
+                                           description=(
+        "Display time in am/pm format."),
+                                           default=0)
+
+  property_names = Widget.property_names +\
+                    ['default_now', 'date_separator', 'time_separator',
+                     'input_style', 'input_order', 'date_only',
+                     'ampm_time_style', 'timezone_style', 'hide_day',
+                     'hidden_day_is_last_day']
+
+  def getInputOrder(self, field):
+    input_order = field.get_value('input_order')
+    if field.get_value('hide_day'):
+      if input_order == 'ymd':
+        input_order = 'ym'
+      elif input_order in ('dmy', 'mdy'):
+        input_order = 'my'
+    return input_order
+
+  def render_dict(self, field, value, render_prefix=None):
+    """
+      This is yet another field rendering. It is designed to allow code to
+      understand field's value data by providing its type and format when
+      applicable.
+
+      It returns a dict with 3 keys:
+        type  : Text representation of value's type.
+        format: Type-dependant-formated formating information.
+                This only describes the field format settings, not the actual
+                format of provided value.
+        query : Passthrough of given value.
+    """
+    format_dict = self.format_to_sql_format_dict
+    input_order = format_dict.get(self.getInputOrder(field),
+                                  self.sql_format_default)
+    if isinstance(value, unicode):
+      value = value.encode(field.get_form_encoding())
+    return {'query': value,
+            'format': field.get_value('date_separator').join(input_order),
+            'type': 'date'}
+
+  def render(self, field, key, value, REQUEST, render_prefix=None):
+    use_ampm = field.get_value('ampm_time_style')
+    use_timezone = field.get_value('timezone_style')
+    # FIXME: backwards compatibility hack:
+    if not hasattr(field, 'sub_form'):
+      field.sub_form = create_datetime_text_sub_form()
+        
+    # Is it still usefull to test the None value,
+    # as DateTimeField should be considerer as the other field
+    # and get an empty string as default value?
+    # XXX hasattr(REQUEST, 'form') seems useless, 
+    # because REQUEST always has a form property
+    if (value in (None, '')) and (field.get_value('default_now')) and \
+        ((REQUEST is None) or (not hasattr(REQUEST, 'form')) or \
+        (not REQUEST.form.has_key('subfield_%s_%s' % (key, 'year')))):
+      value = DateTime()
+    year   = None
+    month  = None
+    day    = None
+    hour   = None
+    minute = None
+    ampm   = None
+    timezone = None
+    if isinstance(value, DateTime):
+      year = "%04d" % value.year()
+      month = "%02d" % value.month()
+      day = "%02d" % value.day()
+      if use_ampm:
+          hour = "%02d" % value.h_12()
+      else:
+          hour = "%02d" % value.hour()
+      minute = "%02d" % value.minute()
+      ampm = value.ampm()
+      timezone = value.timezone()
+    input_order = self.getInputOrder(field)
+    if input_order == 'ymd':
+      order = [('year', year),
+                ('month', month),
+                ('day', day)]
+    elif input_order == 'dmy':
+      order = [('day', day),
+                ('month', month),
+                ('year', year)]
+    elif input_order == 'mdy':
+      order = [('month', month),
+                ('day', day),
+                ('year', year)]
+    elif input_order == 'my':
+      order = [('month', month),
+                ('year', year)]
+    elif input_order == 'ym':
+      order = [('year', year),
+                ('month', month)]
+    else:
+      order = [('year', year),
+                ('month', month),
+                ('day', day)]
+    result = []
+    for sub_field_name, sub_field_value in order:
+      result.append(field.render_sub_field(sub_field_name,
+                                            sub_field_value, REQUEST, key=key))
+    date_result = string.join(result, field.get_value('date_separator'))
+    if not field.get_value('date_only'):
+      time_result = (field.render_sub_field('hour', hour, REQUEST, key=key) +
+                      field.get_value('time_separator') +
+                      field.render_sub_field('minute', minute, REQUEST, key=key))
+
+      if use_ampm:
+        time_result += '&nbsp;' + field.render_sub_field('ampm',
+                                                      ampm, REQUEST, key=key)
+      if use_timezone:
+        time_result += '&nbsp;' + field.render_sub_field('timezone',
+                                                      timezone, REQUEST, key=key)
+      return date_result + '&nbsp;&nbsp;&nbsp;' + time_result
+    else:
+      return date_result
+
+  def format_value(self, field, value, mode='html'):
+    # Is it still usefull to test the None value,
+    # as DateTimeField should be considerer as the other field
+    # and get an empty string as default value?
+    if value in (None, ''):
+      return ''
+
+    use_ampm = field.get_value('ampm_time_style')
+    use_timezone = field.get_value('timezone_style')
+
+    year = "%04d" % value.year()
+    month = "%02d" % value.month()
+    day = "%02d" % value.day()
+    if use_ampm:
+      hour = "%02d" % value.h_12()
+    else:
+      hour = "%02d" % value.hour()
+    minute = "%02d" % value.minute()
+    ampm = value.ampm()
+    timezone = value.timezone()
+
+    order = self.getInputOrder(field)
+    if order == 'ymd':
+      output = [year, month, day]
+    elif order == 'dmy':
+      output = [day, month, year]
+    elif order == 'mdy':
+      output = [month, day, year]
+    elif order == 'my':
+      output = [month, year]
+    elif order == 'ym':
+      output = [year, month]
+    else:
+      output = [year, month, day]
+    date_result = string.join(output, field.get_value('date_separator'))
+
+    if mode in ('html', ):
+      space = '&nbsp;'
+    else:
+      space = ' '
+
+    if not field.get_value('date_only'):
+      time_result = hour + field.get_value('time_separator') + minute
+      if use_ampm:
+          time_result += space + ampm
+      if use_timezone:
+          time_result += space + timezone
+      return date_result + (space * 3) + time_result
+    else:
+      return date_result
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    return self.format_value(field, value, mode='html')
+
+  def render_pdf(self, field, value, render_prefix=None):
+    return self.format_value(field, value, mode='pdf')
+
+DateTimeWidgetInstance = DateTimeWidget()
+
+class LabelWidget(Widget):
+    """Widget that is a label only. It simply returns its default value.
+    """
+    property_names = ['title', 'description',
+                      'default', 'css_class', 'hidden', 'extra']
+
+    default = fields.TextAreaField(
+        'default',
+        title="Label text",
+        description="Label text to render",
+        default="",
+        width=20, height=3,
+        required=0)
+
+    def render(self, field, value, REQUEST=None, render_prefix=None):
+        return render_element("div",
+                              css_class=field.get_value('css_class'),
+                              contents=field.get_value('default'))
+
+    # XXX should render view return the same information as render?
+    def render_view(self, field, value, REQUEST=None, render_prefix=None):
+        return field.get_value('default')
+
+LabelWidgetInstance = LabelWidget()
+
+def render_tag(tag, **kw):
+  """Render the tag. Well, not all of it, as we may want to / it.
+  """
+  attr_list = []
+
+  # special case handling for css_class
+  if kw.has_key('css_class'):
+    if kw['css_class'] != "":
+      attr_list.append('class="%s"' % kw['css_class'])
+    del kw['css_class']
+
+  # special case handling for extra 'raw' code
+  if kw.has_key('extra'):
+    extra = kw['extra'] # could be empty string but we don't care
+    del kw['extra']
+  else:
+    extra = ""
+
+  # handle other attributes
+  for key, value in kw.items():
+    if value == None:
+        value = key
+    attr_list.append('%s="%s"' % (key, html_quote(value)))
+
+  attr_str = string.join(attr_list, " ")
+  return "<%s %s %s" % (tag, attr_str, extra)
+
+def render_element(tag, **kw):
+  if kw.has_key('contents'):
+    contents = kw['contents']
+    del kw['contents']
+    return "%s>%s</%s>" % (apply(render_tag, (tag, ), kw), contents, tag)
+  else:
+    return apply(render_tag, (tag, ), kw) + " />"
+
+
+##############################################################################
+#
+# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
+#                    Jean Paul Smets <jp@nexedi.com>
+#                    Jerome Perrin <jerome@nexedi.com>
+#                    Yoshinori Okuji <yo@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+
+class IntegerWidget(TextWidget) :
+  def render(self, field, key, value, REQUEST, render_prefix=None) :
+    """Render an editable integer.
+    """
+    if isinstance(value, float):
+      value = int(value)
+    display_maxwidth = field.get_value('display_maxwidth') or 0
+    if display_maxwidth > 0:
+      return render_element("input",
+                            type="text",
+                            name=key,
+                            css_class=field.get_value('css_class'),
+                            value=value,
+                            size=field.get_value('display_width'),
+                            maxlength=display_maxwidth,
+                            extra=field.get_value('extra'))
+    else:
+      return render_element("input",
+                            type="text",
+                            name=key,
+                            css_class=field.get_value('css_class'),
+                            value=value,
+                            size=field.get_value('display_width'),
+                            extra=field.get_value('extra'))
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    """Render a non-editable interger."""
+    if isinstance(value, float):
+      value = int(value)
+    return TextWidget.render_view(self, field, value, REQUEST=REQUEST)
+
+IntegerWidgetInstance = IntegerWidget()
+class FloatWidget(TextWidget):
+
+  property_names = TextWidget.property_names +\
+                    ['input_style','precision']
+
+  input_style = fields.ListField('input_style',
+                                  title="Input style",
+                                  description=(
+      "The type of float we should enter. "),
+                                  default="-1234.5",
+                                  items=[("-1234.5",  "-1234.5"),
+                                        ("-1 234.5", "-1 234.5"),
+                                        ("-12.3%", "-12.3%"),],
+                                  required=1,
+                                  size=1)
+
+  precision = fields.IntegerField('precision',
+                                      title='Precision',
+                                      description=(
+      "Number of digits after the decimal point"),
+                                      default=None,
+                                      required=0)
+
+  def format_value(self, field, value):
+    """Formats the value as requested"""
+    if value not in (None,''):
+      precision = field.get_value('precision')
+      input_style = field.get_value('input_style')
+      percent = 0
+      original_value = value
+      if input_style.find('%')>=0:
+        percent=1
+        try:
+          value = float(value) * 100
+        except ValueError:
+          return value
+      try :
+        float_value = float(value)
+        if precision not in (None, ''):
+          float_value = round(float_value, precision)
+        value = str(float_value)
+      except ValueError:
+        return value
+      else:
+        if 'e' in value:
+          # %f will not use exponential format
+          value = '%f' % float(original_value)
+      value_list = value.split('.')
+      integer = value_list[0]
+      if input_style.find(' ')>=0:
+        integer = value_list[0]
+        i = len(integer)%3
+        value = integer[:i]
+        while i != len(integer):
+          value += ' ' + integer[i:i+3]
+          i += 3
+      else:
+        value = value_list[0]
+      if precision != 0:
+        value += '.'
+      if precision not in (None,''):
+        for i in range(0,precision):
+          if i < len(value_list[1]):
+            value += value_list[1][i]
+          else:
+            value += '0'
+      else:
+        value += value_list[1]
+      if percent:
+        value += '%'
+      return value.strip()
+    return ''
+
+  def render(self, field, key, value, REQUEST, render_prefix=None):
+    """Render Float input field
+    """
+    value = self.format_value(field, value)
+    display_maxwidth = field.get_value('display_maxwidth') or 0
+    extra_keys = {}
+    if display_maxwidth > 0:
+      extra_keys['maxlength'] = display_maxwidth
+    return render_element( "input",
+                            type="text",
+                            name=key,
+                            css_class=field.get_value('css_class'),
+                            value=value,
+                            size=field.get_value('display_width'),
+                            extra=field.get_value('extra'),
+                            **extra_keys)
+
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    """
+      Render Float display field.
+      This patch add:
+        * replacement of spaces by unbreakable spaces if the content is float-like
+        * support of extra CSS class when render as pure text
+    """
+    value = self.format_value(field, value)
+
+    float_value = None
+    try:
+      float_value = float(value.replace(' ', ''))
+    except:
+      pass
+    if float_value != None:
+      value = value.replace(' ', '&nbsp;')
+
+    extra = field.get_value('extra')
+    if extra not in (None, ''):
+      value = "<div %s>%s</div>" % (extra, value)
+
+    css_class = field.get_value('css_class')
+    if css_class not in ('', None):
+      return "<span class='%s'>%s</span>" % (css_class, value)
+    return value
+
+  def render_pdf(self, field, value, render_prefix=None):
+    """Render the field as PDF."""
+    return self.format_value(field, value)
+
+  def render_dict(self, field, value, render_prefix=None):
+    """
+      This is yet another field rendering. It is designed to allow code to
+      understand field's value data by providing its type and format when
+      applicable.
+
+      It returns a dict with 3 keys:
+        type  : Text representation of value's type.
+        format: Type-dependant-formated formating information.
+                This only describes the field format settings, not the actual
+                format of provided value.
+        query : Passthrough of given value.
+    """
+    input_style = field.get_value('input_style')
+    precision = field.get_value('precision')      
+    if precision not in (None, '') and precision != 0:
+      for x in xrange(1, precision):
+        input_style += '5'
+    else:
+      input_style = input_style.split('.')[0]
+    if isinstance(value, unicode):
+      value = value.encode(field.get_form_encoding())
+    return {'query': value,
+            'format': input_style,
+            'type': 'float'}
+
+FloatWidgetInstance = FloatWidget()
+
+class LinkWidget(TextWidget):
+  def render_view(self, field, value, REQUEST=None, render_prefix=None):
+    """Render link.
+    """
+    link_type = field.get_value('link_type', REQUEST=REQUEST)
+    if REQUEST is None:
+      REQUEST = get_request()
+
+    if link_type == 'internal':
+      value = urljoin(REQUEST['BASE0'], value)
+    elif link_type == 'relative':
+      value = urljoin(REQUEST['URL1'], value)
+
+    return '<a href="%s">%s</a>' % (value,
+        field.get_value('title', cell=getattr(REQUEST,'cell',None)))
+
+LinkWidgetInstance = LinkWidget()
+
diff --git a/product/Formulator/XMLObjects.py b/product/Formulator/XMLObjects.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8f1185a62785a037d1c176f94d9aa1b6409dc9e
--- /dev/null
+++ b/product/Formulator/XMLObjects.py
@@ -0,0 +1,96 @@
+from xml.dom.minidom import parse, parseString, Node
+
+# an extremely simple system for loading in XML into objects
+
+class Object:
+    pass
+
+class XMLObject:
+    def __init__(self):
+        self.elements = Object()
+        self.first = Object()
+        self.attributes = {}
+        self.text = ''
+        
+    def getElementNames(self):
+        return [element for element in dir(self.elements)
+                if not element.startswith('__')]
+
+    def getAttributes(self):
+        return self.attributes
+    
+def elementToObject(parent, node):
+    # create an object to represent element node
+    object = XMLObject()
+    # make object attributes off node attributes
+    for key, value in node.attributes.items():
+        object.attributes[key] = value
+    # make lists of child elements (or ignore them)
+    for child in node.childNodes:
+        nodeToObject(object, child)
+    # add ourselves to parent node
+    name = str(node.nodeName)
+    l = getattr(parent.elements, name, [])
+    l.append(object)
+    setattr(parent.elements, name, l)
+    
+def attributeToObject(parent, node):
+    # should never be called
+    pass
+
+def textToObject(parent, node):
+    # add this text to parents text content
+    parent.text += node.data
+    
+def processingInstructionToObject(parent, node):
+    # don't do anything with these
+    pass
+
+def commentToObject(parent, node):
+    # don't do anything with these
+    pass
+
+def documentToObject(parent, node):
+    elementToObject(parent, node.documentElement)
+
+def documentTypeToObject(parent, node):
+    # don't do anything with these
+    pass
+
+_map = {
+    Node.ELEMENT_NODE: elementToObject,
+    Node.ATTRIBUTE_NODE: attributeToObject,
+    Node.TEXT_NODE: textToObject,
+ #   Node.CDATA_SECTION_NODE: 
+ #   Node.ENTITY_NODE:
+    Node.PROCESSING_INSTRUCTION_NODE: processingInstructionToObject,
+    Node.COMMENT_NODE: commentToObject,
+    Node.DOCUMENT_NODE: documentToObject,
+    Node.DOCUMENT_TYPE_NODE: documentTypeToObject,
+#    Node.NOTATION_NODE:
+    }
+
+def nodeToObject(parent, node):
+    _map[node.nodeType](parent, node)
+
+def simplify_single_entries(object):
+    for name in object.getElementNames():
+        l = getattr(object.elements, name)
+        # set the first subelement (in case it's just one, this is easy)
+        setattr(object.first, name, l[0])
+        # now do the same for rest
+        for element in l:
+            simplify_single_entries(element)
+  
+def XMLToObjectsFromFile(path):
+    return XMLToObjects(parse(path))
+
+def XMLToObjectsFromString(s):
+    return XMLToObjects(parseString(s))
+
+def XMLToObjects(document):
+    object = XMLObject()
+    documentToObject(object, document)
+    document.unlink()
+    simplify_single_entries(object)
+    return object
diff --git a/product/Formulator/XMLToForm.py b/product/Formulator/XMLToForm.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2539e555988f9b5a3820cbce34b876954d422a2
--- /dev/null
+++ b/product/Formulator/XMLToForm.py
@@ -0,0 +1,127 @@
+import XMLObjects
+from Products.Formulator.TALESField import TALESMethod
+from Products.Formulator.MethodField import Method
+
+def XMLToForm(s, form, override_encoding=None):
+    """Takes an xml string and changes formulator form accordingly.
+    Heavily inspired by code from Nikolay Kim.
+
+    If override_encoding is set, form data is read assuming given
+    encoding instead of the one in the XML data itself. The form will
+    have to be modified afterwards to this stored_encoding itself.
+    """
+    top = XMLObjects.XMLToObjectsFromString(s)
+    # wipe out groups
+    form.groups = {'Default':[]}
+    form.group_list = ['Default']
+
+    if override_encoding is None:
+        try:
+            unicode_mode = top.first.form.first.unicode_mode.text
+        except AttributeError:
+            unicode_mode = 'false'
+        # retrieve encoding information from XML
+        if unicode_mode == 'true':
+            # just use unicode strings being read in
+            encoding = None
+        else:
+            # store strings as specified encoding
+            try:
+                encoding = top.first.form.first.stored_encoding.text
+            except AttributeError:
+                encoding = 'ISO-8859-1'
+    else:
+        if override_encoding == 'unicode':
+            encoding = None
+        else:
+            encoding = override_encoding
+        
+    #  get the settings
+    settings = [field.id for field in form.settings_form.get_fields()]    
+    for setting in settings:
+        value = getattr(top.first.form.first, setting, None)
+        if value is None:
+            continue
+        if setting == 'unicode_mode':
+            v = value.text == 'true'
+        elif setting == 'row_length':
+            v = int(value.text)
+        else:
+            v = encode(value.text, encoding)
+        setattr(form, setting, v) 
+
+    # create groups
+    has_default = 0
+    for group in top.first.form.first.groups.elements.group:
+        # get group title and create group
+        group_title = encode(group.first.title.text, encoding)
+        if group_title == 'Default':
+            has_default = 1
+        form.add_group(group_title)
+        # create fields in group
+        if not hasattr(group.first.fields.elements, 'field'):
+            # empty <fields> element
+            continue
+        for entry in group.first.fields.elements.field:
+            id = str(encode(entry.first.id.text, encoding))
+            meta_type = encode(entry.first.type.text, encoding)
+            try:
+                form._delObject(id)
+            except (KeyError, AttributeError):
+                pass
+            form.manage_addField(id, '', meta_type)
+            field = form._getOb(id)
+            if group_title != 'Default':
+                form.move_field_group([id], 'Default', group_title)
+            # set values
+            values = entry.first.values
+            for name in values.getElementNames():
+                value = getattr(values.first, name)
+                if value.attributes.get('type') == 'float':
+                    field.values[name] = float(value.text)
+                elif value.attributes.get('type') == 'int':
+                    field.values[name] = int(value.text)
+                elif value.attributes.get('type') == 'method': # XXX Patch
+                    field.values[name] = Method(value.text) # XXX Patch
+                elif value.attributes.get('type') == 'list':
+                    # XXX bare eval here (this may be a security leak ?)
+                    field.values[name] = eval(
+                        encode(value.text, encoding))
+                else:
+                    field.values[name] = encode(value.text, encoding)
+
+            # special hack for the DateTimeField
+            if field.meta_type=='DateTimeField':
+                field.on_value_input_style_changed(
+                    field.get_value('input_style'))
+
+            # set tales
+            tales = entry.first.tales
+            for name in tales.getElementNames():
+                field.tales[name] = TALESMethod(
+                    encode(getattr(tales.first, name).text, encoding))
+
+            # set messages
+            if hasattr(entry.first, 'messages'):
+                messages = entry.first.messages
+                entries = getattr(messages.elements, 'message', [])
+                for entry in entries:
+                    name = entry.attributes.get('name')
+                    text = encode(entry.text, encoding)
+                    field.message_values[name] = text
+
+            # for persistence machinery
+            field.values = field.values
+            field.tales = field.tales
+            field.message_values = field.message_values
+        
+    # delete default group
+    if not has_default:
+        form.move_group_down('Default')
+        form.remove_group('Default')
+    
+def encode(text, encoding):
+    if encoding is None:
+        return text
+    else:
+        return text.encode(encoding)
diff --git a/product/Formulator/__init__.py b/product/Formulator/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..151fbf1bc153d2f73f995d8855e3378ebab841ea
--- /dev/null
+++ b/product/Formulator/__init__.py
@@ -0,0 +1,90 @@
+from Globals import DTMLFile
+import Form
+import StandardFields, HelperFields
+from FieldRegistry import FieldRegistry
+import Errors
+from Products.PythonScripts.Utility import allow_module
+
+try:
+    import Products.FileSystemSite
+except ImportError:
+    try:
+        import Products.CMFCore
+    except ImportError:
+        pass
+    else:
+        import FSForm
+else:
+    import FSForm
+
+# Allow Errors to be imported TTW
+allow_module('Products.Formulator.Errors')
+
+def initialize(context):
+    """Initialize the Formulator product.
+    """
+    # register field classes
+    FieldRegistry.registerField(StandardFields.StringField,
+                                'www/StringField.gif')
+    FieldRegistry.registerField(StandardFields.CheckBoxField,
+                                'www/CheckBoxField.gif')
+    FieldRegistry.registerField(StandardFields.IntegerField,
+                                'www/IntegerField.gif')
+    FieldRegistry.registerField(StandardFields.TextAreaField,
+                                'www/TextAreaField.gif')
+    FieldRegistry.registerField(StandardFields.RawTextAreaField,
+                                'www/TextAreaField.gif')
+    FieldRegistry.registerField(StandardFields.LinesField,
+                                'www/LinesField.gif')
+    FieldRegistry.registerField(StandardFields.ListField,
+                                'www/ListField.gif')
+    FieldRegistry.registerField(StandardFields.MultiListField,
+                                'www/MultiListField.gif')
+    FieldRegistry.registerField(StandardFields.RadioField,
+                                'www/RadioField.gif')
+    FieldRegistry.registerField(StandardFields.MultiCheckBoxField,
+                                'www/MultiCheckBoxField.gif')
+    FieldRegistry.registerField(StandardFields.PasswordField,
+                                'www/PasswordField.gif')
+    FieldRegistry.registerField(StandardFields.EmailField,
+                                'www/EmailField.gif')
+    FieldRegistry.registerField(StandardFields.PatternField,
+                                'www/PatternField.gif')
+    FieldRegistry.registerField(StandardFields.FloatField,
+                                'www/FloatField.gif')
+    FieldRegistry.registerField(StandardFields.DateTimeField,
+                                'www/DateTimeField.gif')
+    FieldRegistry.registerField(StandardFields.FileField,
+                                'www/FileField.gif')
+    FieldRegistry.registerField(StandardFields.LinkField,
+                                'www/LinkField.gif')
+    FieldRegistry.registerField(StandardFields.LabelField,
+                                'www/LabelField.gif')
+    
+    # some helper fields
+    FieldRegistry.registerField(HelperFields.ListTextAreaField)
+    FieldRegistry.registerField(HelperFields.MethodField)
+    FieldRegistry.registerField(HelperFields.TALESField)
+    
+    # obsolete field (same as helper; useable but not addable)
+    FieldRegistry.registerField(StandardFields.RangedIntegerField,
+                                'www/RangedIntegerField.gif')
+    
+    # register help for the product
+    context.registerHelp()
+    # register field help for all fields
+    FieldRegistry.registerFieldHelp(context)
+    
+    # register the form itself
+    context.registerClass(
+        Form.ZMIForm,
+        constructors = (Form.manage_addForm,
+                        Form.manage_add),
+        icon = 'www/Form.gif')
+
+    # make Dummy Fields into real fields
+    FieldRegistry.initializeFields()
+    
+    # do initialization of Form class to make fields addable
+    Form.initializeForm(FieldRegistry)
+
diff --git a/product/Formulator/dtml/FieldHelpTopic.dtml b/product/Formulator/dtml/FieldHelpTopic.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..b9e4590d8ac3a41b63c4b18903bd996ce32362b9
--- /dev/null
+++ b/product/Formulator/dtml/FieldHelpTopic.dtml
@@ -0,0 +1,21 @@
+<dtml-var standard_html_header>
+<h3>Formulator Field - <dtml-var id></h3>
+<dtml-in get_groups>
+<dtml-let group=sequence-item fields="get_fields_in_group(group)">
+  <dtml-if fields>
+    <h4><i><dtml-var "_.string.capitalize(group)"> properties</i></h4>
+  <dtml-in fields>
+  <dtml-let field=sequence-item>
+   <b><dtml-var "field.get_value('title')"> (<dtml-var "field.id">)</b>
+   <p><dtml-var "field.get_value('description')"></p>
+  </dtml-let>
+  </dtml-in>
+  </dtml-if>
+</dtml-let>
+</dtml-in>
+
+<h4>More help</h4>
+
+<p><a href="fieldEdit.txt">Field edit screen help</a></p>
+
+<dtml-var standard_html_footer>
diff --git a/product/Formulator/dtml/fieldAdd.dtml b/product/Formulator/dtml/fieldAdd.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..ccc135d37429e34e17f14f0fe18ef73d12d2a554
--- /dev/null
+++ b/product/Formulator/dtml/fieldAdd.dtml
@@ -0,0 +1,52 @@
+<dtml-var manage_page_header>
+
+<dtml-var "manage_form_title(this(), _,
+           form_title='Add %s' % fieldname,
+           )">
+
+<p class="form-help">
+Add a <dtml-var fieldname> to the form.
+</p>
+
+<form action="manage_addField" method="POST">
+
+<table cellspacing="0" cellpadding="2" border="0">
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Id
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="id" size="40" />
+    </td>
+  </tr>
+
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Title
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="title" size="40" />
+    </td>
+  </tr>
+
+  <input type="hidden" name="fieldname" value="&dtml-fieldname;">
+  <tr>
+    <td align="left" valign="top">
+    </td>
+    <td align="left" valign="top">
+    <div class="form-element">
+    <input class="form-element" type="submit" name="submit_add" 
+     value=" Add " /> 
+    <input class="form-element" type="submit" name="submit_add_and_edit" 
+     value=" Add and Edit " />
+    </div>
+    </td>
+  </tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>
diff --git a/product/Formulator/dtml/fieldEdit.dtml b/product/Formulator/dtml/fieldEdit.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..bae25d5f3412c5a5da79f95e04b6ecf32debc133
--- /dev/null
+++ b/product/Formulator/dtml/fieldEdit.dtml
@@ -0,0 +1,65 @@
+<dtml-var manage_page_header>
+<dtml-let help_product="'Formulator'" help_topic=meta_type>
+<dtml-var manage_tabs>
+</dtml-let>
+
+<p class="form-help">
+Edit <dtml-var meta_type> properties here.
+</p>
+
+<form action="manage_edit" method="POST">
+<table cellspacing="0" cellpadding="2" border="0">
+
+<dtml-in "form.get_groups()">
+<dtml-let group=sequence-item fields="form.get_fields_in_group(group)">
+
+<dtml-if fields>
+<tr>
+<td colspan="3" class="form-title">
+  <dtml-var "_.string.capitalize(group)"> properties
+</td>
+</tr>
+
+<dtml-var fieldListHeader>
+
+<dtml-let current_field="this()">
+<dtml-in fields>
+<dtml-let field=sequence-item field_id="field.id"
+          value="current_field.get_orig_value(field_id)"
+          override="current_field.get_override(field_id)"
+          tales="current_field.get_tales(field_id)">
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    <dtml-if "tales or override">[</dtml-if><dtml-var "field.title()"><dtml-if "field.has_value('required') and field.get_value('required')">*</dtml-if><dtml-if "tales or override">]</dtml-if>
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <dtml-var "field.render(value)">
+    </td>
+    <td><div class="form-element">
+    <dtml-var "field.meta_type">
+    </div></td>
+  </tr>
+</dtml-let>
+</dtml-in>
+</dtml-let>
+</dtml-if>
+</dtml-let>
+</dtml-in>
+
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-element">
+    <input class="form-element" type="submit" name="submit" 
+     value="Save Changes" /> 
+    </div>
+    </td>
+  </tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>
+
+
+
diff --git a/product/Formulator/dtml/fieldListHeader.dtml b/product/Formulator/dtml/fieldListHeader.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..e30fef5dfeb2ec2ecbf5d31116a82da0f91621dc
--- /dev/null
+++ b/product/Formulator/dtml/fieldListHeader.dtml
@@ -0,0 +1,17 @@
+<tr class="list-header">
+  <td align="left" valign="top">
+  <div class="form-label">
+  Name
+  </div>
+  </td>
+  <td align="left" valign="top">
+  <div class="form-label">
+  Value
+  </div>
+  </td>
+  <td align="left" valign="top">
+  <div class="form-label">
+  Field
+  </div>
+  </td>
+</tr>
\ No newline at end of file
diff --git a/product/Formulator/dtml/fieldMessages.dtml b/product/Formulator/dtml/fieldMessages.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..c70b659273d375b3bd536ad5f410094412b458cb
--- /dev/null
+++ b/product/Formulator/dtml/fieldMessages.dtml
@@ -0,0 +1,23 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+Edit <dtml-var meta_type> error messages here.
+</p>
+
+<form action="manage_messages" method="POST">
+<table border="0">
+<dtml-in "get_error_names()">
+  <dtml-let name=sequence-item value="get_error_message(name)">
+  <tr>
+  <td class="form-label"><dtml-var name></td>
+  <td><textarea name="&dtml-name;" cols="50" rows="4"><dtml-var value></textarea></td>
+  </tr>
+  </dtml-let>
+</dtml-in>
+<tr><td><input type="submit" value=" OK "></td></tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>
+
diff --git a/product/Formulator/dtml/fieldOverride.dtml b/product/Formulator/dtml/fieldOverride.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..86b6133350bdb38d97287b0d80440e8aa32fb927
--- /dev/null
+++ b/product/Formulator/dtml/fieldOverride.dtml
@@ -0,0 +1,58 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+Edit <dtml-var meta_type> method overrides here.
+</p>
+
+<form action="manage_override" method="POST">
+<table cellspacing="0" cellpadding="2" border="0">
+
+<dtml-in "override_form.get_groups()">
+<dtml-let group=sequence-item fields="override_form.get_fields_in_group(group)">
+
+<dtml-if fields>
+<tr>
+<td colspan="3" class="form-title">
+  <dtml-var "_.string.capitalize(group)"> properties
+</td>
+</tr>
+
+<dtml-var fieldListHeader>
+
+<dtml-let current_field="this()">
+<dtml-in fields>
+<dtml-let field=sequence-item field_id="field.id"
+          value="current_field.get_override(field.id)">
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    <dtml-var "field.title()">
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <dtml-var "field.render(value)">
+    </td>
+    <td><div class="form-element">
+    <dtml-var "current_field.form.get_field(field.id).meta_type">
+    </div></td>
+  </tr>
+</dtml-let>
+</dtml-in>
+</dtml-let>
+</dtml-if>
+</dtml-let>
+</dtml-in>
+
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-element">
+    <input class="form-element" type="submit" name="submit" 
+     value="Save Changes" /> 
+    </div>
+    </td>
+  </tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>
diff --git a/product/Formulator/dtml/fieldTales.dtml b/product/Formulator/dtml/fieldTales.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..352b0a18c5f6984d70ecae4df17376f50dff041a
--- /dev/null
+++ b/product/Formulator/dtml/fieldTales.dtml
@@ -0,0 +1,64 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+Edit <dtml-var meta_type> method TALES expressions here.
+<dtml-if "not isTALESAvailable()"><br>
+<span style="color: #FF0000;">
+Zope Page Templates and therefore TALES is not installed.
+This tab can therefore not be used.
+</span>
+</dtml-if>
+</p>
+
+<form action="manage_tales" method="POST">
+<table cellspacing="0" cellpadding="2" border="0">
+
+<dtml-in "override_form.get_groups()">
+<dtml-let group=sequence-item fields="tales_form.get_fields_in_group(group)">
+
+<dtml-if fields>
+<tr>
+<td colspan="3" class="form-title">
+  <dtml-var "_.string.capitalize(group)"> properties
+</td>
+</tr>
+
+<dtml-var fieldListHeader>
+
+<dtml-let current_field="this()">
+<dtml-in fields>
+<dtml-let field=sequence-item field_id="field.id"
+          value="current_field.get_tales(field.id)">
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    <dtml-var "field.title()">
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <dtml-var "field.render(value)">
+    </td>
+    <td><div class="form-element">
+    <dtml-var "current_field.form.get_field(field.id).meta_type">
+    </div></td>
+  </tr>
+</dtml-let>
+</dtml-in>
+</dtml-let>
+</dtml-if>
+</dtml-let>
+</dtml-in>
+
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-element">
+    <input class="form-element" type="submit" name="submit" 
+     value="Save Changes" /> 
+    </div>
+    </td>
+  </tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>
diff --git a/product/Formulator/dtml/fieldTest.dtml b/product/Formulator/dtml/fieldTest.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..aa644e1f614717443137366e13046fe22cdf98c1
--- /dev/null
+++ b/product/Formulator/dtml/fieldTest.dtml
@@ -0,0 +1,53 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+Test this <dtml-var meta_type>.
+</p>
+
+<dtml-if fieldTestActivated>
+  <dtml-try>
+    <p><div class="form-text">Test successful: <dtml-var "validate(REQUEST)"></div></p>
+  <dtml-except ValidationError>
+   <p>There was a validation error:</p>
+   <table cellspacing="0" cellpadding="2" border="0">
+     <tr class="list-header">
+       <td class="form-label">field_id</td>
+       <td class="form-label">error_key</td>
+       <td class="form-label">error_text</td>
+     </tr>
+     <dtml-let error=error_value>
+     <tr>
+       <td class="form-text">
+       <dtml-var "error.field_id">
+       </td>
+       <td class="form-text">
+       <dtml-var "error.error_key">
+       </td>
+       <td class="form-text">
+       <dtml-var "error.error_text">
+       </td>
+     </tr>
+     </dtml-let>
+   </table>
+  </dtml-try>
+  <hr>
+</dtml-if>
+
+<form action="fieldTest" method="POST">
+  <table cellspacing="0" cellpadding="2" border="0">
+  <tr>
+    <td class="form-label" >
+    <dtml-var title>
+    </td> 
+     
+    <td align="left" valign="top">
+    <dtml-var "render()">
+    </td>
+  </tr>
+  <input type="hidden" name="fieldTestActivated" value="1">
+  <tr><td><input type="submit" value="Test"></td></tr>
+  </table>
+</form>
+
+<dtml-var manage_page_footer>
diff --git a/product/Formulator/dtml/formAdd.dtml b/product/Formulator/dtml/formAdd.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..a8c230950bcc56aa654be4149dd73c29b5b26c6f
--- /dev/null
+++ b/product/Formulator/dtml/formAdd.dtml
@@ -0,0 +1,62 @@
+<dtml-var manage_page_header>
+
+<dtml-var "manage_form_title(this(), _,
+           form_title='Add Formulator Form',
+           )">
+
+<p class="form-help">
+Formulator Forms allow you to create solid web forms more easily.
+</p>
+
+<form action="manage_add" method="POST">
+
+<table cellspacing="0" cellpadding="2" border="0">
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Id
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="id" size="40" />
+    </td>
+  </tr>
+
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Title
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="title" size="40" />
+    </td>
+  </tr>
+
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Unicode mode
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="checkbox" name="unicode_mode" size="40" />
+    </td>
+  </tr>
+
+  <tr>
+    <td align="left" valign="top">
+    </td>
+    <td align="left" valign="top">
+    <div class="form-element">
+    <input class="form-element" type="submit" name="submit_add" 
+     value=" Add " /> 
+    <input class="form-element" type="submit" name="submit_add_and_edit" 
+     value=" Add and Edit " />
+    </div>
+    </td>
+  </tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>
diff --git a/product/Formulator/dtml/formOrder.dtml b/product/Formulator/dtml/formOrder.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..2961661487a977c2d532deae29e1b244e9b41b2b
--- /dev/null
+++ b/product/Formulator/dtml/formOrder.dtml
@@ -0,0 +1,142 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+Change the display order and grouping of the fields in this form.
+</p>
+
+<table border="1" cellspacing="1" cellpadding="3">
+<dtml-let all_groups="get_groups(include_empty=1)" 
+          group_length="get_largest_group_length()"
+          first_group="all_groups and all_groups[0] or None">
+<dtml-in "get_group_rows()">
+<tr>
+<dtml-let groups=sequence-item>
+<dtml-in groups>
+<dtml-let group=sequence-item>
+  <td nowrap valign="top">
+  <table border="0" cellspacing="0" cellpadding="0">
+  <form action="." method="POST">
+  <input type="hidden" name="group" value="&dtml-group;">
+  <tr><td align="center" class="list-header">
+    <div class="list-nav">
+    <dtml-var group html_quote>
+    </div>
+    </td></tr>
+  
+  <tr><td align="left">
+    <dtml-let fields="get_fields_in_group(group)" fields_amount="_.len(fields)">
+    <table border="0" cellspacing="0" cellpadding="0">
+    <dtml-in fields>
+    <dtml-let field=sequence-item field_id="field.id">
+      <tr><td height="25">
+          <div class="list-item">
+          <input type="checkbox" name="&dtml-field_id;">&nbsp;<a href="&dtml-field_id;/manage_main"><img src="&dtml-BASEPATH1;/&dtml-icon;" alt="&dtml-meta_type;" title="&dtml-meta_type;" border="0"></a>&nbsp;<a href="&dtml-field_id;/manage_main"><dtml-var field_id></a>
+	  </div>
+	  </td></tr>
+    </dtml-let>
+    </dtml-in>
+    <dtml-in "_.range(group_length - fields_amount)">
+       <tr><td height="25"></td></tr>
+    </dtml-in>
+    </dtml-let>
+    </table>
+  </td></tr>
+     
+  <tr><td align="center">
+    <input class="form-element" type="submit" name="manage_move_field_up:method"
+           value="Move Up">
+  </td></tr>
+  
+  <tr><td align="center">
+    <input class="form-element" type="submit" name="manage_move_field_down:method" 
+           value="Move Dn"><br><br>
+  </td></tr>
+
+  <tr><td align="center">
+    <div class="form-element">
+    <select class="form-element" name="to_group" size="1">
+      <option>Move to:</option>
+      <dtml-in all_groups>
+        <option><dtml-var sequence-item html_quote></option>
+      </dtml-in>
+    </select>
+    </div>
+  </td></tr>
+ 
+  <tr><td align="center">
+    <input class="form-element" type="submit" name="manage_move_group:method"
+           value="Transfer">
+  </td></tr>
+
+
+  <dtml-if "group != first_group">
+  
+  <tr><td align="center" class="list-header">
+    <div class="list-item">
+    Group
+    </div>
+  </td></tr>
+
+  <tr><td align="center">
+    <input class="form-element" type="submit" name="manage_move_group_up:method"
+           value="Move Up">
+  </td></tr>
+
+  <tr><td align="center">
+    <input class="form-element" type="submit" name="manage_move_group_down:method" 
+           value="Move Dn"><br><br>
+  </td></tr>
+
+  <tr><td align="center">
+    <input type="text" name="new_name" value="" size="10">
+  </td></tr>
+
+  <tr><td align="center">
+    <input class="form-element" type="submit" name="manage_rename_group:method"
+           value="Rename"><br>
+  </td></tr>
+  <tr><td align="center">
+    <input class="form-element" type="submit" name="manage_remove_group:method"
+           value="Remove"><br>
+  </td></tr>
+
+  <dtml-else>
+
+  <tr><td align="center" class="list-header">
+    <div class="list-item">
+    Group
+    </div>
+  </td></tr>
+  
+  <tr><td align="center">
+    <input type="text" name="new_group" value="" size="10">
+  </td></tr>
+
+  <tr><td align="center">
+    <input type="submit" name="manage_add_group:method" value="Create"><br><br>
+  </td></tr>
+
+  <tr><td align="center">
+    <input type="text" name="new_name" value="" size="10">
+  </td></tr>
+
+  <tr><td align="center">
+    <input class="form-element" type="submit" name="manage_rename_group:method"
+           value="Rename"><br>
+  </td></tr>
+
+  </dtml-if>
+
+  </form>
+  </table>
+  </td>
+</dtml-let>
+</dtml-in>
+</dtml-let>
+</tr>
+</dtml-in>
+</dtml-let>
+</table>
+
+<dtml-var manage_page_footer>
diff --git a/product/Formulator/dtml/formSettings.dtml b/product/Formulator/dtml/formSettings.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..51ea7a57d7f24708bfa71c14e3d05b5c4d98925a
--- /dev/null
+++ b/product/Formulator/dtml/formSettings.dtml
@@ -0,0 +1,34 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+Settings for this form.
+</p>
+
+<dtml-let me="this()">
+ 
+<dtml-var "settings_form.header()">
+
+<table border="0">
+  <dtml-in "settings_form.get_fields()"><dtml-let field=sequence-item>
+  <tr>
+    <td class="form-label"><dtml-var "field.get_value('title')"></td>
+    <td><dtml-if "_.getattr(me, field.id)"><dtml-var "field.render(_.getattr(me, field.id, None))"><dtml-else><dtml-var "field.render()"></dtml-if></td>
+  </tr>
+  </dtml-let></dtml-in>
+
+  <tr>
+    <td><input type="submit" value="Change"></td>
+  </tr>
+</table>
+
+<dtml-var "settings_form.footer()">
+
+</dtml-let>
+
+<p>Upgrade</p>
+<form action="manage_refresh" method="POST">
+  <p><input type="submit" value="Upgrade"></p>
+</form>
+
+<dtml-var manage_page_footer>
diff --git a/product/Formulator/dtml/formTest.dtml b/product/Formulator/dtml/formTest.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..d5eb0b0a4dd36e05b4a926c3a2d357ff47b4c84f
--- /dev/null
+++ b/product/Formulator/dtml/formTest.dtml
@@ -0,0 +1,72 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+Test this form.
+</p>
+
+<dtml-if formTestActivated>
+  <dtml-try>
+    <dtml-call "validate_all(REQUEST)">
+    <p>All fields were validated correctly.</p>
+  <dtml-except FormValidationError>
+   <p>Not all fields could be validated.</p>
+   <table cellspacing="0" cellpadding="2" border="0">
+     <tr class="list-header">
+       <td><div class="form-label">field_id&nbsp;</div></td>
+       <td><div class="form-label">error_key</div></td>
+       <td><div class="form-label">error_text</div></td>
+     </tr>
+     <dtml-in "error_value.errors">
+     <dtml-let error=sequence-item>
+     <tr>
+       <td class="form-text">
+       <dtml-var "error.field_id">
+       </td>
+       <td class="form-text">
+       <dtml-var "error.error_key">
+       </td>
+       <td class="form-text">
+       <dtml-var "error.error_text">
+       </td>
+     </tr>
+     </dtml-let>
+     </dtml-in>
+   </table>
+  </dtml-try>
+  <hr>
+</dtml-if>
+
+<form action="formTest" method="POST">
+  <table cellspacing="0" cellpadding="2" border="0">
+  <dtml-in "get_groups()">
+  <dtml-let group=sequence-item fields="get_fields_in_group(group)">
+  <dtml-if fields>
+  <tr><td colspan="2" class="list-header"><div class="form-label"><dtml-var group></div></td></tr>
+  <dtml-in fields>
+  <dtml-let field=sequence-item>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    <dtml-var "field.title()">
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <dtml-var "field.render()">
+    </td>
+  </tr>  
+  </dtml-let>
+  </dtml-in>
+  </dtml-if>
+  </dtml-let>
+  </dtml-in>
+
+  <input type="hidden" name="formTestActivated" value="1">
+  <tr><td><input type="submit" value="Test"></td></tr>
+
+  </table>
+</form>
+
+
+<dtml-var manage_page_footer>
+
diff --git a/product/Formulator/dtml/formXML.dtml b/product/Formulator/dtml/formXML.dtml
new file mode 100644
index 0000000000000000000000000000000000000000..4a9419eaedb4a9fe8f4775f38dfd119520ead1e5
--- /dev/null
+++ b/product/Formulator/dtml/formXML.dtml
@@ -0,0 +1,17 @@
+<dtml-call "REQUEST.set('management_page_charset', 'UTF-8')">
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<p class="form-help">
+XML Representation of this form.
+</p>
+
+<form action="manage_editXML" method="POST">
+  <table cellspacing="0" cellpadding="2" border="0">
+     <textarea wrap="off" style="width: 100%;" cols="50" rows="20" name="form_data"><dtml-var get_xml></textarea>
+  </table>
+  <input type="submit" name="save_xml" value=" Save " />
+</form>
+
+<dtml-var manage_page_footer>
+
diff --git a/product/Formulator/help/BasicForm.py b/product/Formulator/help/BasicForm.py
new file mode 100644
index 0000000000000000000000000000000000000000..4803085571c33b00a24f2ff2c8d9180b65521fb9
--- /dev/null
+++ b/product/Formulator/help/BasicForm.py
@@ -0,0 +1,39 @@
+
+class BasicForm:
+    """
+    Use BasicForm to construct and use Formulator forms from
+    Python code.
+    """
+    
+    __extends__ = ('Formulator.Form.Form',)
+
+    def add_field(field, group=None):
+        """
+        Add a field to a group on
+        the form. The 'group' argument is optional, and if no group is
+        given the field is added to the first group. The field is
+        always added to the bottom of the group.
+
+        Permission -- 'Change Formulator Forms'
+        """
+
+    def add_fields(fields, group=None):
+        """
+        Add a list of fields to a on the form. The 'group' argument is
+        optional; if no group is given the fields are added to the first
+        group. Fields are added in the order given to the bottom of the
+        group.
+
+        Permission -- 'Change Formulator Forms'
+        """
+
+    def remove_field(field):
+        """
+        Remove a particular field from the form.
+
+        Permission -- 'Change Formulator Forms'
+        """
+
+
+    
+
diff --git a/product/Formulator/help/Field.py b/product/Formulator/help/Field.py
new file mode 100644
index 0000000000000000000000000000000000000000..78c14ab50d6356b215dbea186019761c877b7cbf
--- /dev/null
+++ b/product/Formulator/help/Field.py
@@ -0,0 +1,179 @@
+class Field:
+    """Formulator field base class; shared functionality of all
+    fields.
+    """
+    def initialize_values(dict):
+        """
+        Initialize the values of field properties. 'dict' is a
+        dictionary. A dictionary entry has as key the property id,
+        and as value the value the property should take initially.
+        If there is no entry in the dictionary for a particular
+        property, the default value for that property will be used.
+        All old property values will be cleared.
+
+        Permission -- 'Change Formulator Fields'
+        """
+
+    def initialize_overrides():
+        """
+        Clear (initialize to nothing) overrides for all properties.
+
+        Permission -- 'Change Formulator Fields'
+        """
+
+    def has_value(id):
+        """
+        Returns true if a property with name 'id' exists.
+
+        Permission -- 'Access contents information'
+        """
+
+    def get_orig_value(id):
+        """
+        Get value of property without looking at possible overrides.
+
+        Permission -- 'Access contents information'
+        """
+
+    def get_value(id, **kw):
+        """    
+        Get property value for an id. Call override if override is
+        defined, otherwise use property value. Alternatively the
+        dictionary interface can also be used ('__getitem__()').
+
+        Keywords arguments can optionally be passed, which will end up
+        in the namespace of any TALES expression that gets called.
+        
+        Permission -- 'Access contents information'
+        """
+
+    def __getitem__(self, key):
+        """
+        Get property value for an id. Call override if override is
+        defined, otherwise use property value. Alternatively
+        'get_value()' can be used to do this explicitly.
+
+        In Python, you can access property values using::
+
+          form.field['key']
+
+        and in Zope Page Templates you can use the following path
+        notation::
+
+          form/field/key
+
+        Permission -- 'Access contents information'
+        """
+        
+    def get_override(id):
+        """
+        Get the override method for an id, or empty string
+        if no such override method exists.
+
+        Permission -- 'Access contents information'
+        """
+        
+    def is_required():
+        """
+        A utility method that returns true if this field is required.
+        (checks for 'required' property).
+
+        Permission -- 'Access contents information'
+        """
+
+    def get_error_names():
+        """
+        Get all keys of error messages that the validator
+        of this field provides.
+
+        Permission -- 'View management screens'
+        """
+
+    def get_error_message(name):
+        """
+        Get the contents of a particular error message with key
+        'name'.
+
+        Permission -- 'View management screens'
+        """
+
+    def render(value=None, REQUEST=None):
+        """
+        Get the rendered HTML for the widget of this field, to
+        display the fields on a form.
+
+        'value' -- If the 'value' parameter is not None, this will be
+        the pre-filled value of the field on the form.
+
+        'REQUEST' -- If the 'value' parameter is 'None' and 'REQUEST' is
+        supplied, raw (unvalidated) values will be looked up in
+        'REQUEST' to display in the field.
+
+        If neither 'value' or 'REQUEST' are supplied, the field's
+        default value will be used instead.
+
+        Permission -- 'View'
+        """
+        
+    def render_from_request(REQUEST):
+        """
+        A convenience method to render the field widget using
+        the raw data from 'REQUEST' if any is available. The field's
+        default value will be used if no raw data can be found.
+
+        Pemrissions -- 'View'
+        """
+
+    def render_sub_field(id, value=None, REQUEST=None):
+        """
+        Render a sub field of this field. This is used by composite
+        fields that are composed of multiple sub fields such as
+        'DateTimeField'. 'id' is the id of the sub field. 'value' and
+        'REQUEST' work like in 'render()', but for the sub field.
+
+        Permission -- 'View'
+        """
+
+    def render_sub_field_from_request(id, REQUEST):
+        """
+        A convenience method to render a sub field widget from
+        'REQUEST' (unvalidated data).
+
+        Permission -- 'View'
+        """
+        
+    def validate(REQUEST):
+        """
+        Validate this field using the raw unvalidated data found
+        in 'REQUEST'.
+
+        Returns the validated and processed value, or raises a
+        ValidationError.
+
+        Permission -- 'View'
+        """
+        
+    def validate_sub_field(id, REQUEST):
+        """
+        Validate a sub field of this field using the raw unvalidated
+        data found in 'REQUEST'. This is used by composite fields
+        composed of multiple sub fields such as 'DateTimeField'.
+        
+        Returns the validated and processed value, or raises a
+        ValidationError.
+
+        Permission -- 'View'
+        """
+
+    def render_view(value):
+        """
+        Render supplied value for viewing, not editing. This can be used
+        to show form results, for instance.
+
+        Permission -- 'View'
+        """
+
+
+
+
+
diff --git a/product/Formulator/help/Form.py b/product/Formulator/help/Form.py
new file mode 100644
index 0000000000000000000000000000000000000000..2302d928254a7c9e836c9c1e685eb2c1ecf44c7e
--- /dev/null
+++ b/product/Formulator/help/Form.py
@@ -0,0 +1,268 @@
+class Form:
+    """A Formulator Form; this is the base class of all forms.
+    """
+    def move_field_up(field_id, group):
+        """
+        Move the field with 'field_id' up in the group with name 'group'.
+        Returns 1 if move succeeded, 0 if it failed.
+        
+        Permission -- 'Change Formulator Forms'
+        """
+
+    def move_field_down(field_id, group):
+        """
+        Move the field with 'field_id' down in the group with name 'group'.
+        Returns 1 if move succeeded, 0 if it failed.
+        
+        Permission -- 'Change Formulator Forms'
+        """
+
+    def move_field_group(field_ids, from_group, to_group):
+        """
+        Move a number of field ids in the list 'field_ids' from 'from_group'
+        to 'to_group'.
+        Returns 1 if move succeeded, 0 if it failed.
+
+        Permission -- 'Change Formulator Forms'
+        """
+
+    def add_group(group):
+        """
+        Add a new group with the name 'group'. The new group must have
+        a unique name in this form and will be added to the bottom of the
+        list of groups.
+        
+        Returns 1 if the new group could be added, 0 if it failed.
+
+        Permission -- 'Change Formulator Forms'
+        """
+
+    def remove_group(group):
+        """
+        Remove an existing group with the name 'group'. All fields that
+        may have been in the group will be moved to the end of the
+        first group. The first group can never be removed.
+
+        Returns 1 if the group could be removed, 0 if it failed.
+
+        Permission -- 'Change Formulator Forms'
+        """
+
+    def rename_group(group, name):
+        """
+        Give an existing group with the name 'group' a new name. The
+        new name must be unique.
+
+        Returns 1 if the rename succeeded, 0 if it failed.
+
+        Permission -- 'Change Formulator Forms'
+        """
+
+    def move_group_up(group):
+        """
+        Move the group with name 'group' up in the list of groups.
+
+        Returns 1 if the move succeeded, 0 if it failed
+
+        Permission -- 'Change Formulator Forms'
+        """
+
+    def move_group_down(group):
+        """
+        Move the group with name 'group' down in the list of groups.
+
+        Returns 1 if the move succeeded, 0 if it failed.
+
+        Permission -- 'Change Formulator Forms'
+        """
+        
+    def get_fields():
+        """
+        Returns a list of all fields in the form (in all groups). The
+        order of the fields will be field order in the groups, and the
+        groups will be in the group order.
+
+        Permission -- 'View'
+        """
+
+    def get_field_ids():
+        """
+        As 'get_fields()', but instead returns a list of ids of all the fields.
+
+        Permission -- 'View'
+        """
+
+    def get_fields_in_group(group):
+        """
+        Get a list in field order in a particular group.
+
+        Permission -- 'View'
+        """
+
+    def has_field(id):
+        """
+        Check whether the form has a field of a certain id. Returns true
+        if the field exists.
+
+        Permission -- 'View'
+        """
+
+    def get_field(id):
+        """
+        Get a field with a certain id.
+
+        Permission -- 'View'
+        """
+
+    def get_groups():
+        """
+        Get a list of all groups in the form, in group order.
+
+        Permission -- 'View'
+        """
+
+    def render(self, dict=None, REQUEST=None):
+        """
+        Returns a basic HTML rendering (in a table) of this form.
+        For more sophisticated renderings you'll have to write
+        DTML or ZPT code yourself.
+
+        You can supply an optional 'dict' argument; this should be a
+        dictionary ('_', the namespace stack, is legal). The
+        dictionary can contain data that should be pre-filled in the
+        form (indexed by field id). The optional 'REQUEST' argument
+        can contain raw form data, which will be used in case nothing
+        can be found in the dictionary (or if the dictionary does not
+        exist).
+
+        Permission -- 'View'
+        """
+        
+    def validate(REQUEST):
+        """
+        Validate all the fields in this form, looking in REQUEST for
+        the raw field values. If any validation error occurs,
+        ValidationError is raised and validation is stopped.
+
+        Returns a dictionary with as keys the field ids and as values
+        the validated and processed field values.
+
+        Exceptions that are raised can be caught in the following way
+        (also in through the web Python scripts)::
+
+          from Products.Formulator.Errors import ValidationError
+          try:
+             myform.validate(REQUEST)
+          except ValidationError, e:
+             print 'error' # handle error 'e'
+          
+        Permission -- 'View'
+        """
+
+    def validate_to_request(REQUEST):
+        """
+        Validate all the fields in this form, looking in REQUEST for
+        the raw field values. If any validation error occurs,
+        ValidationError is raised and validation is stopped.
+
+        Returns a dictionary with as keys the field ids and as values
+        the validated and processed field values. In addition, this
+        result will also be added to REQUEST (also with the field ids
+        as keys).
+
+        Exceptions that are raised can be caught in the following way
+        (also in through the web Python scripts)::
+
+          from Products.Formulator.Errors import ValidationError
+          try:
+             myform.validate_to_request(REQUEST)
+          except ValidationError, e:
+             print 'error' # handle error 'e'
+
+        Permission -- 'View'
+        """
+
+    def validate_all(REQUEST):
+        """
+        Validate all the fields in this form, looking in REQUEST for
+        the raw field values. If any ValidationError occurs, they are
+        caught and added to a list of errors; after all validations a
+        FormValidationError is then raised with this list of
+        ValidationErrors as the 'errors' attribute.
+
+        Returns a dictionary with as keys the field ids and as values
+        the validated and processed field values.
+
+        Exceptions that are raised can be caught in the following way
+        (also in through the web Python scripts)::
+
+          from Products.Formulator.Errors import ValidationError, FormValidationError
+          try:
+             myform.validate_all(REQUEST)
+          except FormValidationError, e:
+             print 'error' # handle error 'e', which contains 'errors'
+
+        Permission -- 'View'
+        """
+        
+    def validate_all_to_request(REQUEST):
+        """
+        Validate all the fields in this form, looking in REQUEST for
+        the raw field values. If any ValidationError occurs, they are
+        caught and added to a list of errors; after all validations a
+        FormValidationError is then raised with this list of
+        ValidationErrors as the 'errors' attribute.
+
+        Returns a dictionary with as keys the field ids and as values
+        the validated and processed field values. In addition, the
+        validated fields will be added to REQUEST, as in
+        'validate_to_request()'. This will always be done for all
+        fields that validated successfully, even if the validation of
+        other fields failed and a FormValidationError is raised.
+
+        Exceptions that are raised can be caught in the following way
+        (also in through the web Python scripts)::
+
+          from Products.Formulator.Errors import ValidationError, FormValidationError
+          try:
+             myform.validate_all_to_request(REQUEST)
+          except FormValidationError, e:
+             print 'error' # handle error 'e', which contains 'errors'
+
+        Permission -- 'View'
+        """
+
+    def session_store(session, REQUEST):
+        """
+        Store any validated form data in REQUEST in a session object
+        (Core Session Tracking).
+
+        Permission -- 'View'
+        """
+
+    def session_retrieve(session, REQUEST):
+        """
+        Retrieve validated form data from session (Core Session
+        Tracking) into REQUEST.
+
+        Permission -- 'View'
+        """
+        
+    def header():
+        """
+        Get the HTML code for the start of a form. This produces a
+        '<form>' tag with the 'action' and 'method' attributes
+        that have been set in the Form.
+
+        Permission -- 'View'
+        """
+
+    def footer():
+        """
+        Get the code for the end of the form ('</form>').
+
+        Permission -- 'View'
+        """
+
+
+
diff --git a/product/Formulator/help/ZMIForm.py b/product/Formulator/help/ZMIForm.py
new file mode 100644
index 0000000000000000000000000000000000000000..3306c285840048622de796ba7655840fe3a04cae
--- /dev/null
+++ b/product/Formulator/help/ZMIForm.py
@@ -0,0 +1,24 @@
+
+class ZMIForm:
+    """Form used from Zope Management Interface. Inherits from
+    ObjectManager to present folderish view.
+    """
+
+    __extends__ = ('Formulator.Form.Form',
+                   'OFSP.ObjectManager.ObjectManager',
+                   'OFSP.ObjectManagerItem.ObjectManagerItem')
+
+
+    def manage_addField(id, title, fieldname, REQUEST=None):
+        """
+        Add a new field with 'id' and 'title' of field type
+        'fieldname' to this ZMIForm. 'REQUEST' is optional.  Note that
+        it's better to use BasicForm and 'add_field' if you want to
+        use Formulator Forms outside the Zope Management Interface.
+        
+        Permission -- 'Change Formulator Forms'
+        """
+
+        
+        
+    
diff --git a/product/Formulator/help/dogfood.txt b/product/Formulator/help/dogfood.txt
new file mode 100644
index 0000000000000000000000000000000000000000..acfbcc29325fa8f83507f8a5c43119a77a0a44e3
--- /dev/null
+++ b/product/Formulator/help/dogfood.txt
@@ -0,0 +1,181 @@
+How Formulator Eats Its Own Dogfood
+
+  **NOTE**: You do not have to read this or understand this in order to
+  use or even extend Formulator. Your brain may explode. Have fun.
+
+  Formulator eats its own dogfood; fields have editable properties
+  also represented by fields. A field class may contain an instance of
+  itself in the end! This is accomplished by some hard to comprehend
+  code, which I've still tried to write down as clearly as
+  possible. Since at times I still can't figure it out myself, I've
+  included this textual description.
+
+The following files are in play (in 'Products/Formulator'):
+
+  'FieldRegistry.py'
+
+    All field classes are registered here (instead of with the
+    standard Zope addable product registry).
+
+  'Field.py'
+
+    The main Field classes.
+
+  'Form.py'
+
+    The main Form classes. BasicForm is used internally for Forms
+    without any web management UI. PythonForm is a form with a web UI
+    (derived from ObjectManager).
+
+  'StandardFields.py'
+ 
+    Contains a bunch of standard fields that can be used by Formulator,
+    such as StringField, IntegerField and TextArea field.
+
+  'DummyField.py'
+
+    Contains a dummy implementation of a field that can be used by
+    fields before the field classes have been fully defined.
+
+  'Widget.py'
+
+    Contains the code for displaying a field as HTML. A widget has a
+    'property_names' list associated with it, as well a number of
+    class attributes for the fields that help define the parameters of
+    the widget (dimensions, default value, etc). These are in fact not
+    real fields at widget creation time, but DummyField instances.
+
+  'Validator.py'
+
+    Contains the code for validating field input. Like a widget, it
+    contains a 'property_names' list and a number of
+    field-but-not-really (DummyField) class attributes.
+
+  '__init__.py'
+
+    Sets up the fields first, and then registers the Formulator
+    product (Formulator Form addable) with Zope itself. Somewhat more
+    complicated than the average Product __init__.py.
+
+  'FieldHelpTopic.py'
+ 
+    Used to make the Zope help system automatically generate help for
+    each field class.
+
+  'HelperFields.py'
+
+    Collects helper (internal) fields together for easy importing.
+
+  'MethodField.py'
+
+    Experimental MethodField. Right now only used internally by
+    ListFields.
+
+  'ListTextAreaField.py'
+
+    Used internally by ListFields.
+
+  'www' 
+ 
+    This directory contains dtml files and icons used in the
+    management screens of Formulator.
+
+  'help'
+
+    Help files.
+
+Startup Sequence
+
+  Before 'initialize()' in '__init__.py' is called:
+
+    * the widget and validator classes is initialized, using the
+      dummy FieldProperty objects as if they are fields as class
+      attributes.
+ 
+    * Singleton widget instance and validator instance objects are
+      created.
+
+    * The field classes is initialized, with widget and validator
+      class attributes.
+
+    * A singleton FieldRegistry object is created.
+
+  'initialize()' - fields are registered with FieldRegistry:
+ 
+    * A reference to the field class is stored in the FieldRegistry.
+
+    * A BasicForm instance is created for each field class. Then the
+      DummyFields associated with the field class' widget and validator
+      objects are added to the BasicForm (to the Widget and the Validator
+      groups).
+
+    * Each field class now has a BasicForm containing the (dummy) fields
+      that define its look and feel and behavior.
+     
+    * The appropriate field icons are also added to field classes. 
+
+    * the Form is registered with Zope
+
+    * finally, initializeFormulator in Form.py is called.
+
+  'initialize()' - help is registered:
+
+    * the .txt files in the help directory are registered with Zope.
+
+    * A special FieldHelpTopic is created for each registered field.
+ 
+  'initialize()' - final touches:
+
+    * initializeForm in Form.py registers each (non-internal) field in
+      the registry with Python Form, so that users can add the fields
+      to the form.
+
+    * initializeFields in the FieldRegistry makes the DummyFields that
+      stood in for Field properties into real fields, now that
+      everything else has been registered and set up.
+
+Default properties
+
+  It is (for me) hard to understand where default properties are
+  coming from and should come from. Therefore I've created this
+  description.
+
+  A field has a 'default' property field. This is defined in the field
+  *class* 'form' class attribute.
+
+  A field has a 'default' property value. This is defined on the field
+  *instance*, in the 'values' dictionary.
+
+  Field properties have a 'default' field property and value of their
+  own, as they are fields! Infinite regress? The *form* is shared by
+  all fields of the same type, as it's a class attribute. So while
+  there is infinite regress there, it does not cost infinite amounts
+  of memory.
+
+  A StringField has a 'default' property field that is itself a
+  StringField. It also has a 'default' property value that is the
+  empty string. On instantiation of a StringField, the 'default'
+  property value is either taken from a keyword argument 'default'
+  given to the __init__ function or, if none is present, from the
+  default value of the 'default' property field.
+
+  When a field is constructed using the Zope management interface this
+  will use the manage_addField() method, defined on the form.
+  manage_addField will create a field without any property values, so
+  the constructor of the field will use the defaults, which it will
+  take from the defaults of the property_fields.
+
+  The propery_fields *have* (indirectly through FieldDummy instances)
+  been constructed with default values given as keyword arguments.
+ 
+  So this is how it all works out in the end. I hope. My brain just
+  exploded; how's yours?
+
+  Practical advice; don't think too hard about it. If you want
+  particular property field defaults, pass them as keyword arguments
+  when you construct a DummyField for use in a Widget or Validator;
+  if instead you're fine with whatever default the field will come
+  up with, don't pass a keyword argument.
+   
+  Creating new types of fields is actually quite easy; trust me.
+
diff --git a/product/Formulator/help/fieldEdit.txt b/product/Formulator/help/fieldEdit.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8db1494c6d27453fe6c86b45ec2306f3eb0652ba
--- /dev/null
+++ b/product/Formulator/help/fieldEdit.txt
@@ -0,0 +1,62 @@
+Formulator Field - Edit
+
+  Description
+
+    A field has a number of properties that determine its look and
+    feel as well as its behavior. You can configure these properties
+    in this tab. You can also use the TALES tab and the Override tab
+    to associate dynamic behavior with field properties, though the
+    Override tab is eventually to be phased out in favor of the TALES
+    tab. Overridden fields will have their names be shown in square
+    brackets.
+
+    Which properties appear in this view depends on what kind of field
+    you are editing.
+
+    Each field has two sets of properties; widget properties and
+    validator properties.
+
+  Widget properties
+
+    The widget properties determine the look and feel of the field
+    that you see on the web page (which HTML code is generated for the
+    field); i.e. what GUI *widget* you see.
+
+    A very common widget property shared by all fields is called
+    'Title'; all fields have titles -- the name you will see when the
+    field is displayed on the screen.
+
+    Another very common widget property is the 'Default' value of the
+    widget. This is what will be filled in before the user changes
+    anything to the form, unless you pass a value to the 'render'
+    function; see the API reference for more information.
+
+    Many widgets also have size information; the StringField for
+    instance has a 'Display width' property which determines how large
+    the field should appear on the screen, as well as a 'Maximum
+    input' property that determines how much the user can enter
+    (though this is independent from actual server-side validation).
+
+    For some widget properties such as 'Maximum input' in StringField
+    you can set the value to '0'; in that the HTML widget won't care
+    how much the user inputs.
+      
+  Validator properties
+
+    This set of properties determines how the field validates the 
+    information that is submitted for this field.
+
+    A very common validator property is the 'required' property. If a
+    field is required, the field cannot be left empty by the user when
+    submitting a web page. Validation in that case will result in
+    failure.
+
+    In case of the 'StringField', one validation property is called
+    'Maximum length'; the field cannot contain more characters than
+    that.
+
+    For some validator properties such as 'Maximum length' in
+    StringField, you can set the value to '0'. The validator will then
+    not care how much the user entered -- there won't be any maximum.
+
+
diff --git a/product/Formulator/help/fieldMessages.txt b/product/Formulator/help/fieldMessages.txt
new file mode 100644
index 0000000000000000000000000000000000000000..af44d7d8e4e82ee5b00f5368fec791ed91b71f8c
--- /dev/null
+++ b/product/Formulator/help/fieldMessages.txt
@@ -0,0 +1,9 @@
+Formulator Field - Messages
+
+  Description
+
+    Each field has a set of messages that can be displayed in case
+    validation fails. Standard messages are provided, but you can
+    change these messages for the particular field in this view.  The
+    message text is the 'error_text' that will show up in case
+    validation fails.
diff --git a/product/Formulator/help/fieldOverride.txt b/product/Formulator/help/fieldOverride.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0872bb6094f4e62f45097aad73e40139e966df21
--- /dev/null
+++ b/product/Formulator/help/fieldOverride.txt
@@ -0,0 +1,50 @@
+Formulator Field - Override
+
+  Description
+
+    Note: the Override tab is being phased out in favor of the TALES
+    tab.
+
+    Sometimes you'd like a field property to be dynamic instead of
+    just a value filled in in the 'Edit' tab of the field. If you fill
+    in the name of an 'override' method for a property in the Override
+    tab, that method will be called whenever you (or the code) asks
+    for the value of that property using get_value(). Properties which
+    are overridden are shown between square brackets ([ and ]) in the
+    main Edit tab, and the value of the property in the edit tab will
+    be ignored. To stop using an override for a particular property,
+    remove the method name in the override tab.
+
+    An override method should return an object of the same type as the
+    property field would return after validation. For instance, an
+    override method for a StringField would return a simple string,
+    for an IntegerField it would return an integer, and for a
+    CheckBoxField it would return true or false (or something that can
+    be interpreted that way).
+
+  Example
+
+    A good example of the use of the override tab is the 'items'
+    property of a ListField; frequently you may want to get these
+    items from elsewhere, for instance from a database. In this case
+    you would fill in the name of the override method for 'items' that
+    retrieves the right data.
+
+    The 'right data' in this case is that which validation of the
+    builtin field ListTextArea would return. This is a list of tuples,
+    one tuple for each element. Each tuple consists of two strings;
+    the name that should be displayed to the user for that item, and
+    the actual value that will be submitted.
+
+    This for instance is a Python script 'random_numbers' that will return
+    ten random numbers as the elements::
+
+      # random_numbers
+      import random
+      result = []
+      for i in range(10):
+        number = random.randint(0, 100)
+        tuple = str(number), str(number)
+        result.append(tuple)
+      return result
+
diff --git a/product/Formulator/help/fieldTales.txt b/product/Formulator/help/fieldTales.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4905684395ecef8263a639197e26060c47404cc6
--- /dev/null
+++ b/product/Formulator/help/fieldTales.txt
@@ -0,0 +1,101 @@
+Formulator Field - TALES
+
+  Description
+
+    Sometimes you'd like a field property to be dynamic instead of
+    just a value filled in in the 'Edit' tab of the field. To 
+    make your fields more dynamic, you can enter TALES expressions
+    in the TALES tab. Whenever you (or some code) asks for the value of
+    that field property next with 'get_value()', the TALES expression
+    will be evaluated and the result will be the value of that
+    property.
+
+    Properties which are overridden with a TALES expression are shown
+    between square brackets ([ and ]) in the main Edit tab, and the
+    value of the property in the edit tab will be ignored. To stop
+    using a TALES expression for a particular property, remove the
+    expression in the TALES tab.
+
+    A TALES expression should return an object of the same type as the
+    property field would return after validation. For instance, a
+    TALES expression for a StringField would return a simple string,
+    for an IntegerField it would return an integer, and for a
+    CheckBoxField it would return true or false (or something that
+    Python accepts as such).
+
+  More information about TALES
+
+    The specification:
+
+      http://dev.zope.org/Wikis/DevSite/Projects/ZPT/TALES%20Specification%201.3
+     
+  Predefined variables
+
+    Two predefined variables are in the expression namespace, 'form',
+    and 'field'. You can use them to call their methods (though one
+    should be careful with some, to avoid infinite recursion), and
+    also methods and scripts that are the form's (or field's)
+    acquisition context. You can also pass them to the methods you
+    call.
+
+  Relation to the Override tab
+
+    The TALES tab is meant to eventually obsolute the Override tab;
+    the use of the Override tab can therefore be considered
+    deprecated. Once Zope Page Templates (and thus TALES) become part
+    of the Zope core distribution, I plan to phase out the Override
+    tab altogether.
+
+    If an override tab says this:
+
+       foo
+
+    where foo is a Python Script that is acquired by the form, for
+    instance, you can now do:
+
+      python:form.foo()
+
+    This is longer, but the advantage is that you can now pass
+    parameters, for instance:
+
+      python:form.bar(1, 'hey')
+  
+  Example
+
+    A good example of the use of the TALES tab is the 'items' property
+    of a ListField; frequently you may want to get these items from
+    elsewhere, for instance from a database. In this case you would
+    fill in the name of the override method for 'items' that retrieves
+    the right data.
+
+    The 'right data' in this case is that which validation of the
+    builtin field ListTextArea would return. This is a list of tuples,
+    one tuple for each element. Each tuple consists of two strings;
+    the name that should be displayed to the user for that item, and
+    the actual value that will be submitted.
+
+    This for instance is a Python script 'random_numbers' that will
+    return 'n' random numbers as the elements, where 'n' is the (single)
+    parameter to the Python script::
+
+      # random_numbers
+      import random
+      result = []
+      for i in range(n):
+        number = random.randint(0, 100)
+        tuple = str(number), str(number)
+        result.append(tuple)
+      return result
+
+    You can call this script with the following expression for items,
+    which will give 10 random numbers.
+
+      python:form.random_numbers(10)
+ 
+    Caveat: in the current Formulator implementation it is very hard
+    to actually go through validation successfully, as exactly the
+    same random numbers need to be generated twice; once for the
+    display phase, and once during the validation phase. The
+    implementation currently assumes the list won't change through
+    multiple calls to calculate the 'items' property.
+
diff --git a/product/Formulator/help/fieldTest.txt b/product/Formulator/help/fieldTest.txt
new file mode 100644
index 0000000000000000000000000000000000000000..04abaf6ac823b0e588035facb74e1b2a6be883ef
--- /dev/null
+++ b/product/Formulator/help/fieldTest.txt
@@ -0,0 +1,24 @@
+Formulator Field - Test
+
+  Description
+
+    With the 'test' view, you can test the look and feel and
+    validation behavior of the field. Fill in the field and press the
+    'Test' button to test it.
+
+    You will now see the test form again. If your input was
+    successfully validated by the field, you will see a message 'Test
+    successful:' above the test form, followed by the result of the
+    validation processing (this may be empty text in case you filled
+    in nothing).
+
+    If the field could not be validated, you will see the message 'There was a
+    validation error:' instead, followed by a description of the error:
+
+      * 'field_id' gives the field name.
+
+      * 'error_key' is the name of the error message; you can find it
+         in the 'messages' tab.
+
+      * 'error_text' is the actual text associated with the
+        'error_key'; you can modify it in the 'messages' tab.
\ No newline at end of file
diff --git a/product/Formulator/help/formContents.txt b/product/Formulator/help/formContents.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cb1734a7bb3b0909c7c18f3afe0e68ac1a8b5d6e
--- /dev/null
+++ b/product/Formulator/help/formContents.txt
@@ -0,0 +1,27 @@
+Formulator Form - Contents
+ 
+  Description
+
+    The contents view of a Formulator Form behaves much like normal
+    Zope Folder. The difference is that the only objects listed are
+    Formulator Fields, and if you look at the 'Add' list you can see
+    you can only add different types of fields on this screen.
+    
+    Similarly, you can't copy and paste objects into the form that are
+    not fields, and it is not allowed to copy fields to normal folders
+    either.
+  
+    If you click on a field, you will enter the field edit screen, where
+    you can modify field properties.
+
+  Other Help
+
+    The other tabs of the Formulator Form contain their own help text;
+    fields also provide help.
+
+    For more information on how to use the contents view of
+    Formulator Form, look at the online help for Folders.
+
+
+  
+
diff --git a/product/Formulator/help/formOrder.txt b/product/Formulator/help/formOrder.txt
new file mode 100644
index 0000000000000000000000000000000000000000..58b1eb76256502f24049a7d5573145d686482ca0
--- /dev/null
+++ b/product/Formulator/help/formOrder.txt
@@ -0,0 +1,85 @@
+Formulator Form - Order
+
+  Description
+
+    The 'order' view allows you to change the order in which fields
+    should be displayed in the form. You can also group fields together;
+    this may be helpful when you create a complicated form.
+
+    Any field that gets added to the form ends up in the first  
+    group. This group always exists and cannot be removed.
+
+    You can click on the field id to go directly to the field management
+    screen.
+
+    Note that you *cannot* add or remove any fields in this form; you
+    have to use the main 'contents' view of the form for that.
+
+  How this information is used
+
+    The order and grouping information is used internally by the
+    'test' tab of the form, and you can also use it in your own
+    code. See the Formulator API Reference (to be written; for now
+    read the source!) for more information on how to do that.
+
+  Reordering fields inside a group
+
+    You can reorder fields in a group by moving them up and down in
+    the group. Select the field you want to move using the checkbox in
+    front of the field id. Then click on the 'Move Up' or 'Move Dn'
+    button, to move the field up or down respectively. You will see a
+    feedback message at the top of the screen. You can only move a
+    single field at a time this way, you select only the field you
+    want to move!
+
+  Creating a new group
+
+    The first group has a button called 'Create' at the bottom,
+    with an input box above it. Fill in the name of the new group that
+    you want to create there, and press the 'Create' button. The new group
+    will be added (as the last group visible).
+
+  How the groups are displayed
+
+    In order to show more information on a single screen, the groups
+    are ordered in columns from left to right, then in rows from top
+    to bottom. The 'settings' view allows you to modify how many
+    groups should appear in a single row (the default is 4
+    groups). Changing this has no impact on the functionality of the
+    form itself.
+ 
+    When you add a new group, it will be added to the right of the
+    current last group, if this still fits in a row. Otherwise a new
+    row will be created and the group will be displayed there.
+
+  Moving fields to a different group
+
+    When you create a new group, it remains empty. You can move
+    several fields into a group by using the 'Transfer' button of a
+    group. First select one ore more fields in an old group that you
+    want to move away. Then, use the 'Move to:' dropdown list to
+    select the group to which you want to move the selected fields.
+    Then, press the 'Transfer' button. The fields should now disappear
+    from the origin group and appear in the target group.
+
+  Reordering groups
+
+    You can change the order of the gropus by using the 'Move Up' and
+    'Move Dn' buttons in the 'Group' section of a group. This moves
+    the entire group in the group order. You cannot move the first 
+    group (and it therefore has no group movement buttons).
+
+  Renaming groups
+
+    You can rename a group by filling in the new name in the input
+    box above the 'Rename' button, and then pressing that button.
+  
+  Removing groups
+
+    You can remove a group by pressing the 'Remove' button in the 'Group'
+    section of the group. The entire group will disappear. Any fields that
+    were in the group will move to the bottom of the first group;
+    you cannot lose any fields this way.
+
+
+
diff --git a/product/Formulator/help/formSettings.txt b/product/Formulator/help/formSettings.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e4509adca1607c68c1627613c26ca89788d42c7d
--- /dev/null
+++ b/product/Formulator/help/formSettings.txt
@@ -0,0 +1,42 @@
+Formulator Form - Settings
+
+  Description
+
+    You can set some basic settings of the form.
+
+  Number of groups in row (in order tab)
+
+    Change the amount of groups that should appear in a single row
+    in the 'order' view. The default is '4'.
+
+  Form action
+
+    The method the form should call when it is submitted. If you use
+    the 'header()' method of Form, it will use this as the 'action'
+    attribute of the HTML form.
+
+  Form method
+
+    The submit method of the form (not to be confused with a method in
+    Python of Zope). 'POST' is generally used for forms that change
+    underlying data, while 'GET' is generally used for forms that do a
+    query. In case of 'GET' the fields in the form will be encoded in
+    the URL (so you can for instance bookmark the URL). The 'header()'
+    method of the Form will use this as the 'method' attribute of the
+    HTML form.
+  
+  Form enctype
+
+    The encoding type of the form. If no encoding type is selected,
+    the default for HTML will be used, which is
+    'application/x-www-form-urlencoded'. No enctype is therefore
+    usually just fine. For forms that allow the uploading of a file,
+    use 'multipart/form-data'. The 'header()' method of the Form will
+    use this as the 'enctype' attribute of the HTML form.
+
+  Upgrade
+
+    The 'Upgrade' button in this section is really not useful yet.
+    It's used internally to upgrade unreleased development versions of
+    Formulator to the current version. Perhaps this will become more
+    useful in the future.
diff --git a/product/Formulator/help/formTest.txt b/product/Formulator/help/formTest.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dee3cc197acc5cec0e57aba3b13b1c4e69784a63
--- /dev/null
+++ b/product/Formulator/help/formTest.txt
@@ -0,0 +1,27 @@
+Formulator Form - Test
+
+  Description
+
+    In this view, you can test your form. The fields in your form will
+    be rendered as a simple HTML form. The fields are grouped and come
+    in a certain order; this can be defined with the form's 'order'
+    tab.
+
+    You can fill in some values and click on the 'Test' button to
+    submit the form.
+
+    You will now see the test form again. If all of your input was
+    successfully validated by the fields in the form, you the message
+    'All fields were validated correctly' above your form.
+
+    If some of the fields could not be validated, you will instead see
+    the message 'Not all fields could be validated' appear, and a list
+    with the validation errors:
+
+      * 'field_id' indicates the field that gave this validation error.
+
+      * 'error_key' is the name of the error message; you can find it
+         on that field's 'messages' tab.
+
+      * 'error_text' is the actual text associated with that
+        'error_key'; you can modify it on the field's messages tab.
diff --git a/product/Formulator/help/formXML.txt b/product/Formulator/help/formXML.txt
new file mode 100644
index 0000000000000000000000000000000000000000..18c49d491e4b774dc20bbbaa1fb24b3dc620defc
--- /dev/null
+++ b/product/Formulator/help/formXML.txt
@@ -0,0 +1,17 @@
+Formulator Form - XML
+
+  Description
+
+    In this view, you can see an XML serialization of the form. If you
+    are using FSForm along with FileSystemSite, you can use this XML
+    by putting it on the filesystem as a .form file. FileSystemSite
+    will then pick it up and reflect it into the ZODB. This way you
+    can develop and maintain Formulator Forms on the filesystem.
+
+    FileSystemSite can be found here:
+  
+      http://www.zope.org/Members/k_vertigo/Products/FileSystemSite
+
+    To enable Formulator support for FileSystemSite, do a 'from
+    Products.Formulator import FSForm' somewhere in your own code (for
+    instance in your product's '__init__.py').
diff --git a/product/Formulator/help/formulator_howto.txt b/product/Formulator/help/formulator_howto.txt
new file mode 100644
index 0000000000000000000000000000000000000000..163284dab1ddc54b3c375f0fd1c39c7cfe847c72
--- /dev/null
+++ b/product/Formulator/help/formulator_howto.txt
@@ -0,0 +1,271 @@
+Formulator HOWTO
+
+  Introduction
+
+    This HOWTO is intended to give an introduction to the use of
+    Formulator from the Zope Management Interface and from DTML,
+    although much of this applies to use from ZPT or Python as
+    well. Note that Formulator comes with online help for each tab as
+    well as API help, so be sure to check that as well. This document
+    will only give an overview of the possibilities and not all the
+    details.
+
+  Formulator Scope
+
+    Formulator is a tool to create and validate web forms in Zope.
+    Formulator takes care of the rendering of the fields in the form,
+    as well as the validation and processing of the data that is
+    submitted. Formulator's scope is limited to forms: "do web forms,
+    web forms only, and web forms well." Formulator does currently not
+    even take care of the precise layout of a form -- each form is
+    layouted differently and thus layout is left to the developer.
+    Formulator does allow for easy integration with external systems,
+    however.
+
+  Creating a Formulator Form and Fields
+
+    It is easy to create a Formulator Form, just pick it from the add
+    list and add it to a folder. I usually only have one form in a
+    folder and call it 'form' to make automatic layout handling
+    easier; I'll say more about the reason for this later.
+
+    The default view of the form looks just like a folder, except that
+    the only things that are addable are Formulator Fields. When you
+    add a field to a form, it'll show up in the Form, just like an
+    object shows up in a normal Zope Folder.
+    
+  Fields
+
+    When you click on a field, you see a list of its properties in the
+    field's 'Edit' screen. This is a good time to explain that
+    Formulator has an extensive help system, and that if you click on
+    'help' in the 'Edit' screen you'll see a list with a short
+    description of what each property does.
+
+    If you click on the 'Test' tab in the Field, you will see the
+    field displayed as it would appear in the form. If you fill in
+    some value in the field and click on the 'Test' button, you can
+    test its validation behavior. If everything could be validated and
+    processed all right, you'll see the resulting value. If it could
+    not be validated however, you see an error, showing the error_key
+    and error_text.
+
+    The best way to learn about what the different fields do and how
+    their properties work is to try them out. Just change some
+    properties and see what happens in the Test screen. And be sure to
+    look at the help.
+
+  Other Form tabs
+
+    The form 'Test' tab is not difficult to explain; it shows all the
+    fields you have added to the form. You can test the behavior of
+    the entire form here. 
+
+    In the 'Order' part you can group fields and order them inside
+    their groups. The order determines the order in which they appear
+    on the 'Test' screen, and can also can be used in your own
+    code. Initially there is only a single 'Default' group, but you
+    can add new groups and change their names.
+  
+    In the 'Settings' tab you can determine the form properties.  You
+    can set the form submit action and method here, which you can
+    later use with the 'header()' and 'footer()' methods of the form.
+
+  Other Field tabs
+
+    The field 'Override' screen allows you to make the field call an
+    override method (most commonly a Python Script) for a property.
+    Instead of using the property value in the 'Edit' screen, the
+    method with the name listed in the override tab will be called to
+    retrieve a value then. The returned value must be the same as the
+    one that property's field generates; for an IntegerField this is
+    an integer, for instance. The titles of overridden fields will be
+    displayed between square brackets ('[ ]') in the 'Edit' screen.
+
+    In the 'Messages' screen you can set the text of the error
+    messages that field can generate upon validation errors.
+
+  On the examples in this HOWTO
+
+    All the examples in this HOWTO are contained in the file
+    'formulator_howto_examples.zexp', which you can download from the
+    Formulator product page
+    (http://www.zope.org/Members/faassen/formulator) and import into
+    your Zope. In the examples, all the forms are called 'form'.
+
+  Rendering a form manually with DTML ('manual' folder)
+
+    First, I will show how to use DTML to manually layout a form. This
+    takes the most work, but also allows the most flexibility. In all
+    these examples I will assume the form is called 'form'.
+
+    The form contains three fields; a StringField 'animal', a
+    StringField 'color', and an IntegerField 'number'. 'index_html' is
+    the DTML Method that does the manual layout::
+   
+      <dtml-var standard_html_header>
+
+      <!-- show the header of the form, using 'Form action' and 
+	'Form method' form settings (<form action="..." method="...">)  
+	-->
+      <dtml-var "form.header()">
+
+      <!-- a simple table for layout purposes -->
+      <table border="0">
+
+      <!-- each field will be on a line by itself -->
+
+      <tr>
+      <!-- first display the title property of the animal field -->
+      <td><dtml-var "form.animal.get_value('title')"></td>
+      <!-- render the field -->
+      <td><dtml-var "form.animal.render()"></td>
+      </tr>
+
+      <!-- the same for the color field -->
+      <tr>
+      <td><dtml-var "form.color.get_value('title')"></td>
+      <td><dtml-var "form.color.render()"></td>
+      </tr>
+
+      <!-- and the number field -->
+      <tr>
+      <td><dtml-var "form.number.get_value('title')"></td>
+      <td><dtml-var "form.number.render()"></td>
+      </tr>
+
+      <!-- the submit button -->
+      <tr>
+      <td><input type="submit" value=" OK "></td>
+      </tr>
+
+      </table>
+
+      <!-- the form footer -->
+      <dtml-var "form.footer()">
+
+      <dtml-var standard_html_footer>
+
+    This shows a form with the three fields. You can easily rearrange
+    the layout just by changing the HTML.
+
+  Rendering a form automatically with DTML ('automatic' folder)
+
+    For many simple forms you don't need to do the layout yourself all
+    the time. We can use Formulator and acquisition to make layout a
+    lot easier. If we know each form is in a separate folder and is
+    called 'form', we can place DTML method in the root of the site
+    that can render any such form. In this example 'index_html' will
+    do the automated rendering directly. In real-world sites you'd
+    usually use another method (for instance called 'form_body') to
+    render because not all folders would contain forms. In that case
+    it'd be easier to put the form rendering code in another method
+    (for instance called 'form_body'), which you can then call from
+    your other code. Here's 'index_html'::
+
+      <dtml-var standard_html_header>
+
+      <!-- show the header of the form, using 'Form action' and 
+	'Form method' form settings (<form action="..." method="...">)  
+	-->
+      <dtml-var "form.header()">
+
+      <!-- a simple table for layout purposes -->
+      <table border="0">
+
+      <!-- get a list of all fields in the form -->
+      <dtml-in "form.get_fields()">
+      <!-- rename each sequence item to 'field' so they can
+	   be used more easily -->
+      <dtml-let field=sequence-item>
+
+      <!-- each field will be on a line by itself -->    
+      <tr>
+      <!-- display the title property of this field -->
+      <td><dtml-var "field.get_value('title')"></td>
+      <!-- render the field -->
+      <td><dtml-var "field.render()"></td>
+      </tr>
+
+      </dtml-let>
+      </dtml-in>
+
+      <!-- the submit button -->
+      <tr>
+      <td><input type="submit" value=" OK "></td>
+      </tr>
+
+      </table>
+
+      <!-- the form footer -->
+      <dtml-var "form.footer()">
+
+      <dtml-var standard_html_footer>
+
+    The nice thing about the automatic approach is that now you can
+    change the Formulator form as much as you like; this code will
+    always automatically display them. Even better, if you add
+    subfolders with forms in them, acquisition makes those forms
+    display automatically as well! If you have only simple forms on a
+    site, this could be the only DTML Method you need.
+
+  Form validation ('validation' folder)
+
+    I will use the same 'index_html' as in the automatic form
+    rendering example and the 'animal/color/number' form to
+    demonstrate form validation.
+
+    I've set the 'Form action' property of the form to 'feedback'.
+    When the form is submitted it, Zope will access the 'feedback'
+    DTML Method. The form data will be coming into 'feedback' in the
+    'REQUEST' object (more precisely the 'REQUEST.form' object).
+
+    The 'feedback' method should do a number of things:
+
+      * validate all fields (tell formulator to take care of this).
+  
+      * handle any validation errors.
+
+      * if there were no validation errors, do something with the
+        form results.
+
+    Here's 'feedback', with comments::
+
+      <dtml-var standard_html_header>
+      <dtml-try>
+	<!-- try the validation, results should be put in
+	     REQUEST (keyed under the field id) --> 
+	<dtml-call "form.validate_all_to_request(REQUEST)">  
+      <dtml-except FormValidationError>
+	<!-- if something went wrong with any field validation,
+	     a FormValidationError will be raised, which we
+	     will then catch here -->
+	<!-- we will display the errors here -->
+	<ul>
+	<dtml-in "error_value.errors">
+	  <li>
+	  <dtml-var "field.get_value('title')">:
+	  <dtml-var error_text>
+	  </li>
+	</dtml-in>
+	</ul>
+
+      <dtml-else>
+	<!-- if no FormValidationError was raised, we're done
+	     with validation and our results will now be in
+	     REQUEST (and in DTML namespace). -->
+
+	<!-- we could do anything with them, but we'll simply
+	     display them -->
+	Hah, you are a <dtml-var color> <dtml-var animal> with
+	<dtml-var number> legs.
+
+      </dtml-try>
+
+      <dtml-var standard_html_footer> 
+    
+    Note that often you can use acquisition with the validation page
+    as well, so you can reuse most of its functionality.
+  
+
+
diff --git a/product/Formulator/help/formulator_motto.txt b/product/Formulator/help/formulator_motto.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3f45280a58b797fabef15da7fcf8de9b4655cd36
--- /dev/null
+++ b/product/Formulator/help/formulator_motto.txt
@@ -0,0 +1,7 @@
+Some mottos to inspire:
+
+  Formulator - Web forms, web forms well, and web forms only
+
+  Von Wiege bis zur Bahre, Formulare, Formulare! (with thanks to
+  Joachim Werner - it means "from crib to coffin, forms, forms!")
+
diff --git a/product/Formulator/homepage.html b/product/Formulator/homepage.html
new file mode 100644
index 0000000000000000000000000000000000000000..021d8da4bd43a91f457cdac3e40459961a581fe4
--- /dev/null
+++ b/product/Formulator/homepage.html
@@ -0,0 +1,49 @@
+Formulator is an extensible framework that eases the creation and
+validation of web forms.
+
+Important links:
+
+  * Subscribe to the <a
+    href="http://lists.sourceforge.net/lists/listinfo/formulator-general">Formulator
+    mailing list</a>, for general discussions and questions on
+    Formulator usage.
+
+  * If you're interested in the further development of Formulator,
+    subscribe to the <a
+    href="http://lists.infrae.com/mailman/listinfo/formulator-dev">Formulator-dev
+    list</a>
+
+  * <a href="http://sourceforge.net/projects/formulator/">Formulator
+    SourceForge project page</a>
+
+  * <a href="http://cvs.infrae.com/Formulator/">Formulator CVS web</a>
+
+  * Check out Formulator from CVS anonymously like this::
+
+    cvs -z3 -d:pserver:anonymous@cvs.infrae.com:/cvs/infrae co Formulator
+
+Important hint:
+
+  *Don't ever* use *field_&lt;fieldname&gt;*; anything
+  prefixed with *field_* in REQUEST is a Formulator
+  implementation detail. Instead, don't forget to validate the form,
+  for instance using *validate_all_to_request()*. See the
+  Formulator API help and Howto for more information. Forgetting to
+  validate the form is the most frequently made Formulator mistake
+  that I've encountered.
+
+Documentation:
+
+  * <a href="formulator_howto">Formulator HOWTO</a>
+  
+  * <a href="http://www.jquade.de./formulator-slides-de">Very nice slides about Formulator by Jens Quade (in German)</a>
+
+  * <a href="http://www.zopelabs.com/cookbook/1032909599">A  Zopelabs recipe by Scott Burton on using Formulator with Zope Page Templates</a>
+
+  * <a href="http://www.zope.org/Members/beno/HowTo/HowTo/Formulator_With_ZPT">Howto  on using Formulator with Zope Page Templates by Beno</a>
+
+Some less important links:
+
+  * <a href="http://freshmeat.net/projects/formulator/">Formulator on Freshmeat</a>
+
+  * <a href="http://www.advogato.org/proj/Formulator/">Formulator project page on Advogato</a>
diff --git a/product/Formulator/tests/README.txt b/product/Formulator/tests/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d8fac69d15441468d9aaf8343e62479c74122b85
--- /dev/null
+++ b/product/Formulator/tests/README.txt
@@ -0,0 +1,2 @@
+This directory now contains some unit tests for Formulator.
+
diff --git a/product/Formulator/tests/__init__.py b/product/Formulator/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..17fb5500b0142f588c0271501d6b7face9f19fc7
--- /dev/null
+++ b/product/Formulator/tests/__init__.py
@@ -0,0 +1 @@
+# this file is here to make 'tests' a module of its own
diff --git a/product/Formulator/tests/test_Form.py b/product/Formulator/tests/test_Form.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8a64b5551503183ea8208feeb9610bf196a4764
--- /dev/null
+++ b/product/Formulator/tests/test_Form.py
@@ -0,0 +1,175 @@
+import unittest, re
+import Zope
+
+# XXX this does not work for zope2.x if x < 3
+# can we fake this? should we do this?
+from Testing import makerequest
+
+from Products.Formulator.Form import ZMIForm
+from Products.Formulator.Errors import ValidationError, FormValidationError
+from Products.Formulator.MethodField import Method
+from Products.Formulator.TALESField import TALESMethod
+
+from Products.PythonScripts.PythonScript import PythonScript
+
+
+""" random assembly testing some reported bugs.
+    This is _not_ a structured or even complete test suite
+"""
+
+class FormTestCase(unittest.TestCase):
+
+    def setUp(self):
+        get_transaction().begin()
+        self.connection = Zope.DB.open()
+        self.root = makerequest.makerequest(
+            self.connection.root()['Application'])
+
+        self.root.manage_addProduct['Formulator'] \
+                 .manage_add('form', 'Test Form')
+        self.form = self.root.form
+
+
+    def tearDown(self):
+        get_transaction().abort()
+        self.connection.close()
+        
+
+    def test_has_field(self):
+        """ test if has_field works, if one asks for a non-field attribute.
+            this has raised AttributeError "aq_explicit" in previous versions
+        """
+        self.failIf(self.form.has_field('title'))
+
+    def _test_list_values(self):
+        """ test if a list of values returned by TALES (override) expressions
+        is interpreted properly.
+        If a TALES tab returns a sequence of items and some item is
+        actually a string of length 2 (e.g. "ok"), this previously
+        has lead to a item text of 'o' and a display value of 'k'
+        (as the this is actually a sequence of length 2 ...)
+         See http://sourceforge.net/mailarchive/forum.php?thread_id=1359918&forum_id=1702
+         
+        Actually the original problem still does not work,
+        as passing a list of int's is not yet supported.
+        If it should, please uncomment the second part of the test.
+        """
+
+        # XXX deactivated: this maybe should not be fixed at all
+
+        self.form.manage_addField('list_field', 'Test List Field', 'ListField')
+
+        # adding a python script to be called by the override tab
+        # FIXME: the following does not work, as the fake-request
+        # does not have a "form" atribute (?)
+        #self.root.manage_addProduct['PythonScripts'] \
+        #         .manage_addPythonScript('override_test', 'Test for override')
+        #
+        #self.root._getOb('override_test').write("return ['ok', 'no']\n")
+
+        self.form.override_test = PythonScript('override_test')
+        self.form.override_test.write("return ['ok', 'no']\n")
+        # ps._makeFunction()
+
+        
+        list_field = getattr(self.form, 'list_field')
+        list_field.values['items'] = [ ('ok', 'ok'), ('no', 'no') ]
+
+        items1 = list_field.render()
+
+        # test TALES
+        list_field.tales['items'] = TALESMethod("python:['ok', 'no']")
+        items2 = list_field.render()
+
+        self.assertEquals(items1, items2)
+
+        # test override
+        del list_field.tales['items']
+        list_field.overrides['items'] = Method('override_test')
+        items2 = list_field.render()
+        
+        self.assertEquals(items1, items2)
+        
+        # test if TALES returns a list of e.g. int
+        #list_field.values['items'] = [ ('42', '42'), ('88', '88') ]
+        #
+        #items1 = list_field.render()
+        #
+        #list_field.tales['items'] = TALESMethod("python:[42, 88]")
+        #items2 = list_field.render()
+        #
+        #self.assertEquals(items1, items2)
+
+    def test_labels(self):
+        self.form.manage_addField(
+            'label_field', 'Test Label Field', 'LabelField')
+
+        self.form.label_field.overrides['default'] = "Some label"
+
+        self.form.manage_addField(
+            'int_field', 'Test integer field', 'IntegerField')
+
+        result = self.form.validate_all(
+            {'field_int_field': '3'})
+        self.assertEquals({'int_field': 3}, result)
+
+
+    def test_datetime_css_class_rendering(self):
+        # test that a bug is fixed, which causing the css_class value
+        # not to be rendered
+        
+        self.form.manage_addProduct['Formulator']\
+                 .manage_addField('date_time','Test Field','DateTimeField')
+        field = self.form.date_time
+        
+        css_matcher = re.compile('class="([^"]*)"')
+
+        # initially no css class is set
+        self.assertEquals(0, len(css_matcher.findall(field.render())))
+
+        # edit the field, bypassing validation ... 
+        field._edit({'css_class':'some_class'})
+
+        # now we should have five matches for the five subfields ...
+        css_matches = css_matcher.findall(field.render())
+        self.assertEquals(5, len(css_matches))
+        # ... and all have the given value:
+        for m in css_matches:
+            self.assertEquals('some_class',m)
+
+        # change the input style: the css needs to be
+        # propagated to the newly created subfields
+        current_style = field['input_style']
+        other_style = {'list':'text', 'text':'list'} [current_style]
+        field._edit({'input_style':other_style})
+        
+        # still the css classes should remain the same
+        css_matches = css_matcher.findall(field.render())
+        self.assertEquals(5, len(css_matches))
+        for m in css_matches:
+            self.assertEquals('some_class',m)
+
+        # now just change to another value:
+        field._edit({'css_class':'other_class'})
+        css_matches = css_matcher.findall(field.render())
+        self.assertEquals(5, len(css_matches))
+        for m in css_matches:
+            self.assertEquals('other_class',m)           
+
+        # and clear the css_class field:
+        field._edit({'css_class':''})
+        css_matches = css_matcher.findall(field.render())
+        self.assertEquals(0, len(css_matches))
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+
+    suite.addTest(unittest.makeSuite(FormTestCase, 'test'))
+    return suite
+
+def main():
+    unittest.TextTestRunner().run(test_suite())
+
+if __name__ == '__main__':
+    main()
diff --git a/product/Formulator/tests/test_all.py b/product/Formulator/tests/test_all.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2e0c1d6ce43446c17e5413ab5805a57a33a5536
--- /dev/null
+++ b/product/Formulator/tests/test_all.py
@@ -0,0 +1,27 @@
+# Copyright (c) 2002 Infrae. All rights reserved.
+# See also LICENSE.txt
+# $Revision: 1.2 $
+import unittest
+import Zope
+
+try:
+    from Zope import startup
+    startup()
+except ImportError:
+    # startup is only in Zope2.6
+    pass
+
+from Products.Formulator.tests import test_Form, test_validators, test_serialize
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(test_Form.test_suite())
+    suite.addTest(test_validators.test_suite())
+    suite.addTest(test_serialize.test_suite())
+    return suite
+
+def main():
+    unittest.TextTestRunner(verbosity=1).run(test_suite())
+
+if __name__ == '__main__':
+    main()
diff --git a/product/Formulator/tests/test_serialize.py b/product/Formulator/tests/test_serialize.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa5157b395acc6bb2a3ee0017a8c7f3b7fd090b3
--- /dev/null
+++ b/product/Formulator/tests/test_serialize.py
@@ -0,0 +1,400 @@
+import unittest
+import Zope
+
+from Products.Formulator.Form import ZMIForm
+from Products.Formulator.XMLToForm import XMLToForm
+from Products.Formulator.FormToXML import formToXML
+
+from Products.Formulator.Errors import ValidationError, FormValidationError
+
+
+class FakeRequest:
+    """ a fake request for testing.
+    Actually we need this only for item acces,
+    and for evaluating to false always, for
+    the manage_XXX methods to not try to render
+    a response.
+    """
+
+    def __init__(self):
+        self.dict = {}
+
+    def __getitem__(self, key):
+        return self.dict[key]
+
+    def __setitem__(self, key, value):
+        self.dict[key] = value
+
+    def get(self, key, default_value):
+        return self.dict.get(key, default_value)
+
+    def update(self, other_dict):
+        self.dict.update(other_dict)
+
+    def clear(self):
+        self.dict.clear()
+
+    def __nonzero__(self):
+        return 0
+
+class SerializeTestCase(unittest.TestCase):
+    def test_simpleSerialize(self):
+        form = ZMIForm('test', 'My test')
+        xml = '''\
+<?xml version="1.0" encoding="iso-8859-1" ?>
+
+<form>
+  <title></title>
+  <name>tab_status_form</name>
+  <action></action>
+  <enctype></enctype>
+  <method></method>
+
+  <groups>
+    <group>
+      <title>Default</title>
+      <fields>
+
+      <field><id>message</id> <type>RawTextAreaField</type>
+        <values>
+          <alternate_name></alternate_name>
+          <hidden type="int">0</hidden>
+          <max_length></max_length>
+          <width type="int">65</width>
+          <external_validator></external_validator>
+          <height type="int">7</height>
+          <required type="int">0</required>
+          <css_class></css_class>
+          <default></default>
+          <title>Message</title>
+          <truncate type="int">0</truncate>
+          <description></description>
+          <extra>wrap="soft"</extra>
+        </values>
+        <tales>
+        </tales>
+      </field>
+      <field><id>publish_datetime</id> <type>DateTimeField</type>
+        <values>
+          <date_only type="int">0</date_only>
+          <alternate_name></alternate_name>
+          <input_style>list</input_style>
+          <hidden type="int">0</hidden>
+          <input_order>dmy</input_order>
+          <time_separator>:</time_separator>
+          <date_separator>/</date_separator>
+          <external_validator></external_validator>
+          <required type="int">0</required>
+          <default_now type="int">0</default_now>
+          <css_class></css_class>
+          <title>Publish time</title>
+          <description></description>
+        </values>
+        <tales>
+          <time_separator>python:form.time_punctuation</time_separator>
+          <date_separator>python:form.date_punctuation</date_separator>
+        </tales>
+      </field>
+      <field><id>expiration_datetime</id> <type>DateTimeField</type>
+        <values>
+          <date_only type="int">0</date_only>
+          <alternate_name></alternate_name>
+          <input_style>list</input_style>
+          <css_class></css_class>
+          <hidden type="int">0</hidden>
+          <input_order>dmy</input_order>
+          <time_separator>:</time_separator>
+          <date_separator>/</date_separator>
+          <external_validator></external_validator>
+          <required type="int">0</required>
+          <default_now type="int">0</default_now>
+          <title>Expiration time</title>
+          <description>If this document should expire, set the time.</description>
+        </values>
+        <tales>
+          <time_separator>python:form.time_punctuation</time_separator>
+          <date_separator>python:form.date_punctuation</date_separator>
+        </tales>
+      </field>
+      <field><id>expires_flag</id> <type>CheckBoxField</type>
+        <values>
+          <alternate_name></alternate_name>
+          <hidden type="int">0</hidden>
+          <css_class></css_class>
+          <default type="int">0</default>
+          <title>Expire flag</title>
+          <description>Turn on expiration time?</description>
+          <external_validator></external_validator>
+          <extra></extra>
+        </values>
+        <tales>
+        </tales>
+      </field>
+      </fields>
+    </group>
+  </groups>
+</form>'''
+        XMLToForm(xml, form)
+        s = formToXML(form)
+        f = open('output1.txt', 'w')
+        f.write(s)
+        f.close()
+        form2 = ZMIForm('another', 'Something')
+        XMLToForm(xml, form2)
+        f = open('output2.txt', 'w')
+        f.write(formToXML(form2))
+        f.close()
+
+
+    def test_escaping(self):
+        """ test if the necessary elements are escaped in the XML.
+        (Actually this test is very incomplete)
+        """
+        form = ZMIForm('test', '<EncodingTest>')
+        # XXX don't test escaping of name, as needs to be javascript
+        # valid anyway?
+        form.name = 'name'
+        form.add_group('a & b')
+
+        form.manage_addField('string_field', '<string> Field', 'StringField')
+        form.manage_addField('int_field', '<int> Field', 'IntegerField')
+        form.manage_addField('float_field', '<Float> Field', 'FloatField')
+        form.manage_addField('date_field', '<Date> Field', 'DateTimeField')
+        form.manage_addField('list_field', '<List> Field', 'ListField')
+        form.manage_addField('multi_field', '<Checkbox> Field', 'MultiCheckBoxField')
+
+        form2 = ZMIForm('test2', 'ValueTest')
+        
+        xml = formToXML(form)
+        XMLToForm(xml, form2)
+
+        for field in form.get_fields():
+            self.assert_(form2.has_field(field.getId()))
+            field2 = getattr(form2, field.getId())
+            # XXX test if values are the same
+            self.assertEquals(field.values, field2.values)
+            # test if default renderings are the same
+            self.assertEquals(field.render(), field2.render())
+
+        self.assertEquals(form.title, form2.title)
+        self.assertEquals(form.name, form2.name)
+        self.assertEquals(form.action, form2.action)
+        self.assertEquals(form.enctype, form2.enctype)
+        self.assertEquals(form.method, form2.method)
+
+        # if we have forgotten something, this will usually remind us ;-)
+        self.assertEquals(form.render(), form2.render())
+
+
+    def test_messages(self):
+        """ test if the error messages are exported
+        """
+        form = ZMIForm('test', '<EncodingTest>')
+        form.manage_addField('int_field', 'int Field', 'IntegerField')
+        
+        form2 = ZMIForm('test2', 'ValueTest')
+        request = FakeRequest()
+        for message_key in form.int_field.get_error_names():
+           request[message_key] = 'test message for error key <%s>' % message_key
+        form.int_field.manage_messages(REQUEST=request)
+        
+        
+        xml = formToXML(form)
+        XMLToForm(xml, form2)
+        # print xml
+
+        request.clear()
+        request['field_int_field'] = 'not a number'
+
+        try:
+            form.validate_all(request)
+            self.fail('form should fail in validation')
+        except FormValidationError, e:
+            self.assertEquals(1, len(e.errors))
+            text1 = e.errors[0].error_text
+
+        try:
+            form2.validate_all(request)
+            self.fail('form2 should fail in validation')
+        except FormValidationError, e:
+            self.assertEquals(1, len(e.errors))
+            text2 = e.errors[0].error_text
+
+        self.assertEquals(text1, text2)
+        
+        
+
+
+    def test_fieldValueTypes(self):
+        """ test checking if the field values are of the proper type.
+        after reading from XML some field values may not have the right type,
+        if they have a special type (currently int and "list").
+        Also tests if rendering and validation are the same
+        between the original form and the one after one form -> xml -> form
+        roundtrip.
+        """
+
+        form = ZMIForm('test', 'ValueTest')
+        form.manage_addField('int_field', 'Test Integer Field', 'IntegerField')
+        form.manage_addField('float_field', 'Test Float Field', 'FloatField')
+        form.manage_addField('date_field', 'Test Date Field', 'DateTimeField')
+        form.manage_addField('list_field', 'Test List Field', 'ListField')
+        form.manage_addField('multi_field', 'Test Checkbox Field', 'MultiCheckBoxField')
+        form.manage_addField('link_field', 'Test Link Field', 'LinkField')
+        form.manage_addField('empty_field', 'Test Empty Field', 'StringField')
+        int_field   = getattr(form, 'int_field')
+        float_field = getattr(form, 'float_field')
+        date_field  = getattr(form, 'date_field')
+        list_field  = getattr(form, 'list_field')
+        multi_field = getattr(form, 'multi_field')
+        link_field = getattr(form, 'link_field')
+        empty_field = getattr(form, 'empty_field')
+   
+        # XXX editing fields by messing with a fake request
+        # -- any better way to do this?
+
+        default_values = {'field_title': 'Test Title',
+                          'field_display_width': '92',
+                          'field_required':'checked',
+                          'field_enabled':'checked',
+                          }
+        try:
+            request = FakeRequest()
+            request.update(default_values)
+            request.update( {'field_default':'42',
+                             'field_enabled':'checked'})
+            int_field.manage_edit(REQUEST=request)
+            
+            request.clear()
+            request.update(default_values)
+            request.update( {'field_default':'1.7'})
+            float_field.manage_edit(REQUEST=request)
+
+            # XXX cannot test "defaults to now", as this may fail randomly
+            request.clear()
+            request.update(default_values)
+            request.update( {'field_input_style':'list',
+                             'field_input_order':'mdy',
+                             'field_date_only':'',
+                             'field_css_class':'test_css',
+                             'field_time_separator':'$'})
+            date_field.manage_edit(REQUEST=request)
+            
+            request.clear()
+            request.update(default_values)
+            request.update( {'field_default':'foo',
+                             'field_size':'1',
+                             'field_items':'Foo | foo\n Bar | bar'})
+            list_field.manage_edit(REQUEST=request)
+
+            request.clear()
+            request.update(default_values)
+            request.update( {'field_default':'foo',
+                             'field_size':'3',
+                             'field_items':'Foo | foo\n Bar | bar\nBaz | baz',
+                             'field_orientation':'horizontal',
+                             'field_view_separator':'<br />\n',
+                             })
+            multi_field.manage_edit(REQUEST=request)
+
+            request.clear()
+            request.update(default_values)
+            request.update( {'field_default':'http://www.absurd.org',
+                             'field_required':'1',
+                             'field_check_timeout':'5.0',
+                             'field_link_type':'external',
+                             })
+            link_field.manage_edit(REQUEST=request)
+
+            request.clear()
+            request.update(default_values)
+            request.update( {'field_default':'None',
+                             'field_required':'',
+                             })
+            empty_field.manage_edit(REQUEST=request)
+
+        except ValidationError, e:
+            self.fail('error when editing field %s; error message: %s' %
+                       (e.field_id, e.error_text) )
+        
+        form2 = ZMIForm('test2', 'ValueTest')
+        
+        xml = formToXML(form)
+        XMLToForm(xml, form2)
+
+        for field in form.get_fields():
+            self.assert_(form2.has_field(field.getId()))
+            field2 = getattr(form2, field.getId())
+            # XXX test if values are the same
+            self.assertEquals(field.values, field2.values)
+            # test if default renderings are the same
+            self.assertEquals(field.render(), field2.render())
+
+        # brute force compare ...
+        self.assertEquals(form.render(), form2.render())
+        request.clear()
+        request['field_int_field'] = '42'
+        request['field_float_field'] = '2.71828'
+        request['subfield_date_field_month'] = '11'
+        request['subfield_date_field_day'] = '11'
+        request['subfield_date_field_year'] = '2011'
+        request['subfield_date_field_hour'] = '09'
+        request['subfield_date_field_minute'] = '59'
+        request['field_list_field'] = 'bar'
+        request['field_multi_field'] = ['bar', 'baz']
+        request['field_link_field'] = 'http://www.zope.org'
+        try:
+            result1 = form.validate_all(request)
+        except FormValidationError, e:
+            # XXX only render first error ...
+            self.fail('error when editing form1, field %s; error message: %s' %
+                       (e.errors[0].field_id, e.errors[0].error_text) )
+
+        try:
+            result2 = form2.validate_all(request)
+        except FormValidationError, e:
+            # XXX only render first error ...
+            self.fail('error when editing form1, field %s; error message: %s' %
+                       (e.errors[0].field_id, e.errors[0].error_text) )
+        self.assertEquals(result1, result2)
+        self.assertEquals(42, result2['int_field'])
+        self.assertEquals(2.71828, result2['float_field'])
+
+	# check link field timeout value
+	self.assertEquals(link_field.get_value('check_timeout'),
+                          form2.link_field.get_value('check_timeout'))
+
+        # XXX not tested: equal form validation failure on invalid input
+        
+        
+
+    def test_emptyGroup(self):
+        """ test bugfix: empty groups are allowed in the XMLForm """
+        form = ZMIForm('test', 'GroupTest')
+        form.add_group('empty')
+        
+        form2 = ZMIForm('test2', 'GroupTestCopy')
+        
+        xml = formToXML(form)
+        XMLToForm(xml, form2)
+        # print xml
+
+        # XXX actually the empty group is not rendered anyway, but
+        # if we get here, we are behind the bug anyway ...
+        self.assertEquals(form.render(), form2.render())
+
+        self.assertEquals(form.get_groups(), form2.get_groups())
+    
+        
+def test_suite():
+    suite = unittest.TestSuite()
+
+    suite.addTest(unittest.makeSuite(SerializeTestCase, 'test'))
+    return suite
+
+def main():
+    unittest.TextTestRunner().run(test_suite())
+
+if __name__ == '__main__':
+    main()
+    
diff --git a/product/Formulator/tests/test_validators.py b/product/Formulator/tests/test_validators.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2da6b2b12035586f821cf4f245061ae793d5f43
--- /dev/null
+++ b/product/Formulator/tests/test_validators.py
@@ -0,0 +1,466 @@
+import unittest
+import ZODB
+import OFS.Application
+from Products.Formulator import Validator
+from Products.Formulator.StandardFields import DateTimeField
+
+class TestField:
+    def __init__(self, id, **kw):
+        self.id = id
+        self.kw = kw
+
+    def get_value(self, name):
+        # XXX hack
+        return self.kw.get(name, 0)
+
+    def get_error_message(self, key):
+        return "nothing"
+
+    def get_form_encoding(self):
+        # XXX fake ... what if installed python does not support utf-8?
+        return "utf-8"
+
+class ValidatorTestCase(unittest.TestCase):
+    def assertValidatorRaises(self, exception, error_key, f, *args, **kw):
+        try:
+            apply(f, args, kw)
+        except Validator.ValidationError, e:
+            if e.error_key != error_key:
+                self.fail('Got wrong error. Expected %s received %s' %
+                          (error_key, e))
+            else:
+                return
+        self.fail('Expected error %s but no error received.' % error_key)
+
+class StringValidatorTestCase(ValidatorTestCase):
+    def setUp(self):
+        self.v = Validator.StringValidatorInstance
+
+    def tearDown(self):
+        pass
+        
+    def test_basic(self):
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0, required=0, unicode=0),
+            'f', {'f' : 'foo'})
+        self.assertEqual('foo', result)
+
+    def test_htmlquotes(self):
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0, required=0, unicode=0),
+            'f', {'f' : '<html>'})
+        self.assertEqual('<html>', result)
+
+    def test_encoding(self):
+        utf8_string = 'M\303\274ller' # this is a M&uuml;ller
+        unicode_string = unicode(utf8_string, 'utf-8')
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0, required=0, unicode=1),
+            'f', {'f' : utf8_string})
+        self.assertEqual(unicode_string, result)
+
+    def test_strip_whitespace(self):
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0, required=0, unicode=0),
+            'f', {'f' : ' foo  '})
+        self.assertEqual('foo', result)
+
+    def test_error_too_long(self):
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'too_long',
+            self.v.validate,
+            TestField('f', max_length=10, truncate=0, required=0, unicode=0),
+            'f', {'f' : 'this is way too long'})
+        
+    def test_error_truncate(self):
+        result = self.v.validate(
+            TestField('f', max_length=10, truncate=1, required=0, unicode=0),
+            'f', {'f' : 'this is way too long'})
+        self.assertEqual('this is way too long'[:10], result)
+
+    def test_error_required_not_found(self):
+        # empty string
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'required_not_found',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1, unicode=0),
+            'f', {'f': ''})
+        # whitespace only
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'required_not_found',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1, unicode=0),
+            'f', {'f': '   '})
+        # not in dict
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'required_not_found',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1, unicode=0),
+            'f', {})
+
+    def test_whitespace_preserve(self):
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0, required=0, unicode=0,
+                      whitespace_preserve=1),
+            'f', {'f' : ' '})
+        self.assertEqual(' ', result)
+
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0, required=0, unicode=0,
+                      whitespace_preserve=0),
+            'f', {'f' : ' '})
+        self.assertEqual('', result)
+
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0, required=0, unicode=0,
+                      whitespace_preserve=1),
+            'f', {'f' : ' foo '})
+        self.assertEqual(' foo ', result)
+        
+class EmailValidatorTestCase(ValidatorTestCase):
+     
+    def setUp(self):
+        self.v = Validator.EmailValidatorInstance
+        
+    def test_basic(self):
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0, required=1, unicode=0),
+            'f', {'f': 'foo@bar.com'})
+        self.assertEquals('foo@bar.com', result)
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0, required=1, unicode=0),
+            'f', {'f': 'm.faassen@vet.uu.nl'})
+        self.assertEquals('m.faassen@vet.uu.nl', result) 
+
+    def test_error_not_email(self):
+        # a few wrong email addresses should raise error
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'not_email',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1, unicode=0),
+            'f', {'f': 'foo@bar.com.'})
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'not_email',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1, unicode=0),
+            'f', {'f': '@bar.com'})
+        
+    def test_error_required_not_found(self):
+        # empty string
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'required_not_found',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1, unicode=0),
+            'f', {'f': ''})
+
+# skip PatternValidator for now
+
+class BooleanValidatorTestCase(ValidatorTestCase):
+    def setUp(self):
+        self.v = Validator.BooleanValidatorInstance
+        
+    def tearDown(self):
+        pass
+
+    def test_basic(self):
+        result = self.v.validate(
+            TestField('f'),
+            'f', {'f': ''})
+        self.assertEquals(0, result)
+        result = self.v.validate(
+            TestField('f'),
+            'f', {'f': 1})
+        self.assertEquals(1, result)
+        result = self.v.validate(
+            TestField('f'),
+            'f', {'f': 0})
+        self.assertEquals(0, result)
+        result = self.v.validate(
+            TestField('f'),
+            'f', {})
+        self.assertEquals(0, result)
+
+class IntegerValidatorTestCase(ValidatorTestCase):
+    def setUp(self):
+        self.v = Validator.IntegerValidatorInstance
+
+    def test_basic(self):
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0,
+                      required=0, start="", end=""),
+            'f', {'f': '15'})
+        self.assertEquals(15, result)  
+
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0,
+                      required=0, start="", end=""),
+            'f', {'f': '0'})
+        self.assertEquals(0, result)
+
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0,
+                      required=0, start="", end=""),
+            'f', {'f': '-1'})
+        self.assertEquals(-1, result)
+        
+    def test_no_entry(self):
+        # result should be empty string if nothing entered
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0,
+                      required=0, start="", end=""),
+            'f', {'f': ''})
+        self.assertEquals("", result)
+
+    def test_ranges(self):
+        # first check whether everything that should be in range is
+        # in range
+        for i in range(0, 100):
+            result = self.v.validate(
+                TestField('f', max_length=0, truncate=0, required=1,
+                          start=0, end=100),
+                'f', {'f': str(i)})
+            self.assertEquals(i, result)
+        # now check out of range errors
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'integer_out_of_range',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start=0, end=100),
+            'f', {'f': '100'})
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'integer_out_of_range',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start=0, end=100),
+            'f', {'f': '200'})
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'integer_out_of_range',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start=0, end=100),
+            'f', {'f': '-10'})
+        # check some weird ranges
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'integer_out_of_range',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start=10, end=10),
+            'f', {'f': '10'})
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'integer_out_of_range',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start=0, end=0),
+            'f', {'f': '0'})
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'integer_out_of_range',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start=0, end=-10),
+            'f', {'f': '-1'})
+        
+    def test_error_not_integer(self):
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'not_integer',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start="", end=""),
+            'f', {'f': 'foo'})
+        
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'not_integer',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start="", end=""),
+            'f', {'f': '1.0'})
+
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'not_integer',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start="", end=""),
+            'f', {'f': '1e'})
+
+    def test_error_required_not_found(self):   
+        # empty string
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'required_not_found',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start="", end=""),
+            'f', {'f': ''})
+        # whitespace only
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'required_not_found',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start="", end=""),
+            'f', {'f': '   '})
+        # not in dict
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'required_not_found',
+            self.v.validate,
+            TestField('f', max_length=0, truncate=0, required=1,
+                      start="", end=""),
+            'f', {})
+
+class FloatValidatorTestCase(ValidatorTestCase):
+    def setUp(self):
+        self.v = Validator.FloatValidatorInstance
+
+    def test_basic(self):
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0,
+                      required=0),
+            'f', {'f': '15.5'})
+        self.assertEqual(15.5, result)
+
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0,
+                      required=0),
+            'f', {'f': '15.0'})
+        self.assertEqual(15.0, result)
+
+        result = self.v.validate(
+            TestField('f', max_length=0, truncate=0,
+                      required=0),
+            'f', {'f': '15'})
+        self.assertEqual(15.0, result)
+
+    def test_error_not_float(self):
+        self.assertValidatorRaises(
+           Validator.ValidationError, 'not_float',
+           self.v.validate,
+           TestField('f', max_length=0, truncate=0, required=1),
+           'f', {'f': '1f'})
+
+class DateTimeValidatorTestCase(ValidatorTestCase):
+    def setUp(self):
+        self.v = Validator.DateTimeValidatorInstance
+        
+    def test_normal(self):
+        result = self.v.validate(
+            DateTimeField('f'),
+            'f', {'subfield_f_year': '2002',
+                  'subfield_f_month': '12',
+                  'subfield_f_day': '1',
+                  'subfield_f_hour': '10',
+                  'subfield_f_minute': '30'})
+        self.assertEquals(2002, result.year())
+        self.assertEquals(12, result.month())
+        self.assertEquals(1, result.day())
+        self.assertEquals(10, result.hour())
+        self.assertEquals(30, result.minute())
+
+    def test_ampm(self):
+        result = self.v.validate(
+            DateTimeField('f', ampm_time_style=1),
+            'f', {'subfield_f_year': '2002',
+                  'subfield_f_month': '12',
+                  'subfield_f_day': '1',
+                  'subfield_f_hour': '10',
+                  'subfield_f_minute': '30',
+                  'subfield_f_ampm': 'am'})
+        self.assertEquals(2002, result.year())
+        self.assertEquals(12, result.month())
+        self.assertEquals(1, result.day())
+        self.assertEquals(10, result.hour())
+        self.assertEquals(30, result.minute())
+
+        result = self.v.validate(
+            DateTimeField('f', ampm_time_style=1),
+            'f', {'subfield_f_year': '2002',
+                  'subfield_f_month': '12',
+                  'subfield_f_day': '1',
+                  'subfield_f_hour': '10',
+                  'subfield_f_minute': '30',
+                  'subfield_f_ampm': 'pm'})
+        self.assertEquals(2002, result.year())
+        self.assertEquals(12, result.month())
+        self.assertEquals(1, result.day())
+        self.assertEquals(22, result.hour())
+        self.assertEquals(30, result.minute())
+        
+        self.assertValidatorRaises(
+            Validator.ValidationError, 'not_datetime',
+            self.v.validate,
+            DateTimeField('f', ampm_time_style=1),
+            'f', {'subfield_f_year': '2002',
+                  'subfield_f_month': '12',
+                  'subfield_f_day': '1',
+                  'subfield_f_hour': '10',
+                  'subfield_f_minute': '30'})
+
+    def test_date_only(self):
+        result = self.v.validate(
+            DateTimeField('f', date_only=1),
+            'f', {'subfield_f_year': '2002',
+                  'subfield_f_month': '12',
+                  'subfield_f_day': '1'})
+        self.assertEquals(2002, result.year())
+        self.assertEquals(12, result.month())
+        self.assertEquals(1, result.day())
+        self.assertEquals(0, result.hour())
+        self.assertEquals(0, result.minute())
+
+        result = self.v.validate(
+            DateTimeField('f', date_only=1),
+            'f', {'subfield_f_year': '2002',
+                  'subfield_f_month': '12',
+                  'subfield_f_day': '1',
+                  'subfield_f_hour': '10',
+                  'subfield_f_minute': '30'})
+        self.assertEquals(2002, result.year())
+        self.assertEquals(12, result.month())
+        self.assertEquals(1, result.day())
+        self.assertEquals(0, result.hour())
+        self.assertEquals(0, result.minute())
+
+    def test_allow_empty_time(self):
+        result = self.v.validate(
+            DateTimeField('f', allow_empty_time=1),
+            'f', {'subfield_f_year': '2002',
+                  'subfield_f_month': '12',
+                  'subfield_f_day': '1'})
+        self.assertEquals(2002, result.year())
+        self.assertEquals(12, result.month())
+        self.assertEquals(1, result.day())
+        self.assertEquals(0, result.hour())
+        self.assertEquals(0, result.minute())
+
+        result = self.v.validate(
+            DateTimeField('f', allow_empty_time=1),
+            'f', {'subfield_f_year': '2002',
+                  'subfield_f_month': '12',
+                  'subfield_f_day': '1',
+                  'subfield_f_hour': '10',
+                  'subfield_f_minute': '30'})
+        self.assertEquals(2002, result.year())
+        self.assertEquals(12, result.month())
+        self.assertEquals(1, result.day())
+        self.assertEquals(10, result.hour())
+        self.assertEquals(30, result.minute())
+
+    def test_allow_empty_time2(self):
+        result = self.v.validate(
+            DateTimeField('f', allow_empty_time=1, required=0), 'f', {})
+        self.assertEquals(None, result)
+        
+def test_suite():
+    suite = unittest.TestSuite()
+
+    suite.addTest(unittest.makeSuite(StringValidatorTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(EmailValidatorTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(BooleanValidatorTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(IntegerValidatorTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(FloatValidatorTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(DateTimeValidatorTestCase, 'test'))
+    
+    return suite
+
+def main():
+    unittest.TextTestRunner().run(test_suite())
+
+if __name__ == '__main__':
+    main()
+    
diff --git a/product/Formulator/version.txt b/product/Formulator/version.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5ab86037439f04f4509351121894fac5ab668485
--- /dev/null
+++ b/product/Formulator/version.txt
@@ -0,0 +1 @@
+Formulator 1.6.1
diff --git a/product/Formulator/www/BasicField.gif b/product/Formulator/www/BasicField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..bf36aa7d32c59249021ee0a9e563e10806e0de30
Binary files /dev/null and b/product/Formulator/www/BasicField.gif differ
diff --git a/product/Formulator/www/CheckBoxField.gif b/product/Formulator/www/CheckBoxField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..b34917af9ec1b706818ce44ff1c4180e78c62d44
Binary files /dev/null and b/product/Formulator/www/CheckBoxField.gif differ
diff --git a/product/Formulator/www/DateTimeField.gif b/product/Formulator/www/DateTimeField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..d8d1b52e1b24b59eb151a5a9192fe33302ba0892
Binary files /dev/null and b/product/Formulator/www/DateTimeField.gif differ
diff --git a/product/Formulator/www/EmailField.gif b/product/Formulator/www/EmailField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6d3157a71b39344c5dc28ec70cbf8ade957bcc13
Binary files /dev/null and b/product/Formulator/www/EmailField.gif differ
diff --git a/product/Formulator/www/FileField.gif b/product/Formulator/www/FileField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..ab0b9696a16c5e70163d56e55d1a58a38edad1e2
Binary files /dev/null and b/product/Formulator/www/FileField.gif differ
diff --git a/product/Formulator/www/FloatField.gif b/product/Formulator/www/FloatField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6eb2450049e59767c263a821ce3c1de44d052ac0
Binary files /dev/null and b/product/Formulator/www/FloatField.gif differ
diff --git a/product/Formulator/www/Form.gif b/product/Formulator/www/Form.gif
new file mode 100644
index 0000000000000000000000000000000000000000..d402b282620c226e2dfafda51cb5c9cdebbec360
Binary files /dev/null and b/product/Formulator/www/Form.gif differ
diff --git a/product/Formulator/www/IntegerField.gif b/product/Formulator/www/IntegerField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..0f4d61745d6f4a40228ad17b5761f5db1f663941
Binary files /dev/null and b/product/Formulator/www/IntegerField.gif differ
diff --git a/product/Formulator/www/LabelField.gif b/product/Formulator/www/LabelField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..bf36aa7d32c59249021ee0a9e563e10806e0de30
Binary files /dev/null and b/product/Formulator/www/LabelField.gif differ
diff --git a/product/Formulator/www/LinesField.gif b/product/Formulator/www/LinesField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..b39a3d47c688369849419229239cde39c4bf4a90
Binary files /dev/null and b/product/Formulator/www/LinesField.gif differ
diff --git a/product/Formulator/www/LinkField.gif b/product/Formulator/www/LinkField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..e18c597131b07fc949a8d9400c296422f31e6e7d
Binary files /dev/null and b/product/Formulator/www/LinkField.gif differ
diff --git a/product/Formulator/www/ListField.gif b/product/Formulator/www/ListField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..31fcd78323cd09dc68a332f5d22ff66da592512c
Binary files /dev/null and b/product/Formulator/www/ListField.gif differ
diff --git a/product/Formulator/www/MethodField.gif b/product/Formulator/www/MethodField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..bdeb79acbb258f7bcbef38eef3d911bc7934f899
Binary files /dev/null and b/product/Formulator/www/MethodField.gif differ
diff --git a/product/Formulator/www/MultiCheckBoxField.gif b/product/Formulator/www/MultiCheckBoxField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..233094d8dc5296eedb8ec91a8ed77f4505cff24e
Binary files /dev/null and b/product/Formulator/www/MultiCheckBoxField.gif differ
diff --git a/product/Formulator/www/MultiListField.gif b/product/Formulator/www/MultiListField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..7a82b8cf9270dd4938df3d1e2af0cc81d146b59e
Binary files /dev/null and b/product/Formulator/www/MultiListField.gif differ
diff --git a/product/Formulator/www/MultipleListField.gif b/product/Formulator/www/MultipleListField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..33067287fbd057adc2ac65baaea31f0b36618007
Binary files /dev/null and b/product/Formulator/www/MultipleListField.gif differ
diff --git a/product/Formulator/www/PasswordField.gif b/product/Formulator/www/PasswordField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..3af5e028c0d6184a15bcb46b09f19f73f5f23242
Binary files /dev/null and b/product/Formulator/www/PasswordField.gif differ
diff --git a/product/Formulator/www/PatternField.gif b/product/Formulator/www/PatternField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..1bf1f4ecf1cd0894343dc46e043642aca02c4b7a
Binary files /dev/null and b/product/Formulator/www/PatternField.gif differ
diff --git a/product/Formulator/www/RadioField.gif b/product/Formulator/www/RadioField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..9941e245e85f9015d8598ef286f68b6558451978
Binary files /dev/null and b/product/Formulator/www/RadioField.gif differ
diff --git a/product/Formulator/www/RangedIntegerField.gif b/product/Formulator/www/RangedIntegerField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..8731abf91f850343f7dcf9b1ce33cf2dbc725bea
Binary files /dev/null and b/product/Formulator/www/RangedIntegerField.gif differ
diff --git a/product/Formulator/www/StringField.gif b/product/Formulator/www/StringField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..bf36aa7d32c59249021ee0a9e563e10806e0de30
Binary files /dev/null and b/product/Formulator/www/StringField.gif differ
diff --git a/product/Formulator/www/TextAreaField.gif b/product/Formulator/www/TextAreaField.gif
new file mode 100644
index 0000000000000000000000000000000000000000..8ffd9c45b8254f89788577f2da5f15f6c4f7d5fd
Binary files /dev/null and b/product/Formulator/www/TextAreaField.gif differ