source: sasview/src/sans/dataloader/readers/xml_reader.py @ 92a2ecd

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 92a2ecd was ac5b69d, checked in by Jeff Krzywon <jeffery.krzywon@…>, 11 years ago

Ticket #249 fix: Saving and loading projects and analysis is now working.

  • Property mode set to 100644
File size: 9.5 KB
RevLine 
[eda8972]1"""
[2e3b055]2    Generic XML read and write utility
3   
4    Usage: Either extend xml_reader or add as a class variable.
[eda8972]5"""
6############################################################################
7#This software was developed by the University of Tennessee as part of the
8#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
9#project funded by the US National Science Foundation.
10#If you use DANSE applications to do scientific research that leads to
11#publication, we ask that you acknowledge the use of the software with the
12#following sentence:
13#This work benefited from DANSE software developed under NSF award DMR-0520547.
14#copyright 2008,2009 University of Tennessee
15#############################################################################
16
17from lxml import etree
[2e3b055]18from lxml.builder import E
[ac5b69d]19
20PARSER = etree.ETCompatXMLParser(remove_comments=True, remove_pis=False)
[eda8972]21
22class XMLreader():
[ac5b69d]23    """
24    Generic XML read and write class. Mostly helper functions.
25    Makes reading/writing XML a bit easier than calling lxml libraries directly.
26   
27    :Dependencies:
28        This class requires lxml 2.3 or higher.
29    """
[eda8972]30   
[76cd1ae]31    xml = None
32    xmldoc = None
33    xmlroot = None
34    schema = None
35    schemadoc = None
[2e3b055]36    encoding = None
[ac5b69d]37    processing_instructions = None
[76cd1ae]38   
[eda8972]39    def __init__(self, xml = None, schema = None, root = None):
40        self.xml = xml
41        self.schema = schema
[ac5b69d]42        self.processing_instructions = {}
[eda8972]43        if xml is not None:
[ac5b69d]44            self.set_xml_file(xml, root)
[eda8972]45        else:
46            self.xmldoc = None
47            self.xmlroot = None
48        if schema is not None:
[ac5b69d]49            self.set_schema(schema)
[eda8972]50        else:
51            self.schemadoc = None
52   
53    def reader(self):
54        """
55        Read in an XML file into memory and return an lxml dictionary
56        """
[ac5b69d]57        if self.validate_xml():
58            self.xmldoc = etree.parse(self.xml, parser = PARSER)
[eda8972]59        else:
[ac5b69d]60            raise etree.XMLSchemaValidateError(self, self.find_invalid_xml())
[eda8972]61        return self.xmldoc
62   
[ac5b69d]63    def set_xml_file(self, xml, root = None):
[ea67541]64        """
65        Set the XML file and parse
66        """
[eda8972]67        try:
68            self.xml = xml
[ac5b69d]69            self.xmldoc = etree.parse(self.xml, parser = PARSER)
[eda8972]70            self.xmlroot = self.xmldoc.getroot()
[17a25d4]71        except Exception:
[76cd1ae]72            self.xml = None
73            self.xmldoc = None
74            self.xmlroot = None
[eda8972]75   
[ac5b69d]76    def set_schema(self, schema):
[ea67541]77        """
78        Set the schema file and parse
79        """
[eda8972]80        try:
81            self.schema = schema
[ac5b69d]82            self.schemadoc = etree.parse(self.schema, parser = PARSER)
[17a25d4]83        except Exception:
[76cd1ae]84            self.schema = None
85            self.schemadoc = None
[eda8972]86   
[ac5b69d]87    def validate_xml(self):
[eda8972]88        """
89        Checks to see if the XML file meets the schema
90        """
91        valid = True
92        if self.schema is not None:
[ac5b69d]93            self.parse_schema_and_doc()
94            schema_check = etree.XMLSchema(self.schemadoc)
95            valid = schema_check.validate(self.xmldoc)
[eda8972]96        return valid
97   
[ac5b69d]98    def find_invalid_xml(self):
[eda8972]99        """
[ea67541]100        Finds the first offending element that should not be present in XML file
[eda8972]101        """
[ac5b69d]102        first_error = ""
103        self.parse_schema_and_doc()
[eda8972]104        schema = etree.XMLSchema(self.schemadoc)
105        try:
[ac5b69d]106            first_error = schema.assertValid(self.xmldoc)
[eda8972]107        except etree.DocumentInvalid as e:
[ac5b69d]108            first_error = str(e)
109        return first_error
[eda8972]110   
[ac5b69d]111    def parse_schema_and_doc(self):
[eda8972]112        """
113        Creates a dictionary of the parsed schema and xml files.
114        """
[ac5b69d]115        self.set_xml_file(self.xml)
116        self.set_schema(self.schema)
[ea67541]117       
[ac5b69d]118    def to_string(self, elem, pp=False, encoding=None):
[2e3b055]119        """
120        Converts an etree element into a string
121        """
122        return etree.tostring(elem, pretty_print = pp, encoding = encoding)
123   
124    def break_processing_instructions(self, string, dic):
[ea67541]125        """
[2e3b055]126        Method to break a processing instruction string apart and add to a dict
127       
128        :param string: A processing instruction as a string
129        :param dic: The dictionary to save the PIs to
[ea67541]130        """
[2e3b055]131        pi_string = string.replace("<?", "").replace("?>", "")
132        split = pi_string.split(" ", 1)
133        pi_name = split[0]
134        attr = split[1]
135        new_pi_name = self._create_unique_key(dic, pi_name)
136        dic[new_pi_name] = attr
137        return dic
[ea67541]138   
[ac5b69d]139    def set_processing_instructions(self):
[ea67541]140        """
141        Take out all processing instructions and create a dictionary from them
[2e3b055]142        If there is a default encoding, the value is also saved
[ea67541]143        """
144        dic = {}
[ac5b69d]145        proc_instr = self.xmlroot.getprevious()
146        while proc_instr is not None:
147            pi_string = self.to_string(proc_instr)
[2e3b055]148            if "?>\n<?" in pi_string:
149                pi_string = pi_string.split("?>\n<?")
[ea67541]150            if isinstance(pi_string, str):
[2e3b055]151                dic = self.break_processing_instructions(pi_string, dic)
152            elif isinstance(pi_string, list):
153                for item in pi_string:
154                    dic = self.break_processing_instructions(item, dic)
[ac5b69d]155            proc_instr = proc_instr.getprevious()
[2e3b055]156        if 'xml' in dic:
[ac5b69d]157            self.set_encoding(dic['xml'])
[2e3b055]158            del dic['xml']
[ac5b69d]159        self.processing_instructions = dic
[ea67541]160       
[ac5b69d]161    def set_encoding(self, attr_str):
[2e3b055]162        """
163        Find the encoding in the xml declaration and save it as a string
164       
165        :param attr_str: All attributes as a string
166            e.g. "foo1="bar1" foo2="bar2" foo3="bar3" ... foo_n="bar_n""
167        """
168        attr_str = attr_str.replace(" = ", "=")
169        attr_list = attr_str.split( )
170        for item in attr_list:
171            name_value = item.split("\"=")
172            name = name_value[0].lower()
173            value = name_value[1]
174            if name == "encoding":
175                self.encoding = value
176                return
177        self.encoding = None
178       
[ac5b69d]179    def _create_unique_key(self, dictionary, name, numb = 0):
[ea67541]180        """
181        Create a unique key value for any dictionary to prevent overwriting
182        Recurses until a unique key value is found.
183       
184        :param dictionary: A dictionary with any number of entries
185        :param name: The index of the item to be added to dictionary
[ac5b69d]186        :param numb: The number to be appended to the name, starts at 0
[ea67541]187        """
188        if dictionary.get(name) is not None:
[ac5b69d]189            numb += 1
[ea67541]190            name = name.split("_")[0]
[ac5b69d]191            name += "_{0}".format(numb)
192            name = self._create_unique_key(dictionary, name, numb)
[ea67541]193        return name
194   
[f44b076]195    def create_tree(self, root):
196        """
[2e3b055]197        Create an element tree for processing from an etree element
[f44b076]198       
[2e3b055]199        :param root: etree Element(s)
[f44b076]200        """
201        return etree.ElementTree(root)
202   
[ac5b69d]203    def create_element_from_string(self, xml_string):
[2e3b055]204        """
205        Create an element from an XML string
206       
[ac5b69d]207        :param xml_string: A string of xml
[2e3b055]208        """
[ac5b69d]209        return etree.fromstring(xml_string)
[2e3b055]210   
[ac5b69d]211    def create_element(self, name, attrib=None, nsmap=None):
[f44b076]212        """
213        Create an XML element for writing to file
214       
215        :param name: The name of the element to be created
216        """
[ac5b69d]217        if attrib == None:
218            attrib = {}
[2e3b055]219        return etree.Element(name, attrib, nsmap)
[f44b076]220   
221    def write_text(self, elem, text):
222        """
223        Write text to an etree Element
224       
225        :param elem: etree.Element object
226        :param text: text to write to the element
227        """
228        elem.text = text
229        return elem
230   
231    def write_attribute(self, elem, attr_name, attr_value):
232        """
233        Write attributes to an Element
234       
235        :param elem: etree.Element object
236        :param attr_name: attribute name to write
237        :param attr_value: attribute value to set
238        """
239        attr = elem.attrib
240        attr[attr_name] = attr_value
[2e3b055]241       
242    def return_processing_instructions(self):
243        """
244        Get all processing instructions saved when loading the document
245       
246        :param tree: etree.ElementTree object to write PIs to
247        """
248        pi_list = []
[ac5b69d]249        if self.processing_instructions is not None:
250            for key in self.processing_instructions:
251                value = self.processing_instructions.get(key)
252                pi_item = etree.ProcessingInstruction(key, value)
253                pi_list.append(pi_item)
[2e3b055]254        return pi_list
255   
256    def append(self, element, tree):
257        """
258        Append an etree Element to an ElementTree.
259       
260        :param element: etree Element to append
261        :param tree: ElementTree object to append to
262        """
263        tree = tree.append(element)
264        return tree
265   
[ac5b69d]266    def ebuilder(self, parent, elementname, text=None, attrib=None):
267        """
268        Use lxml E builder class with arbitrary inputs.
269       
270        :param parnet: The parent element to append a child to
271        :param elementname: The name of the child in string form
272        :param text: The element text
273        :param attrib: A dictionary of attribute names to attribute values
274        """
[2e3b055]275        text = str(text)
[ac5b69d]276        if attrib == None:
277            attrib = {}
[2e3b055]278        elem = E(elementname, attrib, text)
279        parent = parent.append(elem)
280        return parent
[17a25d4]281       
Note: See TracBrowser for help on using the repository browser.