Index: prview/perspectives/pr/inversion_panel.py
===================================================================
--- prview/perspectives/pr/inversion_panel.py	(revision d2ee6f65fa92abccd361d363fee80f855f73e64b)
+++ prview/perspectives/pr/inversion_panel.py	(revision 91128648ab092f16edfa19d3fa49d60b54ee4a53)
@@ -341,5 +341,9 @@
         
         # Data file
-        state.file = self.data_file.GetValue()
+        if self.manager.standalone==True:
+            state.file = self.data_file.GetValue()
+        else:
+            #TODO: save the data
+            pass
         
         # Background evaluation checkbox
Index: prview/perspectives/pr/inversion_state.py
===================================================================
--- prview/perspectives/pr/inversion_state.py	(revision 6f1f1292247c9b4409a230bcaf88a956ec42edf1)
+++ prview/perspectives/pr/inversion_state.py	(revision 91128648ab092f16edfa19d3fa49d60b54ee4a53)
@@ -9,4 +9,9 @@
 """
 import time, os
+import logging
+import DataLoader
+from DataLoader.readers.cansas_reader import Reader as CansasReader
+
+PRNODE_NAME = 'pr_inversion'
 
 ## List of P(r) inversion inputs 
@@ -111,18 +116,35 @@
         return state
         
-    def toXML(self, file="pr_state.prv"):
+    def toXML(self, file="pr_state.prv", doc=None, entry_node=None):
         """
             Writes the state of the InversionControl 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]
         """
         from xml.dom.minidom import getDOMImplementation
 
-        impl = getDOMImplementation()
-        
-        doc_type = impl.createDocumentType("pr_inversion", "1.0", "1.0")     
-        
-        newdoc = impl.createDocument(None, "pr_inversion", doc_type)
-        top_element = newdoc.documentElement
+        # Check whether we have to write a standalone XML file
+        if doc is None:
+            impl = getDOMImplementation()
+        
+            doc_type = impl.createDocumentType(PRNODE_NAME, "1.0", "1.0")     
+        
+            newdoc = impl.createDocument(None, PRNODE_NAME, doc_type)
+            top_element = newdoc.documentElement
+        else:
+            # We are appending to an existing document
+            newdoc = doc
+            top_element = newdoc.createElement(PRNODE_NAME)
+            if entry_node is None:
+                newdoc.documentElement.appendChild(top_element)
+            else:
+                entry_node.appendChild(top_element)
+            
         attr = newdoc.createAttribute("version")
         attr.nodeValue = '1.0'
@@ -130,7 +152,8 @@
         
         # File name
-        element = newdoc.createElement("filename")
-        element.appendChild(newdoc.createTextNode(self.file))
-        top_element.appendChild(element)
+        if self.file is not None:
+            element = newdoc.createElement("filename")
+            element.appendChild(newdoc.createTextNode(str(self.file)))
+            top_element.appendChild(element)
         
         element = newdoc.createElement("timestamp")
@@ -160,26 +183,34 @@
                     
         # Save the file
-        fd = open(file, 'w')
-        fd.write(newdoc.toprettyxml())
-        fd.close()
-
-    def fromXML(self, file):
+        if doc is None:
+            fd = open(file, 'w')
+            fd.write(newdoc.toprettyxml())
+            fd.close()
+            return None
+        else:
+            return newdoc.toprettyxml()
+
+    def fromXML(self, file=None, node=None):
         """
             Load a P(r) inversion state from a file
             
             @param file: .prv file
-        """
-        # Check whether the file is valid
-        if not os.path.isfile(file):
-            raise  RuntimeError, "P(r) reader: cannot open %s" % file
-        
-        from xml.dom.minidom import parse
-        doc = parse(file)
-        if doc.documentElement.tagName == 'pr_inversion':
-            if doc.documentElement.hasAttribute('version')\
-                and doc.documentElement.getAttribute('version') == '1.0':
+            @param node: node of a XML document to read from
+        """
+        if file is not None:
+            # Check whether the file is valid
+            if not os.path.isfile(file):
+                raise  RuntimeError, "P(r) reader: cannot open %s" % file
+        
+            from xml.dom.minidom import parse
+            doc = parse(file)
+            node = doc.documentElement
+            
+        if node.tagName == PRNODE_NAME:
+            if node.hasAttribute('version')\
+                and node.getAttribute('version') == '1.0':
                 
-                if doc.documentElement.hasChildNodes():
-                    for node in doc.documentElement.childNodes:
+                if node.hasChildNodes():
+                    for node in node.childNodes:
                         if node.nodeName == 'filename':
                             if node.hasChildNodes():
@@ -220,8 +251,8 @@
                                                 exec '%s = None' % out[1]
             else:
-                raise RuntimeError, "Unsupported P(r) file version: %s" % doc.documentElement.getAttribute('version')
+                raise RuntimeError, "Unsupported P(r) file version"
         
                     
-class Reader:
+class Reader(CansasReader):
     """
         Class to load a .prv P(r) inversion file
@@ -235,11 +266,17 @@
     ext=['.prv', '.PRV']  
     
-    def __init__(self, call_back):
+    def __init__(self, call_back, cansas=False):
         """
             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 = standalone mode
