Index: src/sas/sascalc/fit/pagestate.py
===================================================================
--- src/sas/sascalc/fit/pagestate.py	(revision 2a0f33fc9cc1c4ed278b92f004e84ba596199882)
+++ src/sas/sascalc/fit/pagestate.py	(revision 2a0f33fc9cc1c4ed278b92f004e84ba596199882)
@@ -0,0 +1,1391 @@
+"""
+    Class that holds a fit page state
+"""
+# TODO: Refactor code so we don't need to use getattr/setattr
+################################################################################
+# This software was developed by the University of Tennessee as part of the
+# Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+# project funded by the US National Science Foundation.
+#
+# See the license text in license.txt
+#
+# copyright 2009, University of Tennessee
+################################################################################
+import time
+import os
+import sys
+import copy
+import logging
+import numpy as np
+import traceback
+
+import xml.dom.minidom
+from xml.dom.minidom import parseString
+from lxml import etree
+
+from sasmodels import convert
+import sasmodels.weights
+
+import sas.sascalc.dataloader
+from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
+from sas.sascalc.dataloader.readers.cansas_reader import get_content, write_node
+from sas.sascalc.dataloader.data_info import Data2D, Collimation, Detector
+from sas.sascalc.dataloader.data_info import Process, Aperture
+
+logger = logging.getLogger(__name__)
+
+# Information to read/write state as xml
+FITTING_NODE_NAME = 'fitting_plug_in'
+CANSAS_NS = "cansas1d/1.0"
+
+CUSTOM_MODEL = 'Plugin Models'
+CUSTOM_MODEL_OLD = 'Customized Models'
+
+LIST_OF_DATA_ATTRIBUTES = [["is_data", "is_data", "bool"],
+                           ["group_id", "data_group_id", "string"],
+                           ["data_name", "data_name", "string"],
+                           ["data_id", "data_id", "string"],
+                           ["name", "name", "string"],
+                           ["data_name", "data_name", "string"]]
+LIST_OF_STATE_ATTRIBUTES = [["qmin", "qmin", "float"],
+                            ["qmax", "qmax", "float"],
+                            ["npts", "npts", "float"],
+                            ["categorycombobox", "categorycombobox", "string"],
+                            ["formfactorcombobox", "formfactorcombobox",
+                             "string"],
+                            ["structurecombobox", "structurecombobox",
+                             "string"],
+                            ["multi_factor", "multi_factor", "float"],
+                            ["magnetic_on", "magnetic_on", "bool"],
+                            ["enable_smearer", "enable_smearer", "bool"],
+                            ["disable_smearer", "disable_smearer", "bool"],
+                            ["pinhole_smearer", "pinhole_smearer", "bool"],
+                            ["slit_smearer", "slit_smearer", "bool"],
+                            ["enable_disp", "enable_disp", "bool"],
+                            ["disable_disp", "disable_disp", "bool"],
+                            ["dI_noweight", "dI_noweight", "bool"],
+                            ["dI_didata", "dI_didata", "bool"],
+                            ["dI_sqrdata", "dI_sqrdata", "bool"],
+                            ["dI_idata", "dI_idata", "bool"],
+                            ["enable2D", "enable2D", "bool"],
+                            ["cb1", "cb1", "bool"],
+                            ["tcChi", "tcChi", "float"],
+                            ["smearer", "smearer", "float"],
+                            ["smear_type", "smear_type", "string"],
+                            ["dq_l", "dq_l", "float"],
+                            ["dq_r", "dq_r", "float"],
+                            ["dx_percent", "dx_percent", "float"],
+                            ["dxl", "dxl", "float"],
+                            ["dxw", "dxw", "float"]]
+
+LIST_OF_MODEL_ATTRIBUTES = [["values", "values"],
+                            ["weights", "weights"]]
+
+DISPERSION_LIST = [["disp_obj_dict", "_disp_obj_dict", "string"]]
+
+LIST_OF_STATE_PARAMETERS = [["parameters", "parameters"],
+                            ["str_parameters", "str_parameters"],
+                            ["orientation_parameters", "orientation_params"],
+                            ["dispersity_parameters",
+                             "orientation_params_disp"],
+                            ["fixed_param", "fixed_param"],
+                            ["fittable_param", "fittable_param"]]
+LIST_OF_DATA_2D_ATTR = [["xmin", "xmin", "float"],
+                        ["xmax", "xmax", "float"],
+                        ["ymin", "ymin", "float"],
+                        ["ymax", "ymax", "float"],
+                        ["_xaxis", "_xaxis", "string"],
+                        ["_xunit", "_xunit", "string"],
+                        ["_yaxis", "_yaxis", "string"],
+                        ["_yunit", "_yunit", "string"],
+                        ["_zaxis", "_zaxis", "string"],
+                        ["_zunit", "_zunit", "string"]]
+LIST_OF_DATA_2D_VALUES = [["qx_data", "qx_data", "float"],
+                          ["qy_data", "qy_data", "float"],
+                          ["dqx_data", "dqx_data", "float"],
+                          ["dqy_data", "dqy_data", "float"],
+                          ["data", "data", "float"],
+                          ["q_data", "q_data", "float"],
+                          ["err_data", "err_data", "float"],
+                          ["mask", "mask", "bool"]]
+
+
+def parse_entry_helper(node, item):
+    """
+    Create a numpy list from value extrated from the node
+
+    :param node: node from each the value is stored
+    :param item: list name of three strings.the two first are name of data
+        attribute and the third one is the type of the value of that
+        attribute. type can be string, float, bool, etc.
+
+    : return: numpy array
+    """
+    if node is not None:
+        if item[2] == "string":
+            return str(node.get(item[0]).strip())
+        elif item[2] == "bool":
+            try:
+                return node.get(item[0]).strip() == "True"
+            except Exception:
+                return None
+        else:
+            try:
+                return float(node.get(item[0]))
+            except Exception:
+                return None
+
+
+class PageState(object):
+    """
+    Contains information to reconstruct a page of the fitpanel.
+    """
+    def __init__(self, parent=None, model=None, data=None):
+        """
+        Initialize the current state
+
+        :param model: a selected model within a page
+        :param data:
+
+        """
+        self.file = None
+        # Time of state creation
+        self.timestamp = time.time()
+        # Data member to store the dispersion object created
+        self._disp_obj_dict = {}
+        # ------------------------
+        # Data used for fitting
+        self.data = data
+        # model data
+        self.theory_data = None
+        # Is 2D
+        self.is_2D = False
+        self.images = None
+
+        # save additional information on data that dataloader.reader
+        # does not read
+        self.is_data = None
+        self.data_name = ""
+
+        if self.data is not None:
+            self.data_name = self.data.name
+        self.data_id = None
+        if self.data is not None and hasattr(self.data, "id"):
+            self.data_id = self.data.id
+        self.data_group_id = None
+        if self.data is not None and hasattr(self.data, "group_id"):
+            self.data_group_id = self.data.group_id
+
+        # reset True change the state of existing button
+        self.reset = False
+
+        # flag to allow data2D plot
+        self.enable2D = False
+        # model on which the fit would be performed
+        self.model = model
+        self.m_name = None
+        # list of process done to model
+        self.process = []
+        # fit page manager
+        self.manager = None
+        # Store the parent of this panel parent
+        # For this application fitpanel is the parent
+        self.parent = parent
+        # Event_owner is the owner of model event
+        self.event_owner = None
+        # page name
+        self.page_name = ""
+        # Contains link between model, its parameters, and panel organization
+        self.parameters = []
+        # String parameter list that can not be fitted
+        self.str_parameters = []
+        # Contains list of parameters that cannot be fitted and reference to
+        # panel objects
+        self.fixed_param = []
+        # Contains list of parameters with dispersity and reference to
+        # panel objects
+        self.fittable_param = []
+        # orientation parameters
+        self.orientation_params = []
+        # orientation parameters for gaussian dispersity
+        self.orientation_params_disp = []
+        # smearer info
+        self.smearer = None
+        self.smear_type = None
+        self.dq_l = None
+        self.dq_r = None
+        self.dx_percent = None
+        self.dx_old = False
+        self.dxl = None
+        self.dxw = None
+        # list of dispersion parameters
+        self.disp_list = []
+        if self.model is not None:
+            self.disp_list = self.model.getDispParamList()
+
+        self.disp_cb_dict = {}
+        self.values = {}
+        self.weights = {}
+
+        # contains link between a model and selected parameters to fit
+        self.param_toFit = []
+        # dictionary of model type and model class
+        self.model_list_box = None
+        # save the state of the context menu
+        self.saved_states = {}
+        # save selection of combobox
+        self.formfactorcombobox = None
+        self.categorycombobox = None
+        self.structurecombobox = None
+
+        # radio box to select type of model
+        # self.shape_rbutton = False
+        # self.shape_indep_rbutton = False
+        # self.struct_rbutton = False
+        # self.plugin_rbutton = False
+        # the indice of the current selection
+        self.disp_box = 0
+        # Qrange
+        # Q range
+        self.qmin = 0.001
+        self.qmax = 0.1
+        # reset data range
+        self.qmax_x = None
+        self.qmin_x = None
+
+        self.npts = None
+        self.name = ""
+        self.multi_factor = None
+        self.magnetic_on = False
+        # enable smearering state
+        self.enable_smearer = False
+        self.disable_smearer = True
+        self.pinhole_smearer = False
+        self.slit_smearer = False
+        # weighting options
+        self.dI_noweight = False
+        self.dI_didata = True
+        self.dI_sqrdata = False
+        self.dI_idata = False
+        # disperity selection
+        self.enable_disp = False
+        self.disable_disp = True
+
+        # state of selected all check button
+        self.cb1 = False
+        # store value of chisqr
+        self.tcChi = None
+        self.version = (1,0,0)
+
+    def clone(self):
+        """
+        Create a new copy of the current object
+        """
+        model = None
+        if self.model is not None:
+            model = self.model.clone()
+            model.name = self.model.name
+        obj = PageState(self.parent, model=model)
+        obj.file = copy.deepcopy(self.file)
+        obj.data = copy.deepcopy(self.data)
+        if self.data is not None:
+            self.data_name = self.data.name
+        obj.data_name = self.data_name
+        obj.is_data = self.is_data
+        obj.model_list_box = copy.deepcopy(self.model_list_box)
+
+        obj.categorycombobox = self.categorycombobox
+        obj.formfactorcombobox = self.formfactorcombobox
+        obj.structurecombobox = self.structurecombobox
+
+        # obj.shape_rbutton = self.shape_rbutton
+        # obj.shape_indep_rbutton = self.shape_indep_rbutton
+        # obj.struct_rbutton = self.struct_rbutton
+        # obj.plugin_rbutton = self.plugin_rbutton
+
+        obj.manager = self.manager
+        obj.event_owner = self.event_owner
+        obj.disp_list = copy.deepcopy(self.disp_list)
+
+        obj.enable2D = copy.deepcopy(self.enable2D)
+        obj.parameters = copy.deepcopy(self.parameters)
+        obj.str_parameters = copy.deepcopy(self.str_parameters)
+        obj.fixed_param = copy.deepcopy(self.fixed_param)
+        obj.fittable_param = copy.deepcopy(self.fittable_param)
+        obj.orientation_params = copy.deepcopy(self.orientation_params)
+        obj.orientation_params_disp = \
+            copy.deepcopy(self.orientation_params_disp)
+        obj.enable_disp = copy.deepcopy(self.enable_disp)
+        obj.disable_disp = copy.deepcopy(self.disable_disp)
+        obj.tcChi = self.tcChi
+
+        if len(self._disp_obj_dict) > 0:
+            for k, v in self._disp_obj_dict.iteritems():
+                obj._disp_obj_dict[k] = v
+        if len(self.disp_cb_dict) > 0:
+            for k, v in self.disp_cb_dict.iteritems():
+                obj.disp_cb_dict[k] = v
+        if len(self.values) > 0:
+            for k, v in self.values.iteritems():
+                obj.values[k] = v
+        if len(self.weights) > 0:
+            for k, v in self.weights.iteritems():
+                obj.weights[k] = v
+        obj.enable_smearer = copy.deepcopy(self.enable_smearer)
+        obj.disable_smearer = copy.deepcopy(self.disable_smearer)
+        obj.pinhole_smearer = copy.deepcopy(self.pinhole_smearer)
+        obj.slit_smearer = copy.deepcopy(self.slit_smearer)
+        obj.smear_type = copy.deepcopy(self.smear_type)
+        obj.dI_noweight = copy.deepcopy(self.dI_noweight)
+        obj.dI_didata = copy.deepcopy(self.dI_didata)
+        obj.dI_sqrdata = copy.deepcopy(self.dI_sqrdata)
+        obj.dI_idata = copy.deepcopy(self.dI_idata)
+        obj.dq_l = copy.deepcopy(self.dq_l)
+        obj.dq_r = copy.deepcopy(self.dq_r)
+        obj.dx_percent = copy.deepcopy(self.dx_percent)
+        obj.dx_old = copy.deepcopy(self.dx_old)
+        obj.dxl = copy.deepcopy(self.dxl)
+        obj.dxw = copy.deepcopy(self.dxw)
+        obj.disp_box = copy.deepcopy(self.disp_box)
+        obj.qmin = copy.deepcopy(self.qmin)
+        obj.qmax = copy.deepcopy(self.qmax)
+        obj.multi_factor = self.multi_factor
+        obj.magnetic_on = self.magnetic_on
+        obj.npts = copy.deepcopy(self.npts)
+        obj.cb1 = copy.deepcopy(self.cb1)
+        obj.smearer = copy.deepcopy(self.smearer)
+        obj.version = copy.deepcopy(self.version)
+
+        for name, state in self.saved_states.iteritems():
+            copy_name = copy.deepcopy(name)
+            copy_state = state.clone()
+            obj.saved_states[copy_name] = copy_state
+        return obj
+
+    def _old_first_model(self):
+        """
+        Handle save states from 4.0.1 and before where the first item in the
+        selection boxes of category, formfactor and structurefactor were not
+        saved.
+        :return: None
+        """
+        if self.categorycombobox == CUSTOM_MODEL_OLD:
+            self.categorycombobox = CUSTOM_MODEL
+        if self.formfactorcombobox == '':
+            FIRST_FORM = {
+                'Shapes' : 'BCCrystalModel',
+                'Uncategorized' : 'LineModel',
+                'StructureFactor' : 'HardsphereStructure',
+                'Ellipsoid' : 'core_shell_ellipsoid',
+                'Lamellae' : 'lamellar',
+                'Paracrystal' : 'bcc_paracrystal',
+                'Parallelepiped' : 'core_shell_parallelepiped',
+                'Shape Independent' : 'be_polyelectrolyte',
+                'Sphere' : 'adsorbed_layer',
+                'Structure Factor' : 'hardsphere',
+                CUSTOM_MODEL : ''
+            }
+            if self.categorycombobox == '':
+                if len(self.parameters) == 3:
+                    self.categorycombobox = "Shape-Independent"
+                    self.formfactorcombobox = 'PowerLawAbsModel'
+                elif len(self.parameters) == 9:
+                    self.categorycombobox = 'Cylinder'
+                    self.formfactorcombobox = 'barbell'
+                else:
+                    msg = "Save state does not have enough information to load"
+                    msg += " the all of the data."
+                    logger.warning(msg=msg)
+            else:
+                self.formfactorcombobox = FIRST_FORM[self.categorycombobox]
+
+    @staticmethod
+    def param_remap_to_sasmodels_convert(params, is_string=False):
+        """
+        Remaps the parameters for sasmodels conversion
+
+        :param params: list of parameters (likely self.parameters)
+        :return: remapped dictionary of parameters
+        """
+        p = dict()
+        for fittable, name, value, _, uncert, lower, upper, units in params:
+            if not value:
+                value = np.nan
+            if not uncert or uncert[1] == '' or uncert[1] == 'None':
+                uncert[0] = False
+                uncert[1] = np.nan
+            if not upper or upper[1] == '' or upper[1] == 'None':
+                upper[0] = False
+                upper[1] = np.nan
+            if not lower or lower[1] == '' or lower[1] == 'None':
+                lower[0] = False
+                lower[1] = np.nan
+            if is_string:
+                p[name] = str(value)
+            else:
+                p[name] = float(value)
+            p[name + ".fittable"] = bool(fittable)
+            p[name + ".std"] = float(uncert[1])
+            p[name + ".upper"] = float(upper[1])
+            p[name + ".lower"] = float(lower[1])
+            p[name + ".units"] = units
+        return p
+
+    @staticmethod
+    def param_remap_from_sasmodels_convert(params):
+        """
+        Converts {name : value} map back to [] param list
+        :param params: parameter map returned from sasmodels
+        :return: None
+        """
+        p_map = []
+        for name, info in params.iteritems():
+            if ".fittable" in name or ".std" in name or ".upper" in name or \
+                            ".lower" in name or ".units" in name:
+                pass
+            else:
+                fittable = params.get(name + ".fittable", True)
+                std = params.get(name + ".std", '0.0')
+                upper = params.get(name + ".upper", 'inf')
+                lower = params.get(name + ".lower", '-inf')
+                units = params.get(name + ".units")
+                if std is not None and std is not np.nan:
+                    std = [True, str(std)]
+                else:
+                    std = [False, '']
+                if lower is not None and lower is not np.nan:
+                    lower = [True, str(lower)]
+                else:
+                    lower = [True, '-inf']
+                if upper is not None and upper is not np.nan:
+                    upper = [True, str(upper)]
+                else:
+                    upper = [True, 'inf']
+                param_list = [bool(fittable), str(name), str(info),
+                              "+/-", std, lower, upper, str(units)]
+                p_map.append(param_list)
+        return p_map
+
+    def _convert_to_sasmodels(self):
+        """
+        Convert parameters to a form usable by sasmodels converter
+
+        :return: None
+        """
+        # Create conversion dictionary to send to sasmodels
+        self._old_first_model()
+        p = self.param_remap_to_sasmodels_convert(self.parameters)
+        structurefactor, params = convert.convert_model(self.structurecombobox,
+                                                        p, False, self.version)
+        formfactor, params = convert.convert_model(self.formfactorcombobox,
+                                                   params, False, self.version)
+        if len(self.str_parameters) > 0:
+            str_pars = self.param_remap_to_sasmodels_convert(
+                self.str_parameters, True)
+            formfactor, str_params = convert.convert_model(
+                self.formfactorcombobox, str_pars, False, self.version)
+            for key, value in str_params.iteritems():
+                params[key] = value
+
+        if self.formfactorcombobox == 'SphericalSLDModel':
+            self.multi_factor += 1
+        self.formfactorcombobox = formfactor
+        self.structurecombobox = structurefactor
+        self.parameters = []
+        self.parameters = self.param_remap_from_sasmodels_convert(params)
+
+    def _repr_helper(self, list, rep):
+        """
+        Helper method to print a state
+        """
+        for item in list:
+            rep += "parameter name: %s \n" % str(item[1])
+            rep += "value: %s\n" % str(item[2])
+            rep += "selected: %s\n" % str(item[0])
+            rep += "error displayed : %s \n" % str(item[4][0])
+            rep += "error value:%s \n" % str(item[4][1])
+            rep += "minimum displayed : %s \n" % str(item[5][0])
+            rep += "minimum value : %s \n" % str(item[5][1])
+            rep += "maximum displayed : %s \n" % str(item[6][0])
+            rep += "maximum value : %s \n" % str(item[6][1])
+            rep += "parameter unit: %s\n\n" % str(item[7])
+        return rep
+
+    def __repr__(self):
+        """
+        output string for printing
+        """
+        rep = "\nState name: %s\n" % self.file
+        t = time.localtime(self.timestamp)
+        time_str = time.strftime("%b %d %Y %H:%M:%S ", t)
+
+        rep += "State created: %s\n" % time_str
+        rep += "State form factor combobox selection: %s\n" % \
+               self.formfactorcombobox
+        rep += "State structure factor combobox selection: %s\n" % \
+               self.structurecombobox
+        rep += "is data : %s\n" % self.is_data
+        rep += "data's name : %s\n" % self.data_name
+        rep += "data's id : %s\n" % self.data_id
+        if self.model is not None:
+            m_name = self.model.__class__.__name__
+            if m_name == 'Model':
+                m_name = self.m_name
+            rep += "model name : %s\n" % m_name
+        else:
+            rep += "model name : None\n"
+        rep += "multi_factor : %s\n" % str(self.multi_factor)
+        rep += "magnetic_on : %s\n" % str(self.magnetic_on)
+        rep += "model type (Category) selected: %s\n" % self.categorycombobox
+        rep += "data : %s\n" % str(self.data)
+        rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \
+               (str(self.qmin), str(self.qmax), str(self.npts))
+        rep += "Dispersion selection : %s\n" % str(self.disp_box)
+        rep += "Smearing enable : %s\n" % str(self.enable_smearer)
+        rep += "Smearing disable : %s\n" % str(self.disable_smearer)
+        rep += "Pinhole smearer enable : %s\n" % str(self.pinhole_smearer)
+        rep += "Slit smearer enable : %s\n" % str(self.slit_smearer)
+        rep += "Dispersity enable : %s\n" % str(self.enable_disp)
+        rep += "Dispersity disable : %s\n" % str(self.disable_disp)
+        rep += "Slit smearer enable: %s\n" % str(self.slit_smearer)
+
+        rep += "dI_noweight : %s\n" % str(self.dI_noweight)
+        rep += "dI_didata : %s\n" % str(self.dI_didata)
+        rep += "dI_sqrdata : %s\n" % str(self.dI_sqrdata)
+        rep += "dI_idata : %s\n" % str(self.dI_idata)
+
+        rep += "2D enable : %s\n" % str(self.enable2D)
+        rep += "All parameters checkbox selected: %s\n" % self.cb1
+        rep += "Value of Chisqr : %s\n" % str(self.tcChi)
+        rep += "Smear object : %s\n" % self.smearer
+        rep += "Smear type : %s\n" % self.smear_type
+        rep += "dq_l  : %s\n" % self.dq_l
+        rep += "dq_r  : %s\n" % self.dq_r
+        rep += "dx_percent  : %s\n" % str(self.dx_percent)
+        rep += "dxl  : %s\n" % str(self.dxl)
+        rep += "dxw : %s\n" % str(self.dxw)
+        rep += "model  : %s\n\n" % str(self.model)
+        temp_parameters = []
+        temp_fittable_param = []
+        if self.data.__class__.__name__ == "Data2D":
+            self.is_2D = True
+        else:
+            self.is_2D = False
+        if self.data is not None:
+            if not self.is_2D:
+                for item in self.parameters:
+                    if item not in self.orientation_params:
+                        temp_parameters.append(item)
+                for item in self.fittable_param:
+                    if item not in self.orientation_params_disp:
+                        temp_fittable_param.append(item)
+            else:
+                temp_parameters = self.parameters
+                temp_fittable_param = self.fittable_param
+
+            rep += "number parameters(self.parameters): %s\n" % \
+                   len(temp_parameters)
+            rep = self._repr_helper(list=temp_parameters, rep=rep)
+            rep += "number str_parameters(self.str_parameters): %s\n" % \
+                   len(self.str_parameters)
+            rep = self._repr_helper(list=self.str_parameters, rep=rep)
+            rep += "number fittable_param(self.fittable_param): %s\n" % \
+                   len(temp_fittable_param)
+            rep = self._repr_helper(list=temp_fittable_param, rep=rep)
+        return rep
+
+    def _get_report_string(self):
+        """
+        Get the values (strings) from __str__ for report
+        """
+        # Dictionary of the report strings
+        repo_time = ""
+        model_name = ""
+        title = ""
+        title_name = ""
+        file_name = ""
+        param_string = ""
+        paramval_string = ""
+        chi2_string = ""
+        q_range = ""
+        strings = self.__repr__()
+        fixed_parameter = False
+        lines = strings.split('\n')
+        # get all string values from __str__()
+        for line in lines:
+            # Skip lines which are not key: value pairs, which includes
+            # blank lines and freeform notes in SASNotes fields.
+            if not ':' in line:
+                #msg = "Report string expected 'name: value' but got %r" % line
+                #logger.error(msg)
+                continue
+
+            name, value = [s.strip() for s in line.split(":", 1)]
+            if name == "State created":
+                repo_time = value
+            elif name == "parameter name":
+                val_name = value.split(".")
+                if len(val_name) > 1:
+                    if val_name[1].count("width"):
+                        param_string += value + ','
+                    else:
+                        continue
+                else:
+                    param_string += value + ','
+            elif name == "value":
+                param_string += value + ','
+            elif name == "selected":
+                # remember if it is fixed when reporting error value
+                fixed_parameter = (value == u'False')
+            elif name == "error value":
+                if fixed_parameter:
+                    param_string += '(fixed),'
+                else:
+                    param_string += value + ','
+            elif name == "parameter unit":
+                param_string += value + ':'
+            elif name == "Value of Chisqr":
+                chi2 = ("Chi2/Npts = " + value)
+                chi2_string = CENTRE % chi2
+            elif name == "Title":
+                if len(value.strip()) == 0:
+                    continue
+                title = value + " [" + repo_time + "]"
+                title_name = HEADER % title
+            elif name == "data":
+                try:
+                    # parsing "data : File:     filename [mmm dd hh:mm]"
+                    name = value.split(':', 1)[1].strip()
+                    file_value = "File name:" + name
+                    file_name = CENTRE % file_value
+                    if len(title) == 0:
+                        title = name + " [" + repo_time + "]"
+                        title_name = HEADER % title
+                except Exception:
+                    msg = "While parsing 'data: ...'\n"
+                    logger.error(msg + traceback.format_exc())
+            elif name == "model name":
+                try:
+                    modelname = "Model name:" + value
+                except Exception:
+                    modelname = "Model name:" + " NAN"
+                model_name = CENTRE % modelname
+
+            elif name == "Plotting Range":
+                try:
+                    parts = value.split(':')
+                    q_range = parts[0] + " = " + parts[1] \
+                            + " = " + parts[2].split(",")[0]
+                    q_name = ("Q Range:    " + q_range)
+                    q_range = CENTRE % q_name
+                except Exception:
+                    msg = "While parsing 'Plotting Range: ...'\n"
+                    logger.error(msg + traceback.format_exc())
+
+        paramval = ""
+        for lines in param_string.split(":"):
+            line = lines.split(",")
+            if len(lines) > 0:
+                param = line[0]
+                param += " = " + line[1]
+                if len(line[2].split()) > 0 and not line[2].count("None"):
+                    param += " +- " + line[2]
+                if len(line[3].split()) > 0 and not line[3].count("None"):
+                    param += " " + line[3]
+                if not paramval.count(param):
+                    paramval += param + "\n"
+                    paramval_string += CENTRE % param + "\n"
+
+        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
+                      (title, file, q_name, chi2, paramval)
+
+        title_name = self._check_html_format(title_name)
+        file_name = self._check_html_format(file_name)
+        title = self._check_html_format(title)
+
+        html_string = title_name + "\n" + file_name + \
+                                   "\n" + model_name + \
+                                   "\n" + q_range + \
+                                   "\n" + chi2_string + \
+                                   "\n" + ELINE + \
+                                   "\n" + paramval_string + \
+                                   "\n" + ELINE + \
+                                   "\n" + FEET_1 % title
+
+        return html_string, text_string, title
+
+    def _check_html_format(self, name):
+        """
+        Check string '%' for html format
+        """
+        if name.count('%'):
+            name = name.replace('%', '&#37')
+
+        return name
+
+    def report(self, fig_urls):
+        """
+        Invoke report dialog panel
+
+        : param figs: list of pylab figures [list]
+        """
+        # get the strings for report
+        html_str, text_str, title = self._get_report_string()
+        # Allow 2 figures to append
+        image_links = [FEET_2%fig for fig in fig_urls]
+
+        # final report html strings
+        report_str = html_str + ELINE.join(image_links)
+
+        return report_str, text_str
+
+    def _to_xml_helper(self, thelist, element, newdoc):
+        """
+        Helper method to create xml file for saving state
+        """
+        for item in thelist:
+            sub_element = newdoc.createElement('parameter')
+            sub_element.setAttribute('name', str(item[1]))
+            sub_element.setAttribute('value', str(item[2]))
+            sub_element.setAttribute('selected_to_fit', str(item[0]))
+            sub_element.setAttribute('error_displayed', str(item[4][0]))
+            sub_element.setAttribute('error_value', str(item[4][1]))
+            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
+            sub_element.setAttribute('minimum_value', str(item[5][1]))
+            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
+            sub_element.setAttribute('maximum_value', str(item[6][1]))
+            sub_element.setAttribute('unit', str(item[7]))
+            element.appendChild(sub_element)
+
+    def to_xml(self, file="fitting_state.fitv", doc=None,
+               entry_node=None, batch_fit_state=None):
+        """
+        Writes the state of the fit panel to file, as XML.
+
+        Compatible with standalone writing, or appending to an
+        already existing XML document. In that case, the XML document is
+        required. An optional entry node in the XML document may also be given.
+
+        :param file: file to write to
+        :param doc: XML document object [optional]
+        :param entry_node: XML node within the XML document at which we
+                           will append the data [optional]
+        :param batch_fit_state: simultaneous fit state
+        """
+        from xml.dom.minidom import getDOMImplementation
+
+        # Check whether we have to write a standalone XML file
+        if doc is None:
+            impl = getDOMImplementation()
+            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
+            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
+            top_element = newdoc.documentElement
+        else:
+            # We are appending to an existing document
+            newdoc = doc
+            try:
+                top_element = newdoc.createElement(FITTING_NODE_NAME)
+            except Exception:
+                string = etree.tostring(doc, pretty_print=True)
+                newdoc = parseString(string)
+                top_element = newdoc.createElement(FITTING_NODE_NAME)
+            if entry_node is None:
+                newdoc.documentElement.appendChild(top_element)
+            else:
+                try:
+                    entry_node.appendChild(top_element)
+                except Exception:
+                    node_name = entry_node.tag
+                    node_list = newdoc.getElementsByTagName(node_name)
+                    entry_node = node_list.item(0)
+                    entry_node.appendChild(top_element)
+
+        attr = newdoc.createAttribute("version")
+        from sas import sasview
+        attr.nodeValue = sasview.__version__
+        # attr.nodeValue = '1.0'
+        top_element.setAttributeNode(attr)
+
+        # File name
+        element = newdoc.createElement("filename")
+        if self.file is not None:
+            element.appendChild(newdoc.createTextNode(str(self.file)))
+        else:
+            element.appendChild(newdoc.createTextNode(str(file)))
+        top_element.appendChild(element)
+
+        element = newdoc.createElement("timestamp")
+        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
+        attr = newdoc.createAttribute("epoch")
+        attr.nodeValue = str(self.timestamp)
+        element.setAttributeNode(attr)
+        top_element.appendChild(element)
+
+        # Inputs
+        inputs = newdoc.createElement("Attributes")
+        top_element.appendChild(inputs)
+
+        if self.data is not None and hasattr(self.data, "group_id"):
+            self.data_group_id = self.data.group_id
+        if self.data is not None and hasattr(self.data, "is_data"):
+            self.is_data = self.data.is_data
+        if self.data is not None:
+            self.data_name = self.data.name
+        if self.data is not None and hasattr(self.data, "id"):
+            self.data_id = self.data.id
+
+        for item in LIST_OF_DATA_ATTRIBUTES:
+            element = newdoc.createElement(item[0])
+            element.setAttribute(item[0], str(getattr(self, item[1])))
+            inputs.appendChild(element)
+
+        for item in LIST_OF_STATE_ATTRIBUTES:
+            element = newdoc.createElement(item[0])
+            element.setAttribute(item[0], str(getattr(self, item[1])))
+            inputs.appendChild(element)
+
+        # For self.values ={ disp_param_name: [vals,...],...}
+        # and for self.weights ={ disp_param_name: [weights,...],...}
+        for item in LIST_OF_MODEL_ATTRIBUTES:
+            element = newdoc.createElement(item[0])
+            value_list = getattr(self, item[1])
+            for key, value in value_list.iteritems():
+                sub_element = newdoc.createElement(key)
+                sub_element.setAttribute('name', str(key))
+                for val in value:
+                    sub_element.appendChild(newdoc.createTextNode(str(val)))
+
+                element.appendChild(sub_element)
+            inputs.appendChild(element)
+
+        # Create doc for the dictionary of self._disp_obj_dic
+        for tagname, varname, tagtype in DISPERSION_LIST:
+            element = newdoc.createElement(tagname)
+            value_list = getattr(self, varname)
+            for key, value in value_list.iteritems():
+                sub_element = newdoc.createElement(key)
+                sub_element.setAttribute('name', str(key))
+                sub_element.setAttribute('value', str(value))
+                element.appendChild(sub_element)
+            inputs.appendChild(element)
+
+        for item in LIST_OF_STATE_PARAMETERS:
+            element = newdoc.createElement(item[0])
+            self._to_xml_helper(thelist=getattr(self, item[1]),
+                                element=element, newdoc=newdoc)
+            inputs.appendChild(element)
+
+        # Combined and Simultaneous Fit Parameters
+        if batch_fit_state is not None:
+            batch_combo = newdoc.createElement('simultaneous_fit')
+            top_element.appendChild(batch_combo)
+
+            # Simultaneous Fit Number For Linking Later
+            element = newdoc.createElement('sim_fit_number')
+            element.setAttribute('fit_number', str(batch_fit_state.fit_page_no))
+            batch_combo.appendChild(element)
+
+            # Save constraints
+            constraints = newdoc.createElement('constraints')
+            batch_combo.appendChild(constraints)
+            for constraint in batch_fit_state.constraints_list:
+                if constraint.model_cbox.GetValue() != "":
+                    # model_cbox, param_cbox, egal_txt, constraint,
+                    # btRemove, sizer
+                    doc_cons = newdoc.createElement('constraint')
+                    doc_cons.setAttribute('model_cbox',
+                                          str(constraint.model_cbox.GetValue()))
+                    doc_cons.setAttribute('param_cbox',
+                                          str(constraint.param_cbox.GetValue()))
+                    doc_cons.setAttribute('egal_txt',
+                                          str(constraint.egal_txt.GetLabel()))
+                    doc_cons.setAttribute('constraint',
+                                          str(constraint.constraint.GetValue()))
+                    constraints.appendChild(doc_cons)
+
+            # Save all models
+            models = newdoc.createElement('model_list')
+            batch_combo.appendChild(models)
+            for model in batch_fit_state.model_list:
+                doc_model = newdoc.createElement('model_list_item')
+                doc_model.setAttribute('checked', str(model[0].GetValue()))
+                keys = model[1].keys()
+                doc_model.setAttribute('name', str(keys[0]))
+                values = model[1].get(keys[0])
+                doc_model.setAttribute('fit_number', str(model[2]))
+                doc_model.setAttribute('fit_page_source', str(model[3]))
+                doc_model.setAttribute('model_name', str(values.model.id))
+                models.appendChild(doc_model)
+
+            # Select All Checkbox
+            element = newdoc.createElement('select_all')
+            if batch_fit_state.select_all:
+                element.setAttribute('checked', 'True')
+            else:
+                element.setAttribute('checked', 'False')
+            batch_combo.appendChild(element)
+
+        # Save the file
+        if doc is None:
+            fd = open(file, 'w')
+            fd.write(newdoc.toprettyxml())
+            fd.close()
+            return None
+        else:
+            return newdoc
+
+    def _from_xml_helper(self, node, list):
+        """
+        Helper function to write state to xml
+        """
+        for item in node:
+            try:
+                name = item.get('name')
+            except:
+                name = None
+            try:
+                value = item.get('value')
+            except:
+                value = None
+            try:
+                selected_to_fit = (item.get('selected_to_fit') == "True")
+            except:
+                selected_to_fit = None
+            try:
+                error_displayed = (item.get('error_displayed') == "True")
+            except:
+                error_displayed = None
+            try:
+                error_value = item.get('error_value')
+            except:
+                error_value = None
+            try:
+                minimum_displayed = (item.get('minimum_displayed') == "True")
+            except:
+                minimum_displayed = None
+            try:
+                minimum_value = item.get('minimum_value')
+            except:
+                minimum_value = None
+            try:
+                maximum_displayed = (item.get('maximum_displayed') == "True")
+            except:
+                maximum_displayed = None
+            try:
+                maximum_value = item.get('maximum_value')
+            except:
+                maximum_value = None
+            try:
+                unit = item.get('unit')
+            except:
+                unit = None
+            list.append([selected_to_fit, name, value, "+/-",
+                         [error_displayed, error_value],
+                         [minimum_displayed, minimum_value],
+                         [maximum_displayed, maximum_value], unit])
+
+    def from_xml(self, file=None, node=None):
+        """
+        Load fitting state from a file
+
+        :param file: .fitv file
+        :param node: node of a XML document to read from
+        """
+        if file is not None:
+            msg = "PageState no longer supports non-CanSAS"
+            msg += " format for fitting files"
+            raise RuntimeError, msg
+
+        if node.get('version'):
+            # Get the version for model conversion purposes
+            self.version = tuple(int(e) for e in
+                                 str.split(node.get('version'), "."))
+            # The tuple must be at least 3 items long
+            while len(self.version) < 3:
+                ver_list = list(self.version)
+                ver_list.append(0)
+                self.version = tuple(ver_list)
+
+            # Get file name
+            entry = get_content('ns:filename', node)
+            if entry is not None:
+                self.file = entry.text.strip()
+
+            # Get time stamp
+            entry = get_content('ns:timestamp', node)
+            if entry is not None and entry.get('epoch'):
+                try:
+                    self.timestamp = float(entry.get('epoch'))
+                except Exception:
+                    msg = "PageState.fromXML: Could not"
+                    msg += " read timestamp\n %s" % sys.exc_value
+                    logger.error(msg)
+
+            if entry is not None:
+                # Parse fitting attributes
+                entry = get_content('ns:Attributes', node)
+                for item in LIST_OF_DATA_ATTRIBUTES:
+                    node = get_content('ns:%s' % item[0], entry)
+                    setattr(self, item[0], parse_entry_helper(node, item))
+
+                dx_old_node = get_content('ns:%s' % 'dx_min', entry)
+                for item in LIST_OF_STATE_ATTRIBUTES:
+                    if item[0] == "dx_percent" and dx_old_node is not None:
+                        dxmin = ["dx_min", "dx_min", "float"]
+                        setattr(self, item[0], parse_entry_helper(dx_old_node,
+                                                                  dxmin))
+                        self.dx_old = True
+                    else:
+                        node = get_content('ns:%s' % item[0], entry)
+                        setattr(self, item[0], parse_entry_helper(node, item))
+
+                for item in LIST_OF_STATE_PARAMETERS:
+                    node = get_content("ns:%s" % item[0], entry)
+                    self._from_xml_helper(node=node,
+                                          list=getattr(self, item[1]))
+
+                # Recover _disp_obj_dict from xml file
+                self._disp_obj_dict = {}
+                for tagname, varname, tagtype in DISPERSION_LIST:
+                    node = get_content("ns:%s" % tagname, entry)
+                    for attr in node:
+                        parameter = str(attr.get('name'))
+                        value = attr.get('value')
+                        if value.startswith("<"):
+                            try:
+                                # <path.to.NamedDistribution object/instance...>
+                                cls_name = value[1:].split()[0].split('.')[-1]
+                                cls = getattr(sasmodels.weights, cls_name)
+                                value = cls.type
+                            except Exception:
+                                base = "unable to load distribution %r for %s"
+                                logger.error(base % (value, parameter))
+                                continue
+                        _disp_obj_dict = getattr(self, varname)
+                        _disp_obj_dict[parameter] = value
+
+                # get self.values and self.weights dic. if exists
+                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
+                    node = get_content("ns:%s" % tagname, entry)
+                    dic = {}
+                    value_list = []
+                    for par in node:
+                        name = par.get('name')
+                        values = par.text.split()
+                        # Get lines only with numbers
+                        for line in values:
+                            try:
+                                val = float(line)
+                                value_list.append(val)
+                            except Exception:
+                                # pass if line is empty (it happens)
+                                msg = ("Error reading %r from %s %s\n"
+                                       % (line, tagname, name))
+                                logger.error(msg + traceback.format_exc())
+                        dic[name] = np.array(value_list)
+                    setattr(self, varname, dic)
+
+
+class Reader(CansasReader):
+    """
+    Class to load a .fitv fitting file
+    """
+    # File type
+    type_name = "Fitting"
+
+    # Wildcards
+    type = ["Fitting files (*.fitv)|*.fitv"
+            "SASView file (*.svs)|*.svs"]
+    # List of allowed extensions
+    ext = ['.fitv', '.FITV', '.svs', 'SVS']
+
+    def __init__(self, call_back=None, cansas=True):
+        CansasReader.__init__(self)
+        """
+        Initialize the call-back method to be called
+        after we load a file
+
+        :param call_back: call-back method
+        :param cansas:  True = files will be written/read in CanSAS format
+                        False = write CanSAS format
+
+        """
+        # Call back method to be executed after a file is read
+        self.call_back = call_back
+        # CanSAS format flag
+        self.cansas = cansas
+        self.state = None
+        # batch fitting params for saving
+        self.batchfit_params = []
+
+    def get_state(self):
+        return self.state
+
+    def read(self, path):
+        """
+        Load a new P(r) inversion state from file
+
+        :param path: file path
+
+        """
+        if self.cansas:
+            return self._read_cansas(path)
+
+    def _parse_state(self, entry):
+        """
+        Read a fit result from an XML node
+
+        :param entry: XML node to read from
+        :return: PageState object
+        """
+        # Create an empty state
+        state = None
+        # Locate the P(r) node
+        try:
+            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
+                                namespaces={'ns': CANSAS_NS})
+            if nodes:
+                # Create an empty state
+                state = PageState()
+                state.from_xml(node=nodes[0])
+
+        except:
+            logger.info("XML document does not contain fitting information.\n"
+                         + traceback.format_exc())
+
+        return state
+
+    def _parse_simfit_state(self, entry):
+        """
+        Parses the saved data for a simultaneous fit
+        :param entry: XML object to read from
+        :return: XML object for a simultaneous fit or None
+        """
+        nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
+                            namespaces={'ns': CANSAS_NS})
+        if nodes:
+            simfitstate = nodes[0].xpath('ns:simultaneous_fit',
+                                         namespaces={'ns': CANSAS_NS})
+            if simfitstate:
+                from simfitpage import SimFitPageState
+                sim_fit_state = SimFitPageState()
+                simfitstate_0 = simfitstate[0]
+                all = simfitstate_0.xpath('ns:select_all',
+                                          namespaces={'ns': CANSAS_NS})
+                atts = all[0].attrib
+                checked = atts.get('checked')
+                sim_fit_state.select_all = bool(checked)
+                model_list = simfitstate_0.xpath('ns:model_list',
+                                                 namespaces={'ns': CANSAS_NS})
+                model_list_items = model_list[0].xpath('ns:model_list_item',
+                                                       namespaces={'ns':
+                                                                    CANSAS_NS})
+                for model in model_list_items:
+                    attrs = model.attrib
+                    sim_fit_state.model_list.append(attrs)
+
+                constraints = simfitstate_0.xpath('ns:constraints',
+                                                namespaces={'ns': CANSAS_NS})
+                constraint_list = constraints[0].xpath('ns:constraint',
+                                               namespaces={'ns': CANSAS_NS})
+                for constraint in constraint_list:
+                    attrs = constraint.attrib
+                    sim_fit_state.constraints_list.append(attrs)
+
+                return sim_fit_state
+            else:
+                return None
+
+    def _parse_save_state_entry(self, dom):
+        """
+        Parse a SASentry
+
+        :param node: SASentry node
+
+        :return: Data1D/Data2D object
+
+        """
+        node = dom.xpath('ns:data_class', namespaces={'ns': CANSAS_NS})
+        return_value, _ = self._parse_entry(dom)
+        return return_value, _
+
+    def _read_cansas(self, path):
+        """
+        Load data and fitting information from a CanSAS XML file.
+
+        :param path: file path
+        :return: Data1D object if a single SASentry was found,
+                    or a list of Data1D objects if multiple entries were found,
+                    or None of nothing was found
+        :raise RuntimeError: when the file can't be opened
+        :raise ValueError: when the length of the data vectors are inconsistent
+        """
+        output = []
+        simfitstate = None
+        basename = os.path.basename(path)
+        root, extension = os.path.splitext(basename)
+        ext = extension.lower()
+        try:
+            if os.path.isfile(path):
+                if ext in self.ext or ext == '.xml':
+                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
+                    # Check the format version number
+                    # Specifying the namespace will take care of the file
+                    # format version
+                    root = tree.getroot()
+                    entry_list = root.xpath('ns:SASentry',
+                                            namespaces={'ns': CANSAS_NS})
+                    for entry in entry_list:
+                        try:
+                            sas_entry, _ = self._parse_save_state_entry(entry)
+                        except:
+                            raise
+                        fitstate = self._parse_state(entry)
+
+                        # state could be None when .svs file is loaded
+                        # in this case, skip appending to output
+                        if fitstate is not None:
+                            sas_entry.meta_data['fitstate'] = fitstate
+                            sas_entry.filename = fitstate.file
+                            output.append(sas_entry)
+
+            else:
+                self.call_back(format=ext)
+                raise RuntimeError, "%s is not a file" % path
+
+            # Return output consistent with the loader's api
+            if len(output) == 0:
+                self.call_back(state=None, datainfo=None, format=ext)
+                return None
+            else:
+                for ind in range(len(output)):
+                    # Call back to post the new state
+                    state = output[ind].meta_data['fitstate']
+                    t = time.localtime(state.timestamp)
+                    time_str = time.strftime("%b %d %H:%M", t)
+                    # Check that no time stamp is already appended
+                    max_char = state.file.find("[")
+                    if max_char < 0:
+                        max_char = len(state.file)
+                    original_fname = state.file[0:max_char]
+                    state.file = original_fname + ' [' + time_str + ']'
+
+                    if state is not None and state.is_data is not None:
+                        output[ind].is_data = state.is_data
+
+                    output[ind].filename = state.file
+                    state.data = output[ind]
+                    state.data.name = output[ind].filename  # state.data_name
+                    state.data.id = state.data_id
+                    if state.is_data is not None:
+                        state.data.is_data = state.is_data
+                    if output[ind].run_name is not None\
+                         and len(output[ind].run_name) != 0:
+                        if isinstance(output[ind].run_name, dict):
+                            name = output[ind].run_name.keys()[0]
+                        else:
+                            name = output[ind].run_name
+                    else:
+                        name = original_fname
+                    state.data.group_id = name
+                    state.version = fitstate.version
+                    # store state in fitting
+                    self.call_back(state=state,
+                                   datainfo=output[ind], format=ext)
+                    self.state = state
+                simfitstate = self._parse_simfit_state(entry)
+                if simfitstate is not None:
+                    self.call_back(state=simfitstate)
+
+                return output
+        except:
+            self.call_back(format=ext)
+            raise
+
+    def write(self, filename, datainfo=None, fitstate=None):
+        """
+        Write the content of a Data1D as a CanSAS XML file only for standalone
+
+        :param filename: name of the file to write
+        :param datainfo: Data1D object
+        :param fitstate: PageState object
+
+        """
+        # Sanity check
+        if self.cansas:
+            # Add fitting information to the XML document
+            doc = self.write_toXML(datainfo, fitstate)
+            # Write the XML document
+        else:
+            doc = fitstate.to_xml(file=filename)
+
+        # Save the document no matter the type
+        fd = open(filename, 'w')
+        fd.write(doc.toprettyxml())
+        fd.close()
+
+    def write_toXML(self, datainfo=None, state=None, batchfit=None):
+        """
+        Write toXML, a helper for write(),
+        could be used by guimanager._on_save()
+
+        : return: xml doc
+        """
+
+        self.batchfit_params = batchfit
+        if state.data is None or not state.data.is_data:
+            return None
+        # make sure title and data run are filled.
+        if state.data.title is None or state.data.title == '':
+            state.data.title = state.data.name
+        if state.data.run_name is None or state.data.run_name == {}:
+            state.data.run = [str(state.data.name)]
+            state.data.run_name[0] = state.data.name
+
+        data = state.data
+        doc, sasentry = self._to_xml_doc(data)
+
+        if state is not None:
+            doc = state.to_xml(doc=doc, file=data.filename, entry_node=sasentry,
+                               batch_fit_state=self.batchfit_params)
+
+        return doc
+
+# Simple html report template
+HEADER = "<html>\n"
+HEADER += "<head>\n"
+HEADER += "<meta http-equiv=Content-Type content='text/html; "
+HEADER += "charset=windows-1252'> \n"
+HEADER += "<meta name=Generator >\n"
+HEADER += "</head>\n"
+HEADER += "<body lang=EN-US>\n"
+HEADER += "<div class=WordSection1>\n"
+HEADER += "<p class=MsoNormal><b><span ><center><font size='4' >"
+HEADER += "%s</font></center></span></center></b></p>"
+HEADER += "<p class=MsoNormal>&nbsp;</p>"
+PARA = "<p class=MsoNormal><font size='4' > %s \n"
+PARA += "</font></p>"
+CENTRE = "<p class=MsoNormal><center><font size='4' > %s \n"
+CENTRE += "</font></center></p>"
+FEET_1 = \
+"""
+<p class=MsoNormal>&nbsp;</p>
+<br>
+<p class=MsoNormal><b><span ><center> <font size='4' > Graph
+</font></span></center></b></p>
+<p class=MsoNormal>&nbsp;</p>
+<center>
+<br><font size='4' >Model Computation</font>
+<br><font size='4' >Data: "%s"</font><br>
+"""
+FEET_2 = \
+"""<img src="%s" ></img>
+"""
+FEET_3 = \
+"""</center>
+</div>
+</body>
+</html>
+"""
+ELINE = """<p class=MsoNormal>&nbsp;</p>
+"""
Index: src/sas/sasgui/perspectives/fitting/basepage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/basepage.py	(revision 78312f78ace85630125cafc0f9f54ed8d905db13)
+++ src/sas/sasgui/perspectives/fitting/basepage.py	(revision 2a0f33fc9cc1c4ed278b92f004e84ba596199882)
@@ -23,4 +23,5 @@
 from sas.sascalc.dataloader.data_info import Detector
 from sas.sascalc.dataloader.data_info import Source
+from sas.sascalc.fit.pagestate import PageState
 
 from sas.sasgui.guiframe.panel_base import PanelBase
@@ -37,5 +38,4 @@
 from sas.sasgui.guiframe.documentation_window import DocumentationWindow
 
-from sas.sasgui.perspectives.fitting.pagestate import PageState
 from sas.sasgui.perspectives.fitting.report_dialog import ReportDialog
 
Index: src/sas/sasgui/perspectives/fitting/fitting.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitting.py	(revision a534432becfaa32e8ffca1463734221cba9c0dd2)
+++ src/sas/sasgui/perspectives/fitting/fitting.py	(revision 2a0f33fc9cc1c4ed278b92f004e84ba596199882)
@@ -24,4 +24,7 @@
 
 from sas.sascalc.dataloader.loader import Loader
+from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
+from sas.sascalc.fit.pagestate import Reader
+
 from sas.sasgui.guiframe.dataFitting import Data2D
 from sas.sasgui.guiframe.dataFitting import Data1D
@@ -34,5 +37,4 @@
 from sas.sasgui.guiframe.plugin_base import PluginBase
 from sas.sasgui.guiframe.data_processor import BatchCell
-from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
 from sas.sasgui.perspectives.fitting.console import ConsoleUpdate
 from sas.sasgui.perspectives.fitting.fitproblem import FitProblemDictionary