+            
+        """
+        ## Call back method to be executed after a file is read
         self.call_back = call_back
+        ## CanSAS format flag
+        self.cansas = cansas
         
     def read(self, path):
@@ -250,15 +287,162 @@
             @return: None
         """
+        if self.cansas==True:
+            return self._read_cansas(path)
+        else:
+            return self._read_standalone(path)
+        
+    def _read_standalone(self, path):
+        """ 
+            Load a new P(r) inversion state from file.
+            The P(r) node is assumed to be the top element.
+            
+            @param path: file path
+            @return: None
+        """
         # Read the new state from file
         state = InversionState()
-        state.fromXML(path)
+        state.fromXML(file=path)
         
         # Call back to post the new state
         self.call_back(state)
         return None
+    
+    def _parse_prstate(self, entry):
+        """
+            Read a p(r) inversion result from an XML node
+            @param entry: XML node to read from 
+            @return: InversionState object
+        """
+        from xml import xpath
+
+        # Create an empty state
+        state = InversionState()
+        
+        # Locate the P(r) node
+        try:
+            nodes = xpath.Evaluate(PRNODE_NAME, entry)
+            state.fromXML(node=nodes[0])
+        except:
+            #raise RuntimeError, "%s is not a file with P(r) information." % path
+            logging.info("XML document does not contain P(r) information.")
+            import sys
+            print sys.exc_value
+            
+        return state
+    
+    def _read_cansas(self, path):
+        """ 
+            Load data and P(r) 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
+        """
+        from xml.dom.minidom import parse
+        from xml import xpath
+        output = []
+        
+        if os.path.isfile(path):
+            basename  = os.path.basename(path)
+            root, extension = os.path.splitext(basename)
+            if  extension.lower() in self.ext or \
+                extension.lower() == '.xml':
+                
+                dom = parse(path)
+                
+                # Format 1: check whether we have a CanSAS file
+                nodes = xpath.Evaluate('SASroot', dom)
+                # Check the format version number
+                if nodes[0].hasAttributes():
+                    for i in range(nodes[0].attributes.length):
+                        if nodes[0].attributes.item(i).nodeName=='version':
+                            if nodes[0].attributes.item(i).nodeValue != self.version:
+                                raise ValueError, "cansas_reader: unrecognized version number %s" % \
+                                    nodes[0].attributes.item(i).nodeValue
+                
+                entry_list = xpath.Evaluate('SASroot/SASentry', dom)
+                for entry in entry_list:
+                    sas_entry = self._parse_entry(entry)
+                    prstate = self._parse_prstate(entry)
+                    sas_entry.meta_data['prstate'] = prstate
+                    sas_entry.filename = basename
+                    output.append(sas_entry)
+        else:
+            raise RuntimeError, "%s is not a file" % path
+        
+        # Return output consistent with the loader's api
+        if len(output)==0:
+            return None
+        elif len(output)==1:
+            return output[0]
+        else:
+            return output                
+    
+    
+    def write(self, filename, datainfo=None, prstate=None):
+        """
+            Write the content of a Data1D as a CanSAS XML file
+            
+            @param filename: name of the file to write
+            @param datainfo: Data1D object
+            @param prstate: InversionState object
+        """
+
+        # Sanity check
+        if self.cansas == True:
+            if datainfo is None:
+                datainfo = DataLoader.data_info.Data1D(x=[], y=[])    
+            elif not datainfo.__class__ == DataLoader.data_info.Data1D: 
+                raise RuntimeError, "The cansas writer expects a Data1D instance"
+        
+            # Create basic XML document
+            doc, sasentry = self._to_xml_doc(datainfo)
+        
+            # Add the P(r) information to the XML document
+            if prstate is not None:
+                prstate.toXML(doc=doc, entry_node=sasentry)
+        
+            # Write the XML document
+            fd = open(filename, 'w')
+            fd.write(doc.toprettyxml())
+            fd.close()
+        else:
+            prstate.toXML(file=filename)
+        
         
 if __name__ == "__main__": 
+    #TODO: turn all this into unit tests
+    
     state = InversionState()
     #print state.fromXML('../../test/pr_state.prv')     
     print state.fromXML('../../test/test.prv')     
     print state   
+    state.toXML('test_copy.prv')
+    
+    from DataLoader.loader import Loader
+    l = Loader()
+    datainfo = l.load("../../test/cansas1d.xml")
+    
+    def call_back(state):
+        print state
+        
+    reader = Reader(call_back)
+    reader.cansas = False
+    reader.write("test_copy_from_reader.prv", prstate=state)
+    reader.cansas = True
+    reader.write("testout.prv", datainfo, state)
+    reader.write("testout_wo_datainfo.prv", prstate=state)
+    
+    # Now try to load things back
+    reader.cansas = False
+    #print reader.read("test_copy_from_reader.prv")
+    reader.cansas = True
+    #data = reader.read("testout.prv")
+    data = reader.read("testout_wo_datainfo.prv")
+    print data
+    print data.meta_data['prstate']
+    
+    