@@ -361,5 +363,5 @@
                                    'Add a new model function')
         wx.EVT_MENU(owner, wx_id, self.make_new_model)
-        
+
         wx_id = wx.NewId()
         self.edit_model_menu.Append(wx_id, 'Sum|Multi(p1, p2)',
@@ -383,5 +385,5 @@
           '(Re)Load all models present in user plugin_models folder')
         wx.EVT_MENU(owner, wx_id, self.load_plugin_models)
-                
+
     def set_edit_menu_helper(self, owner=None, menu=None):
         """
@@ -1734,6 +1736,6 @@
             @param unsmeared_error: data error, rescaled to unsmeared model
         """
-            
-        number_finite = np.count_nonzero(np.isfinite(y)) 
+
+        number_finite = np.count_nonzero(np.isfinite(y))
         np.nan_to_num(y)
         new_plot = self.create_theory_1D(x, y, page_id, model, data, state,
@@ -1794,5 +1796,5 @@
             msg = "Computing Error: Model did not return any finite value."
             wx.PostEvent(self.parent, StatusEvent(status = msg, info="error"))
-        else:                 
+        else:
             msg = "Computation  completed!"
             if number_finite != y.size:
@@ -1824,5 +1826,5 @@
         that can be plot.
         """
-        number_finite = np.count_nonzero(np.isfinite(image)) 
+        number_finite = np.count_nonzero(np.isfinite(image))
         np.nan_to_num(image)
         new_plot = Data2D(image=image, err_image=data.err_data)
@@ -1927,5 +1929,5 @@
                 ## and may be the cause of other noted instabilities
                 ##
-                ##    -PDB August 12, 2014 
+                ##    -PDB August 12, 2014
                 while self.calc_2D.isrunning():
                     time.sleep(0.1)
@@ -1969,5 +1971,5 @@
             if (self.calc_1D is not None) and self.calc_1D.isrunning():
                 self.calc_1D.stop()
-                ## stop just raises the flag -- the thread is supposed to 
+                ## stop just raises the flag -- the thread is supposed to
                 ## then kill itself but cannot.  Paul Kienzle came up with
                 ## this fix to prevent threads from stepping on each other
@@ -1981,5 +1983,5 @@
                 ## a request to stop the computation.
                 ## It seems thus that the whole thread approach used here
-                ## May need rethinking  
+                ## May need rethinking
                 ##
                 ##    -PDB August 12, 2014
@@ -2146,5 +2148,5 @@
             residuals.dxw = None
             residuals.ytransform = 'y'
-            # For latter scale changes 
+            # For latter scale changes
             residuals.xaxis('\\rm{Q} ', 'A^{-1}')
             residuals.yaxis('\\rm{Residuals} ', 'normalized')
Index: c/sas/sasgui/perspectives/fitting/pagestate.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/pagestate.py	(revision 78312f78ace85630125cafc0f9f54ed8d905db13)
+++ 	(revision )
@@ -1,1391 +1,0 @@
-"""
-    Class that holds a fit page state
-"""
-# TODO: Refactor code so we don't need to use getattr/setattr
-################################################################################
-# This software was developed by the University of Tennessee as part of the
-# Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
-# project funded by the US National Science Foundation.
-#
-# See the license text in license.txt
-#
-# copyright 2009, University of Tennessee
-################################################################################
-import time
-import os
-import sys
-import copy
-import logging
-import numpy as np
-import traceback
-
-import xml.dom.minidom
-from xml.dom.minidom import parseString
-from lxml import etree
-
-from sasmodels import convert
-import sasmodels.weights
-
-import sas.sascalc.dataloader
-from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
-from sas.sascalc.dataloader.readers.cansas_reader import get_content, write_node
-from sas.sascalc.dataloader.data_info import Data2D, Collimation, Detector
-from sas.sascalc.dataloader.data_info import Process, Aperture
-
-logger = logging.getLogger(__name__)
-
-# Information to read/write state as xml
-FITTING_NODE_NAME = 'fitting_plug_in'
-CANSAS_NS = "cansas1d/1.0"
-
-CUSTOM_MODEL = 'Plugin Models'
-CUSTOM_MODEL_OLD = 'Customized Models'
-
-LIST_OF_DATA_ATTRIBUTES = [["is_data", "is_data", "bool"],
-                           ["group_id", "data_group_id", "string"],
-                           ["data_name", "data_name", "string"],
-                           ["data_id", "data_id", "string"],
-                           ["name", "name", "string"],
-                           ["data_name", "data_name", "string"]]
-LIST_OF_STATE_ATTRIBUTES = [["qmin", "qmin", "float"],
-                            ["qmax", "qmax", "float"],
-                            ["npts", "npts", "float"],
-                            ["categorycombobox", "categorycombobox", "string"],
-                            ["formfactorcombobox", "formfactorcombobox",
-                             "string"],
-                            ["structurecombobox", "structurecombobox",
-                             "string"],
-                            ["multi_factor", "multi_factor", "float"],
-                            ["magnetic_on", "magnetic_on", "bool"],
-                            ["enable_smearer", "enable_smearer", "bool"],
-                            ["disable_smearer", "disable_smearer", "bool"],
-                            ["pinhole_smearer", "pinhole_smearer", "bool"],
-                            ["slit_smearer", "slit_smearer", "bool"],
-                            ["enable_disp", "enable_disp", "bool"],
-                            ["disable_disp", "disable_disp", "bool"],
-                            ["dI_noweight", "dI_noweight", "bool"],
-                            ["dI_didata", "dI_didata", "bool"],
-                            ["dI_sqrdata", "dI_sqrdata", "bool"],
-                            ["dI_idata", "dI_idata", "bool"],
-                            ["enable2D", "enable2D", "bool"],
-                            ["cb1", "cb1", "bool"],
-                            ["tcChi", "tcChi", "float"],
-                            ["smearer", "smearer", "float"],
-                            ["smear_type", "smear_type", "string"],
-                            ["dq_l", "dq_l", "float"],
-                            ["dq_r", "dq_r", "float"],
-                            ["dx_percent", "dx_percent", "float"],
-                            ["dxl", "dxl", "float"],
-                            ["dxw", "dxw", "float"]]
-
-LIST_OF_MODEL_ATTRIBUTES = [["values", "values"],
-                            ["weights", "weights"]]
-
-DISPERSION_LIST = [["disp_obj_dict", "_disp_obj_dict", "string"]]
-
-LIST_OF_STATE_PARAMETERS = [["parameters", "parameters"],
-                            ["str_parameters", "str_parameters"],
-                            ["orientation_parameters", "orientation_params"],
-                            ["dispersity_parameters",
-                             "orientation_params_disp"],
-                            ["fixed_param", "fixed_param"],
-                            ["fittable_param", "fittable_param"]]
-LIST_OF_DATA_2D_ATTR = [["xmin", "xmin", "float"],
-                        ["xmax", "xmax", "float"],
-                        ["ymin", "ymin", "float"],
-                        ["ymax", "ymax", "float"],
-                        ["_xaxis", "_xaxis", "string"],
-                        ["_xunit", "_xunit", "string"],
-                        ["_yaxis", "_yaxis", "string"],
-                        ["_yunit", "_yunit", "string"],
-                        ["_zaxis", "_zaxis", "string"],
-                        ["_zunit", "_zunit", "string"]]
-LIST_OF_DATA_2D_VALUES = [["qx_data", "qx_data", "float"],
-                          ["qy_data", "qy_data", "float"],
-                          ["dqx_data", "dqx_data", "float"],
-                          ["dqy_data", "dqy_data", "float"],
-                          ["data", "data", "float"],
-                          ["q_data", "q_data", "float"],
-                          ["err_data", "err_data", "float"],
-                          ["mask", "mask", "bool"]]
-
-
-def parse_entry_helper(node, item):
-    """
-    Create a numpy list from value extrated from the node
-
-    :param node: node from each the value is stored
-    :param item: list name of three strings.the two first are name of data
-        attribute and the third one is the type of the value of that
-        attribute. type can be string, float, bool, etc.
-
-    : return: numpy array
-    """
-    if node is not None:
-        if item[2] == "string":
-            return str(node.get(item[0]).strip())
-        elif item[2] == "bool":
-            try:
-                return node.get(item[0]).strip() == "True"
-            except Exception:
-                return None
-        else:
-            try:
-                return float(node.get(item[0]))
-            except Exception:
-                return None
-
-
-class PageState(object):
-    """
-    Contains information to reconstruct a page of the fitpanel.
-    """
-    def __init__(self, parent=None, model=None, data=None):
-        """
-        Initialize the current state
-
-        :param model: a selected model within a page
-        :param data:
-
-        """
-        self.file = None
-        # Time of state creation
-        self.timestamp = time.time()
-        # Data member to store the dispersion object created
-        self._disp_obj_dict = {}
-        # ------------------------
-        # Data used for fitting
-        self.data = data
-        # model data
-        self.theory_data = None
-        # Is 2D
-        self.is_2D = False
-        self.images = None
-
-        # save additional information on data that dataloader.reader
-        # does not read
-        self.is_data = None
-        self.data_name = ""
-
-        if self.data is not None:
-            self.data_name = self.data.name
-        self.data_id = None
-        if self.data is not None and hasattr(self.data, "id"):
-            self.data_id = self.data.id
-        self.data_group_id = None
-        if self.data is not None and hasattr(self.data, "group_id"):
-            self.data_group_id = self.data.group_id
-
-        # reset True change the state of existing button
-        self.reset = False
-
-        # flag to allow data2D plot
-        self.enable2D = False
-        # model on which the fit would be performed
-        self.model = model
-        self.m_name = None
-        # list of process done to model
-        self.process = []
-        # fit page manager
-        self.manager = None
-        # Store the parent of this panel parent
-        # For this application fitpanel is the parent
-        self.parent = parent
-        # Event_owner is the owner of model event
-        self.event_owner = None
-        # page name
-        self.page_name = ""
-        # Contains link between model, its parameters, and panel organization
-        self.parameters = []
-        # String parameter list that can not be fitted
-        self.str_parameters = []
-        # Contains list of parameters that cannot be fitted and reference to
-        # panel objects
-        self.fixed_param = []
-        # Contains list of parameters with dispersity and reference to
-        # panel objects
-        self.fittable_param = []
-        # orientation parameters
-        self.orientation_params = []
-        # orientation parameters for gaussian dispersity
-        self.orientation_params_disp = []
-        # smearer info
-        self.smearer = None
-        self.smear_type = None
-        self.dq_l = None
-        self.dq_r = None
-        self.dx_percent = None
-        self.dx_old = False
-        self.dxl = None
-        self.dxw = None
-        # list of dispersion parameters
-        self.disp_list = []
-        if self.model is not None:
-            self.disp_list = self.model.getDispParamList()
-
-        self.disp_cb_dict = {}
-        self.values = {}
-        self.weights = {}
-
-        # contains link between a model and selected parameters to fit
-        self.param_toFit = []
-        # dictionary of model type and model class
-        self.model_list_box = None
-        # save the state of the context menu
-        self.saved_states = {}
-        # save selection of combobox
-        self.formfactorcombobox = None
-        self.categorycombobox = None
-        self.structurecombobox = None
-
-        # radio box to select type of model
-        # self.shape_rbutton = False
-        # self.shape_indep_rbutton = False
-        # self.struct_rbutton = False
-        # self.plugin_rbutton = False
-        # the indice of the current selection
-        self.disp_box = 0
-        # Qrange
-        # Q range
-        self.qmin = 0.001
-        self.qmax = 0.1
-        # reset data range
-        self.qmax_x = None
-        self.qmin_x = None
-
-        self.npts = None
-        self.name = ""
-        self.multi_factor = None
-        self.magnetic_on = False
-        # enable smearering state
-        self.enable_smearer = False
-        self.disable_smearer = True
-        self.pinhole_smearer = False
-        self.slit_smearer = False
-        # weighting options
-        self.dI_noweight = False
-        self.dI_didata = True
-        self.dI_sqrdata = False
-        self.dI_idata = False
-        # disperity selection
-        self.enable_disp = False
-        self.disable_disp = True
-
-        # state of selected all check button
-        self.cb1 = False
-        # store value of chisqr
-        self.tcChi = None
-        self.version = (1,0,0)
-
-    def clone(self):
-        """
-        Create a new copy of the current object
-        """
-        model = None
-        if self.model is not None:
-            model = self.model.clone()
-            model.name = self.model.name
-        obj = PageState(self.parent, model=model)
-        obj.file = copy.deepcopy(self.file)
-        obj.data = copy.deepcopy(self.data)
-        if self.data is not None:
-            self.data_name = self.data.name
-        obj.data_name = self.data_name
-        obj.is_data = self.is_data
-        obj.model_list_box = copy.deepcopy(self.model_list_box)
-
-        obj.categorycombobox = self.categorycombobox
-        obj.formfactorcombobox = self.formfactorcombobox
-        obj.structurecombobox = self.structurecombobox
-
-        # obj.shape_rbutton = self.shape_rbutton
-        # obj.shape_indep_rbutton = self.shape_indep_rbutton
-        # obj.struct_rbutton = self.struct_rbutton
-        # obj.plugin_rbutton = self.plugin_rbutton
-
-        obj.manager = self.manager
-        obj.event_owner = self.event_owner
-        obj.disp_list = copy.deepcopy(self.disp_list)
-
-        obj.enable2D = copy.deepcopy(self.enable2D)
-        obj.parameters = copy.deepcopy(self.parameters)
-        obj.str_parameters = copy.deepcopy(self.str_parameters)
-        obj.fixed_param = copy.deepcopy(self.fixed_param)
-        obj.fittable_param = copy.deepcopy(self.fittable_param)
-        obj.orientation_params = copy.deepcopy(self.orientation_params)
-        obj.orientation_params_disp = \
-            copy.deepcopy(self.orientation_params_disp)
-        obj.enable_disp = copy.deepcopy(self.enable_disp)
-        obj.disable_disp = copy.deepcopy(self.disable_disp)
-        obj.tcChi = self.tcChi
-
-        if len(self._disp_obj_dict) > 0:
-            for k, v in self._disp_obj_dict.iteritems():
-                obj._disp_obj_dict[k] = v
-        if len(self.disp_cb_dict) > 0:
-            for k, v in self.disp_cb_dict.iteritems():
-                obj.disp_cb_dict[k] = v
-        if len(self.values) > 0:
-            for k, v in self.values.iteritems():
-                obj.values[k] = v
-        if len(self.weights) > 0:
-            for k, v in self.weights.iteritems():
-                obj.weights[k] = v
-        obj.enable_smearer = copy.deepcopy(self.enable_smearer)
-        obj.disable_smearer = copy.deepcopy(self.disable_smearer)
-        obj.pinhole_smearer = copy.deepcopy(self.pinhole_smearer)
-        obj.slit_smearer = copy.deepcopy(self.slit_smearer)
-        obj.smear_type = copy.deepcopy(self.smear_type)
-        obj.dI_noweight = copy.deepcopy(self.dI_noweight)
-        obj.dI_didata = copy.deepcopy(self.dI_didata)
-        obj.dI_sqrdata = copy.deepcopy(self.dI_sqrdata)
-        obj.dI_idata = copy.deepcopy(self.dI_idata)
-        obj.dq_l = copy.deepcopy(self.dq_l)
-        obj.dq_r = copy.deepcopy(self.dq_r)
-        obj.dx_percent = copy.deepcopy(self.dx_percent)
-        obj.dx_old = copy.deepcopy(self.dx_old)
-        obj.dxl = copy.deepcopy(self.dxl)
-        obj.dxw = copy.deepcopy(self.dxw)
-        obj.disp_box = copy.deepcopy(self.disp_box)
-        obj.qmin = copy.deepcopy(self.qmin)
-        obj.qmax = copy.deepcopy(self.qmax)
-        obj.multi_factor = self.multi_factor
-        obj.magnetic_on = self.magnetic_on
-        obj.npts = copy.deepcopy(self.npts)
-        obj.cb1 = copy.deepcopy(self.cb1)
-        obj.smearer = copy.deepcopy(self.smearer)
-        obj.version = copy.deepcopy(self.version)
-
-        for name, state in self.saved_states.iteritems():
-            copy_name = copy.deepcopy(name)
-            copy_state = state.clone()
-            obj.saved_states[copy_name] = copy_state
-        return obj
-
-    def _old_first_model(self):
-        """
-        Handle save states from 4.0.1 and before where the first item in the
-        selection boxes of category, formfactor and structurefactor were not
-        saved.
-        :return: None
-        """
-        if self.categorycombobox == CUSTOM_MODEL_OLD:
-            self.categorycombobox = CUSTOM_MODEL
-        if self.formfactorcombobox == '':
-            FIRST_FORM = {
-                'Shapes' : 'BCCrystalModel',
-                'Uncategorized' : 'LineModel',
-                'StructureFactor' : 'HardsphereStructure',
-                'Ellipsoid' : 'core_shell_ellipsoid',
-                'Lamellae' : 'lamellar',
-                'Paracrystal' : 'bcc_paracrystal',
-                'Parallelepiped' : 'core_shell_parallelepiped',
-                'Shape Independent' : 'be_polyelectrolyte',
-                'Sphere' : 'adsorbed_layer',
-                'Structure Factor' : 'hardsphere',
-                CUSTOM_MODEL : ''
-            }
-            if self.categorycombobox == '':
-                if len(self.parameters) == 3:
-                    self.categorycombobox = "Shape-Independent"
-                    self.formfactorcombobox = 'PowerLawAbsModel'
-                elif len(self.parameters) == 9:
-                    self.categorycombobox = 'Cylinder'
-                    self.formfactorcombobox = 'barbell'
-                else:
-                    msg = "Save state does not have enough information to load"
-                    msg += " the all of the data."
-                    logger.warning(msg=msg)
-            else:
-                self.formfactorcombobox = FIRST_FORM[self.categorycombobox]
-
-    @staticmethod
-    def param_remap_to_sasmodels_convert(params, is_string=False):
-        """
-        Remaps the parameters for sasmodels conversion
-
-        :param params: list of parameters (likely self.parameters)
-        :return: remapped dictionary of parameters
-        """
-        p = dict()
-        for fittable, name, value, _, uncert, lower, upper, units in params:
-            if not value:
-                value = np.nan
-            if not uncert or uncert[1] == '' or uncert[1] == 'None':
-                uncert[0] = False
-                uncert[1] = np.nan
-            if not upper or upper[1] == '' or upper[1] == 'None':
-                upper[0] = False
-                upper[1] = np.nan
-            if not lower or lower[1] == '' or lower[1] == 'None':
-                lower[0] = False
-                lower[1] = np.nan
-            if is_string:
-                p[name] = str(value)
-            else:
-                p[name] = float(value)
-            p[name + ".fittable"] = bool(fittable)
-            p[name + ".std"] = float(uncert[1])
-            p[name + ".upper"] = float(upper[1])
-            p[name + ".lower"] = float(lower[1])
-            p[name + ".units"] = units
-        return p
-
-    @staticmethod
-    def param_remap_from_sasmodels_convert(params):
-        """
-        Converts {name : value} map back to [] param list
-        :param params: parameter map returned from sasmodels
-        :return: None
-        """
-        p_map = []
-        for name, info in params.iteritems():
-            if ".fittable" in name or ".std" in name or ".upper" in name or \
-                            ".lower" in name or ".units" in name:
-                pass
-            else:
-                fittable = params.get(name + ".fittable", True)
-                std = params.get(name + ".std", '0.0')
-                upper = params.get(name + ".upper", 'inf')
-                lower = params.get(name + ".lower", '-inf')
-                units = params.get(name + ".units")
-                if std is not None and std is not np.nan:
-                    std = [True, str(std)]
-                else:
-                    std = [False, '']
-                if lower is not None and lower is not np.nan:
-                    lower = [True, str(lower)]
-                else:
-                    lower = [True, '-inf']
-                if upper is not None and upper is not np.nan:
-                    upper = [True, str(upper)]
-                else:
-                    upper = [True, 'inf']
-                param_list = [bool(fittable), str(name), str(info),
-                              "+/-", std, lower, upper, str(units)]
-                p_map.append(param_list)
-        return p_map
-
-    def _convert_to_sasmodels(self):
-        """
-        Convert parameters to a form usable by sasmodels converter
-
-        :return: None
-        """
-        # Create conversion dictionary to send to sasmodels
-        self._old_first_model()
-        p = self.param_remap_to_sasmodels_convert(self.parameters)
-        structurefactor, params = convert.convert_model(self.structurecombobox,
-                                                        p, False, self.version)
-        formfactor, params = convert.convert_model(self.formfactorcombobox,
-                                                   params, False, self.version)
-        if len(self.str_parameters) > 0:
-            str_pars = self.param_remap_to_sasmodels_convert(
-                self.str_parameters, True)
-            formfactor, str_params = convert.convert_model(
-                self.formfactorcombobox, str_pars, False, self.version)
-            for key, value in str_params.iteritems():
-                params[key] = value
-
-        if self.formfactorcombobox == 'SphericalSLDModel':
-            self.multi_factor += 1
-        self.formfactorcombobox = formfactor
-        self.structurecombobox = structurefactor
-        self.parameters = []
-        self.parameters = self.param_remap_from_sasmodels_convert(params)
-
-    def _repr_helper(self, list, rep):
-        """
-        Helper method to print a state
-        """
-        for item in list:
-            rep += "parameter name: %s \n" % str(item[1])
-            rep += "value: %s\n" % str(item[2])
-            rep += "selected: %s\n" % str(item[0])
-            rep += "error displayed : %s \n" % str(item[4][0])
-            rep += "error value:%s \n" % str(item[4][1])
-            rep += "minimum displayed : %s \n" % str(item[5][0])
-            rep += "minimum value : %s \n" % str(item[5][1])
-            rep += "maximum displayed : %s \n" % str(item[6][0])
-            rep += "maximum value : %s \n" % str(item[6][1])
-            rep += "parameter unit: %s\n\n" % str(item[7])
-        return rep
-
-    def __repr__(self):
-        """
-        output string for printing
-        """
-        rep = "\nState name: %s\n" % self.file
-        t = time.localtime(self.timestamp)
-        time_str = time.strftime("%b %d %Y %H:%M:%S ", t)
-
-        rep += "State created: %s\n" % time_str
-        rep += "State form factor combobox selection: %s\n" % \
-               self.formfactorcombobox
-        rep += "State structure factor combobox selection: %s\n" % \
-               self.structurecombobox
-        rep += "is data : %s\n" % self.is_data
-        rep += "data's name : %s\n" % self.data_name
-        rep += "data's id : %s\n" % self.data_id
-        if self.model is not None:
-            m_name = self.model.__class__.__name__
-            if m_name == 'Model':
-                m_name = self.m_name
-            rep += "model name : %s\n" % m_name
-        else:
-            rep += "model name : None\n"
-        rep += "multi_factor : %s\n" % str(self.multi_factor)
-        rep += "magnetic_on : %s\n" % str(self.magnetic_on)
-        rep += "model type (Category) selected: %s\n" % self.categorycombobox
-        rep += "data : %s\n" % str(self.data)
-        rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \
-               (str(self.qmin), str(self.qmax), str(self.npts))
-        rep += "Dispersion selection : %s\n" % str(self.disp_box)
-        rep += "Smearing enable : %s\n" % str(self.enable_smearer)
-        rep += "Smearing disable : %s\n" % str(self.disable_smearer)
-        rep += "Pinhole smearer enable : %s\n" % str(self.pinhole_smearer)
-        rep += "Slit smearer enable : %s\n" % str(self.slit_smearer)
-        rep += "Dispersity enable : %s\n" % str(self.enable_disp)
-        rep += "Dispersity disable : %s\n" % str(self.disable_disp)
-        rep += "Slit smearer enable: %s\n" % str(self.slit_smearer)
-
-        rep += "dI_noweight : %s\n" % str(self.dI_noweight)
-        rep += "dI_didata : %s\n" % str(self.dI_didata)
-        rep += "dI_sqrdata : %s\n" % str(self.dI_sqrdata)
-        rep += "dI_idata : %s\n" % str(self.dI_idata)
-
-        rep += "2D enable : %s\n" % str(self.enable2D)
-        rep += "All parameters checkbox selected: %s\n" % self.cb1
-        rep += "Value of Chisqr : %s\n" % str(self.tcChi)
-        rep += "Smear object : %s\n" % self.smearer
-        rep += "Smear type : %s\n" % self.smear_type
-        rep += "dq_l  : %s\n" % self.dq_l
-        rep += "dq_r  : %s\n" % self.dq_r
-        rep += "dx_percent  : %s\n" % str(self.dx_percent)
-        rep += "dxl  : %s\n" % str(self.dxl)
-        rep += "dxw : %s\n" % str(self.dxw)
-        rep += "model  : %s\n\n" % str(self.model)
-        temp_parameters = []
-        temp_fittable_param = []
-        if self.data.__class__.__name__ == "Data2D":
-            self.is_2D = True
-        else:
-            self.is_2D = False
-        if self.data is not None:
-            if not self.is_2D:
-                for item in self.parameters:
-                    if item not in self.orientation_params:
-                        temp_parameters.append(item)
-                for item in self.fittable_param:
-                    if item not in self.orientation_params_disp:
-                        temp_fittable_param.append(item)
-            else:
-                temp_parameters = self.parameters
-                temp_fittable_param = self.fittable_param
-
-            rep += "number parameters(self.parameters): %s\n" % \
-                   len(temp_parameters)
-            rep = self._repr_helper(list=temp_parameters, rep=rep)
-            rep += "number str_parameters(self.str_parameters): %s\n" % \
-                   len(self.str_parameters)
-            rep = self._repr_helper(list=self.str_parameters, rep=rep)
-            rep += "number fittable_param(self.fittable_param): %s\n" % \
-                   len(temp_fittable_param)
-            rep = self._repr_helper(list=temp_fittable_param, rep=rep)
-        return rep
-
-    def _get_report_string(self):
-        """
-        Get the values (strings) from __str__ for report
-        """
-        # Dictionary of the report strings
-        repo_time = ""
-        model_name = ""
-        title = ""
-        title_name = ""
-        file_name = ""
-        param_string = ""
-        paramval_string = ""
-        chi2_string = ""
-        q_range = ""
-        strings = self.__repr__()
-        fixed_parameter = False
-        lines = strings.split('\n')
-        # get all string values from __str__()
-        for line in lines:
-            # Skip lines which are not key: value pairs, which includes
-            # blank lines and freeform notes in SASNotes fields.
-            if not ':' in line:
-                #msg = "Report string expected 'name: value' but got %r" % line
-                #logger.error(msg)
-                continue
-
-            name, value = [s.strip() for s in line.split(":", 1)]
-            if name == "State created":
-                repo_time = value
-            elif name == "parameter name":
-                val_name = value.split(".")
-                if len(val_name) > 1:
-                    if val_name[1].count("width"):
-                        param_string += value + ','
-                    else:
-                        continue
-                else:
-                    param_string += value + ','
-            elif name == "value":
-                param_string += value + ','
-            elif name == "selected":
-                # remember if it is fixed when reporting error value
-                fixed_parameter = (value == u'False')
-            elif name == "error value":
-                if fixed_parameter:
-                    param_string += '(fixed),'
-                else:
-                    param_string += value + ','
-            elif name == "parameter unit":
-                param_string += value + ':'
-            elif name == "Value of Chisqr":
-                chi2 = ("Chi2/Npts = " + value)
-                chi2_string = CENTRE % chi2
-            elif name == "Title":
-                if len(value.strip()) == 0:
-                    continue
-                title = value + " [" + repo_time + "]"
-                title_name = HEADER % title
-            elif name == "data":
-                try:
-                    # parsing "data : File:     filename [mmm dd hh:mm]"
-                    name = value.split(':', 1)[1].strip()
-                    file_value = "File name:" + name
-                    file_name = CENTRE % file_value
-                    if len(title) == 0:
-                        title = name + " [" + repo_time + "]"
-                        title_name = HEADER % title
-                except Exception:
-                    msg = "While parsing 'data: ...'\n"
-                    logger.error(msg + traceback.format_exc())
-            elif name == "model name":
-                try:
-                    modelname = "Model name:" + value
-                except Exception:
-                    modelname = "Model name:" + " NAN"
-                model_name = CENTRE % modelname
-
-            elif name == "Plotting Range":
-                try:
-                    parts = value.split(':')
-                    q_range = parts[0] + " = " + parts[1] \
-                            + " = " + parts[2].split(",")[0]
-                    q_name = ("Q Range:    " + q_range)
-                    q_range = CENTRE % q_name
-                except Exception:
-                    msg = "While parsing 'Plotting Range: ...'\n"
-                    logger.error(msg + traceback.format_exc())
-
-        paramval = ""
-        for lines in param_string.split(":"):
-            line = lines.split(",")
-            if len(lines) > 0:
-                param = line[0]
-                param += " = " + line[1]
-                if len(line[2].split()) > 0 and not line[2].count("None"):
-                    param += " +- " + line[2]
-                if len(line[3].split()) > 0 and not line[3].count("None"):
-                    param += " " + line[3]
-                if not paramval.count(param):
-                    paramval += param + "\n"
-                    paramval_string += CENTRE % param + "\n"
-
-        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
-                      (title, file, q_name, chi2, paramval)
-
-        title_name = self._check_html_format(title_name)
-        file_name = self._check_html_format(file_name)
-        title = self._check_html_format(title)
-
-        html_string = title_name + "\n" + file_name + \
-                                   "\n" + model_name + \
-                                   "\n" + q_range + \
-                                   "\n" + chi2_string + \
-                                   "\n" + ELINE + \
-                                   "\n" + paramval_string + \
-                                   "\n" + ELINE + \
-                                   "\n" + FEET_1 % title
-
-        return html_string, text_string, title
-
-    def _check_html_format(self, name):
-        """
-        Check string '%' for html format
-        """
-        if name.count('%'):
-            name = name.replace('%', '&#37')
-
-        return name
-
-    def report(self, fig_urls):
-        """
-        Invoke report dialog panel
-
-        : param figs: list of pylab figures [list]
-        """
-        # get the strings for report
-        html_str, text_str, title = self._get_report_string()
-        # Allow 2 figures to append
-        image_links = [FEET_2%fig for fig in fig_urls]
-
-        # final report html strings
-        report_str = html_str + ELINE.join(image_links)
-
-        return report_str, text_str
-
-    def _to_xml_helper(self, thelist, element, newdoc):
-        """
-        Helper method to create xml file for saving state
-        """
-        for item in thelist:
-            sub_element = newdoc.createElement('parameter')
-            sub_element.setAttribute('name', str(item[1]))
-            sub_element.setAttribute('value', str(item[2]))
-            sub_element.setAttribute('selected_to_fit', str(item[0]))
-            sub_element.setAttribute('error_displayed', str(item[4][0]))
-            sub_element.setAttribute('error_value', str(item[4][1]))
-            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
-            sub_element.setAttribute('minimum_value', str(item[5][1]))
-            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
-            sub_element.setAttribute('maximum_value', str(item[6][1]))
-            sub_element.setAttribute('unit', str(item[7]))
-            element.appendChild(sub_element)
-
-    def to_xml(self, file="fitting_state.fitv", doc=None,
-               entry_node=None, batch_fit_state=None):
-        """
-        Writes the state of the fit panel to file, as XML.
-
-        Compatible with standalone writing, or appending to an
-        already existing XML document. In that case, the XML document is
-        required. An optional entry node in the XML document may also be given.
-
-        :param file: file to write to
-        :param doc: XML document object [optional]
-        :param entry_node: XML node within the XML document at which we
-                           will append the data [optional]
-        :param batch_fit_state: simultaneous fit state
-        """
-        from xml.dom.minidom import getDOMImplementation
-
-        # Check whether we have to write a standalone XML file
-        if doc is None:
-            impl = getDOMImplementation()
-            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
-            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
-            top_element = newdoc.documentElement
-        else:
-            # We are appending to an existing document
-            newdoc = doc
-            try:
-                top_element = newdoc.createElement(FITTING_NODE_NAME)
-            except Exception:
-                string = etree.tostring(doc, pretty_print=True)
-                newdoc = parseString(string)
-                top_element = newdoc.createElement(FITTING_NODE_NAME)
-            if entry_node is None:
-                newdoc.documentElement.appendChild(top_element)
-            else:
-                try:
-                    entry_node.appendChild(top_element)
-                except Exception:
-                    node_name = entry_node.tag
-                    node_list = newdoc.getElementsByTagName(node_name)
-                    entry_node = node_list.item(0)
-                    entry_node.appendChild(top_element)
-
-        attr = newdoc.createAttribute("version")
-        from sas import sasview
-        attr.nodeValue = sasview.__version__
-        # attr.nodeValue = '1.0'
-        top_element.setAttributeNode(attr)
-
-        # File name
-        element = newdoc.createElement("filename")
-        if self.file is not None:
-            element.appendChild(newdoc.createTextNode(str(self.file)))
-        else:
-            element.appendChild(newdoc.createTextNode(str(file)))
-        top_element.appendChild(element)
-
-        element = newdoc.createElement("timestamp")
-        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
-        attr = newdoc.createAttribute("epoch")
-        attr.nodeValue = str(self.timestamp)
-        element.setAttributeNode(attr)
-        top_element.appendChild(element)
-
-        # Inputs
-        inputs = newdoc.createElement("Attributes")
-        top_element.appendChild(inputs)
-
-        if self.data is not None and hasattr(self.data, "group_id"):
-            self.data_group_id = self.data.group_id
-        if self.data is not None and hasattr(self.data, "is_data"):
-            self.is_data = self.data.is_data
-        if self.data is not None:
-            self.data_name = self.data.name
-        if self.data is not None and hasattr(self.data, "id"):
-            self.data_id = self.data.id
-
-        for item in LIST_OF_DATA_ATTRIBUTES:
-            element = newdoc.createElement(item[0])
-            element.setAttribute(item[0], str(getattr(self, item[1])))
-            inputs.appendChild(element)
-
-        for item in LIST_OF_STATE_ATTRIBUTES:
-            element = newdoc.createElement(item[0])
-            element.setAttribute(item[0], str(getattr(self, item[1])))
-            inputs.appendChild(element)
-
-        # For self.values ={ disp_param_name: [vals,...],...}
-        # and for self.weights ={ disp_param_name: [weights,...],...}
-        for item in LIST_OF_MODEL_ATTRIBUTES:
-            element = newdoc.createElement(item[0])
-            value_list = getattr(self, item[1])
-            for key, value in value_list.iteritems():
-                sub_element = newdoc.createElement(key)
-                sub_element.setAttribute('name', str(key))
-                for val in value:
-                    sub_element.appendChild(newdoc.createTextNode(str(val)))
-
-                element.appendChild(sub_element)
-            inputs.appendChild(element)
-
-        # Create doc for the dictionary of self._disp_obj_dic
-        for tagname, varname, tagtype in DISPERSION_LIST:
-            element = newdoc.createElement(tagname)
-            value_list = getattr(self, varname)
-            for key, value in value_list.iteritems():
-                sub_element = newdoc.createElement(key)
-                sub_element.setAttribute('name', str(key))
-                sub_element.setAttribute('value', str(value))
-                element.appendChild(sub_element)
-            inputs.appendChild(element)
-
-        for item in LIST_OF_STATE_PARAMETERS:
-            element = newdoc.createElement(item[0])
-            self._to_xml_helper(thelist=getattr(self, item[1]),
-                                element=element, newdoc=newdoc)
-            inputs.appendChild(element)
-
-        # Combined and Simultaneous Fit Parameters
-        if batch_fit_state is not None:
-            batch_combo = newdoc.createElement('simultaneous_fit')
-            top_element.appendChild(batch_combo)
-
-            # Simultaneous Fit Number For Linking Later
-            element = newdoc.createElement('sim_fit_number')
-            element.setAttribute('fit_number', str(batch_fit_state.fit_page_no))
-            batch_combo.appendChild(element)
-
-            # Save constraints
-            constraints = newdoc.createElement('constraints')
-            batch_combo.appendChild(constraints)
-            for constraint in batch_fit_state.constraints_list:
-                if constraint.model_cbox.GetValue() != "":
-                    # model_cbox, param_cbox, egal_txt, constraint,
-                    # btRemove, sizer
-                    doc_cons = newdoc.createElement('constraint')
-                    doc_cons.setAttribute('model_cbox',
-                                          str(constraint.model_cbox.GetValue()))
-                    doc_cons.setAttribute('param_cbox',
-                                          str(constraint.param_cbox.GetValue()))
-                    doc_cons.setAttribute('egal_txt',
-                                          str(constraint.egal_txt.GetLabel()))
-                    doc_cons.setAttribute('constraint',
-                                          str(constraint.constraint.GetValue()))
-                    constraints.appendChild(doc_cons)
-
-            # Save all models
-            models = newdoc.createElement('model_list')
-            batch_combo.appendChild(models)
-            for model in batch_fit_state.model_list:
-                doc_model = newdoc.createElement('model_list_item')
-                doc_model.setAttribute('checked', str(model[0].GetValue()))
-                keys = model[1].keys()
-                doc_model.setAttribute('name', str(keys[0]))
-                values = model[1].get(keys[0])
-                doc_model.setAttribute('fit_number', str(model[2]))
-                doc_model.setAttribute('fit_page_source', str(model[3]))
-                doc_model.setAttribute('model_name', str(values.model.id))
-                models.appendChild(doc_model)
-
-            # Select All Checkbox
-            element = newdoc.createElement('select_all')
-            if batch_fit_state.select_all:
-                element.setAttribute('checked', 'True')
-            else:
-                element.setAttribute('checked', 'False')
-            batch_combo.appendChild(element)
-
-        # Save the file
-        if doc is None:
-            fd = open(file, 'w')
-            fd.write(newdoc.toprettyxml())
-            fd.close()
-            return None
-        else:
-            return newdoc
-
-    def _from_xml_helper(self, node, list):
-        """
-        Helper function to write state to xml
-        """
-        for item in node:
-            try:
-                name = item.get('name')
-            except:
-                name = None
-            try:
-                value = item.get('value')
-            except:
-                value = None
-            try:
-                selected_to_fit = (item.get('selected_to_fit') == "True")
-            except:
-                selected_to_fit = None
-            try:
-                error_displayed = (item.get('error_displayed') == "True")
-            except:
-                error_displayed = None
-            try:
-                error_value = item.get('error_value')
-            except:
-                error_value = None
-            try:
-                minimum_displayed = (item.get('minimum_displayed') == "True")
-            except:
-                minimum_displayed = None
-            try:
-                minimum_value = item.get('minimum_value')
-            except:
-                minimum_value = None
-            try:
-                maximum_displayed = (item.get('maximum_displayed') == "True")
-            except:
-                maximum_displayed = None
-            try:
-                maximum_value = item.get('maximum_value')
-            except:
-                maximum_value = None
-            try:
-                unit = item.get('unit')
-            except:
-                unit = None
-            list.append([selected_to_fit, name, value, "+/-",
-                         [error_displayed, error_value],
-                         [minimum_displayed, minimum_value],
-                         [maximum_displayed, maximum_value], unit])
-
-    def from_xml(self, file=None, node=None):
-        """
-        Load fitting state from a file
-
-        :param file: .fitv file
-        :param node: node of a XML document to read from
-        """
-        if file is not None:
-            msg = "PageState no longer supports non-CanSAS"
-            msg += " format for fitting files"
-            raise RuntimeError, msg
-
-        if node.get('version'):
-            # Get the version for model conversion purposes
-            self.version = tuple(int(e) for e in
-                                 str.split(node.get('version'), "."))
-            # The tuple must be at least 3 items long
-            while len(self.version) < 3:
-                ver_list = list(self.version)
-                ver_list.append(0)
-                self.version = tuple(ver_list)
-
-            # Get file name
-            entry = get_content('ns:filename', node)
-            if entry is not None:
-                self.file = entry.text.strip()
-
-            # Get time stamp
-            entry = get_content('ns:timestamp', node)
-            if entry is not None and entry.get('epoch'):
-                try:
-                    self.timestamp = float(entry.get('epoch'))
-                except Exception:
-                    msg = "PageState.fromXML: Could not"
-                    msg += " read timestamp\n %s" % sys.exc_value
-                    logger.error(msg)
-
-            if entry is not None:
-                # Parse fitting attributes
-                entry = get_content('ns:Attributes', node)
-                for item in LIST_OF_DATA_ATTRIBUTES:
-                    node = get_content('ns:%s' % item[0], entry)
-                    setattr(self, item[0], parse_entry_helper(node, item))
-
-                dx_old_node = get_content('ns:%s' % 'dx_min', entry)
-                for item in LIST_OF_STATE_ATTRIBUTES:
-                    if item[0] == "dx_percent" and dx_old_node is not None:
-                        dxmin = ["dx_min", "dx_min", "float"]
-                        setattr(self, item[0], parse_entry_helper(dx_old_node,
-                                                                  dxmin))
-                        self.dx_old = True
-                    else:
-                        node = get_content('ns:%s' % item[0], entry)
-                        setattr(self, item[0], parse_entry_helper(node, item))
-
-                for item in LIST_OF_STATE_PARAMETERS:
-                    node = get_content("ns:%s" % item[0], entry)
-                    self._from_xml_helper(node=node,
-                                          list=getattr(self, item[1]))
-
-                # Recover _disp_obj_dict from xml file
-                self._disp_obj_dict = {}
-                for tagname, varname, tagtype in DISPERSION_LIST:
-                    node = get_content("ns:%s" % tagname, entry)
-                    for attr in node:
-                        parameter = str(attr.get('name'))
-                        value = attr.get('value')
-                        if value.startswith("<"):
-                            try:
-                                # <path.to.NamedDistribution object/instance...>
-                                cls_name = value[1:].split()[0].split('.')[-1]
-                                cls = getattr(sasmodels.weights, cls_name)
-                                value = cls.type
-                            except Exception:
-                                base = "unable to load distribution %r for %s"
-                                logger.error(base % (value, parameter))
-                                continue
-                        _disp_obj_dict = getattr(self, varname)
-                        _disp_obj_dict[parameter] = value
-
-                # get self.values and self.weights dic. if exists
-                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
-                    node = get_content("ns:%s" % tagname, entry)
-                    dic = {}
-                    value_list = []
-                    for par in node:
-                        name = par.get('name')
-                        values = par.text.split()
-                        # Get lines only with numbers
-                        for line in values:
-                            try:
-                                val = float(line)
-                                value_list.append(val)
-                            except Exception:
-                                # pass if line is empty (it happens)
-                                msg = ("Error reading %r from %s %s\n"
-                                       % (line, tagname, name))
-                                logger.error(msg + traceback.format_exc())
-                        dic[name] = np.array(value_list)
-                    setattr(self, varname, dic)
-
-
-class Reader(CansasReader):
-    """
-    Class to load a .fitv fitting file
-    """
-    # File type
-    type_name = "Fitting"
-
-    # Wildcards
-    type = ["Fitting files (*.fitv)|*.fitv"
-            "SASView file (*.svs)|*.svs"]
-    # List of allowed extensions
-    ext = ['.fitv', '.FITV', '.svs', 'SVS']
-
-    def __init__(self, call_back=None, cansas=True):
-        CansasReader.__init__(self)
-        """
-        Initialize the call-back method to be called
-        after we load a file
-
-        :param call_back: call-back method
-        :param cansas:  True = files will be written/read in CanSAS format
-                        False = write CanSAS format
-
-        """
-        # Call back method to be executed after a file is read
-        self.call_back = call_back
-        # CanSAS format flag
-        self.cansas = cansas
-        self.state = None
-        # batch fitting params for saving
-        self.batchfit_params = []
-
-    def get_state(self):
-        return self.state
-
-    def read(self, path):
-        """
-        Load a new P(r) inversion state from file
-
-        :param path: file path
-
-        """
-        if self.cansas:
-            return self._read_cansas(path)
-
-    def _parse_state(self, entry):
-        """
-        Read a fit result from an XML node
-
-        :param entry: XML node to read from
-        :return: PageState object
-        """
-        # Create an empty state
-        state = None
-        # Locate the P(r) node
-        try:
-            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
-                                namespaces={'ns': CANSAS_NS})
-            if nodes:
-                # Create an empty state
-                state = PageState()
-                state.from_xml(node=nodes[0])
-
-        except:
-            logger.info("XML document does not contain fitting information.\n"
-                         + traceback.format_exc())
-
-        return state
-
-    def _parse_simfit_state(self, entry):
-        """
-        Parses the saved data for a simultaneous fit
-        :param entry: XML object to read from
-        :return: XML object for a simultaneous fit or None
-        """
-        nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
-                            namespaces={'ns': CANSAS_NS})
-        if nodes:
-            simfitstate = nodes[0].xpath('ns:simultaneous_fit',
-                                         namespaces={'ns': CANSAS_NS})
-            if simfitstate:
-                from simfitpage import SimFitPageState
-                sim_fit_state = SimFitPageState()
-                simfitstate_0 = simfitstate[0]
-                all = simfitstate_0.xpath('ns:select_all',
-                                          namespaces={'ns': CANSAS_NS})
-                atts = all[0].attrib
-                checked = atts.get('checked')
-                sim_fit_state.select_all = bool(checked)
-                model_list = simfitstate_0.xpath('ns:model_list',
-                                                 namespaces={'ns': CANSAS_NS})
-                model_list_items = model_list[0].xpath('ns:model_list_item',
-                                                       namespaces={'ns':
-                                                                    CANSAS_NS})
-                for model in model_list_items:
-                    attrs = model.attrib
-                    sim_fit_state.model_list.append(attrs)
-
-                constraints = simfitstate_0.xpath('ns:constraints',
-                                                namespaces={'ns': CANSAS_NS})
-                constraint_list = constraints[0].xpath('ns:constraint',
-                                               namespaces={'ns': CANSAS_NS})
-                for constraint in constraint_list:
-                    attrs = constraint.attrib
-                    sim_fit_state.constraints_list.append(attrs)
-
-                return sim_fit_state
-            else:
-                return None
-
-    def _parse_save_state_entry(self, dom):
-        """
-        Parse a SASentry
-
-        :param node: SASentry node
-
-        :return: Data1D/Data2D object
-
-        """
-        node = dom.xpath('ns:data_class', namespaces={'ns': CANSAS_NS})
-        return_value, _ = self._parse_entry(dom)
-        return return_value, _
-
-    def _read_cansas(self, path):
-        """
-        Load data and fitting information from a CanSAS XML file.
-
-        :param path: file path
-        :return: Data1D object if a single SASentry was found,
-                    or a list of Data1D objects if multiple entries were found,
-                    or None of nothing was found
-        :raise RuntimeError: when the file can't be opened
-        :raise ValueError: when the length of the data vectors are inconsistent
-        """
-        output = []
-        simfitstate = None
-        basename = os.path.basename(path)
-        root, extension = os.path.splitext(basename)
-        ext = extension.lower()
-        try:
-            if os.path.isfile(path):
-                if ext in self.ext or ext == '.xml':
-                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
-                    # Check the format version number
-                    # Specifying the namespace will take care of the file
-                    # format version
-                    root = tree.getroot()
-                    entry_list = root.xpath('ns:SASentry',
-                                            namespaces={'ns': CANSAS_NS})
-                    for entry in entry_list:
-                        try:
-                            sas_entry, _ = self._parse_save_state_entry(entry)
-                        except:
-                            raise
-                        fitstate = self._parse_state(entry)
-
-                        # state could be None when .svs file is loaded
-                        # in this case, skip appending to output
-                        if fitstate is not None:
-                            sas_entry.meta_data['fitstate'] = fitstate
-                            sas_entry.filename = fitstate.file
-                            output.append(sas_entry)
-
-            else:
-                self.call_back(format=ext)
-                raise RuntimeError, "%s is not a file" % path
-
-            # Return output consistent with the loader's api
-            if len(output) == 0:
-                self.call_back(state=None, datainfo=None, format=ext)
-                return None
-            else:
-                for ind in range(len(output)):
-                    # Call back to post the new state
-                    state = output[ind].meta_data['fitstate']
-                    t = time.localtime(state.timestamp)
-                    time_str = time.strftime("%b %d %H:%M", t)
-                    # Check that no time stamp is already appended
-                    max_char = state.file.find("[")
-                    if max_char < 0:
-                        max_char = len(state.file)
-                    original_fname = state.file[0:max_char]
-                    state.file = original_fname + ' [' + time_str + ']'
-
-                    if state is not None and state.is_data is not None:
-                        output[ind].is_data = state.is_data
-
-                    output[ind].filename = state.file
-                    state.data = output[ind]
-                    state.data.name = output[ind].filename  # state.data_name
-                    state.data.id = state.data_id
-                    if state.is_data is not None:
-                        state.data.is_data = state.is_data
-                    if output[ind].run_name is not None\
-                         and len(output[ind].run_name) != 0:
-                        if isinstance(output[ind].run_name, dict):
-                            name = output[ind].run_name.keys()[0]
-                        else:
-                            name = output[ind].run_name
-                    else:
-                        name = original_fname
-                    state.data.group_id = name
-                    state.version = fitstate.version
-                    # store state in fitting
-                    self.call_back(state=state,
-                                   datainfo=output[ind], format=ext)
-                    self.state = state
-                simfitstate = self._parse_simfit_state(entry)
-                if simfitstate is not None:
-                    self.call_back(state=simfitstate)
-
-                return output
-        except:
-            self.call_back(format=ext)
-            raise
-
-    def write(self, filename, datainfo=None, fitstate=None):
-        """
-        Write the content of a Data1D as a CanSAS XML file only for standalone
-
-        :param filename: name of the file to write
-        :param datainfo: Data1D object
-        :param fitstate: PageState object
-
-        """
-        # Sanity check
-        if self.cansas:
-            # Add fitting information to the XML document
-            doc = self.write_toXML(datainfo, fitstate)
-            # Write the XML document
-        else:
-            doc = fitstate.to_xml(file=filename)
-
-        # Save the document no matter the type
-        fd = open(filename, 'w')
-        fd.write(doc.toprettyxml())
-        fd.close()
-
-    def write_toXML(self, datainfo=None, state=None, batchfit=None):
-        """
-        Write toXML, a helper for write(),
-        could be used by guimanager._on_save()
-
-        : return: xml doc
-        """
-
-        self.batchfit_params = batchfit
-        if state.data is None or not state.data.is_data:
-            return None
-        # make sure title and data run are filled.
-        if state.data.title is None or state.data.title == '':
-            state.data.title = state.data.name
-        if state.data.run_name is None or state.data.run_name == {}:
-            state.data.run = [str(state.data.name)]
-            state.data.run_name[0] = state.data.name
-
-        data = state.data
-        doc, sasentry = self._to_xml_doc(data)
-
-        if state is not None:
-            doc = state.to_xml(doc=doc, file=data.filename, entry_node=sasentry,
-                               batch_fit_state=self.batchfit_params)
-
-        return doc
-
-# Simple html report template
-HEADER = "<html>\n"
-HEADER += "<head>\n"
-HEADER += "<meta http-equiv=Content-Type content='text/html; "
-HEADER += "charset=windows-1252'> \n"
-HEADER += "<meta name=Generator >\n"
-HEADER += "</head>\n"
-HEADER += "<body lang=EN-US>\n"
-HEADER += "<div class=WordSection1>\n"
-HEADER += "<p class=MsoNormal><b><span ><center><font size='4' >"
-HEADER += "%s</font></center></span></center></b></p>"
-HEADER += "<p class=MsoNormal>&nbsp;</p>"
-PARA = "<p class=MsoNormal><font size='4' > %s \n"
-PARA += "</font></p>"
-CENTRE = "<p class=MsoNormal><center><font size='4' > %s \n"
-CENTRE += "</font></center></p>"
-FEET_1 = \
-"""
-<p class=MsoNormal>&nbsp;</p>
-<br>
-<p class=MsoNormal><b><span ><center> <font size='4' > Graph
-</font></span></center></b></p>
-<p class=MsoNormal>&nbsp;</p>
-<center>
-<br><font size='4' >Model Computation</font>
-<br><font size='4' >Data: "%s"</font><br>
-"""
-FEET_2 = \
-"""<img src="%s" ></img>
-"""
-FEET_3 = \
-"""</center>
-</div>
-</body>
-</html>
-"""
-ELINE = """<p class=MsoNormal>&nbsp;</p>
-"""
