source: wxmtxy/trunk/src/moxy/moxy_xml.py @ 631

Last change on this file since 631 was 631, checked in by jemian, 12 years ago

begin refactoring into release structure and rebranding with new product name: moxy

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Date Revision Author HeadURL Id
File size: 22.3 KB
Line 
1#!/usr/bin/env python
2
3#*************************************************************************
4# Copyright (c) 2009-2010 The University of Chicago, as Operator of Argonne
5#     National Laboratory.
6# Copyright (c) 2009-2010 The Regents of the University of California, as
7#     Operator of Los Alamos National Laboratory.
8# This file is distributed subject to a Software License Agreement found
9# in the file LICENSE that is included with this distribution.
10#*************************************************************************
11
12'''@note: support the XML settings file for the wxmtxy application
13
14This Python file provides routines to read and write XML settings
15files for the wxmtxy application.  An example of the XML file is
16shown below.  The routines manage the settings internally with a
17Python dictionary.  Interface routines are used to read and write
18the various components of the file.  *HOWEVER*, the EPICS configuration
19is communicated in a Python dictionary.  An example of the Python
20dictionary with the EPICS configuration is shown below.
21
22@version:
23########### SVN repository information ###################
24# $Date: 2011-09-09 17:12:01 +0000 (Fri, 09 Sep 2011) $
25# $Author: jemian $
26# $Revision: 631 $
27# $URL: wxmtxy/trunk/src/moxy/moxy_xml.py $
28# $Id: moxy_xml.py 631 2011-09-09 17:12:01Z jemian $
29########### SVN repository information ###################
30
31@note: for help with xml.dom, see http://docs.python.org/library/xml.dom.html
32
33@note: Here is an example XML file:
34<?xml version="1.0" ?>
35<wxmtxy date="2009-04-09" time="10:27:00" version="1.0">
36  <XYpair name="example" selected="True">
37    <EPICS_configuration>
38      <axis name="x">
39        <flag isMotorRec="False" />
40        <field name="VAL" pv="32idbLAX:float1" />
41        <field name="RBV" pv="32idbLAX:float2" />
42        <field name="DESC" pv="32idbLAX:string1" />
43        <field name="EGU" pv="32idbLAX:string2" />
44        <field name="DMOV" pv="32idbLAX:bit1" />
45        <field name="STOP" pv="32idbLAX:bit2" />
46      </axis>
47      <axis name="y">
48        <flag isMotorRec="True" />
49        <field name="VAL" pv="32idbLAX:m58:c1:m1" /><!-- USAXS a1y -->
50      </axis>
51    </EPICS_configuration>
52    <tab name="page 1">
53      <row name="page 1, row 0" x="1.0" y="-1.0" selected="True"/>
54      <row name="page 1, row 1" x="1.1" y="-1.1"/>
55      <row/>
56    </tab>
57    <tab name="page 2" selected="True">
58      <row name="page 2, row 0" x="2.0" y="-2.0"/>
59      <row name="page 2, row 1" x="2.1" y="-2.1"/>
60      <row name="page 2, row 2" x="2.2" y="-2.2" selected="True"/>
61      <row name="page 2, row 3" x="2.3" y="-2.3"/>
62      <row name="page 2, row 4" x="2.4" y="-2.4"/>
63      <row name="page 2, row 5" x="2.5" y="-2.5"/>
64      <row name="page 2, row 6" x="2.6" y="-2.6"/>
65    </tab>
66    <tab name="empty page"/>
67    <tab name="page 3">
68  </XYpair>
69</wxmtxy>
70
71@note: Here is an example Python dictionary of the EPICS configuration above:
72    example_dictionary = {
73        'x': {
74            'isMotorRec': False,
75            'VAL': '32idbLAX:float1',
76            'RBV': '32idbLAX:float2',
77            'DESC': '32idbLAX:string1',
78            'EGU': '32idbLAX:string2',
79            'DMOV': '32idbLAX:bit1',
80            'STOP': '32idbLAX:bit2'
81        },
82        'y': {
83            'isMotorRec': True,
84            'VAL': '32idbLAX:m58:c1:m1.VAL'
85        }
86    }
87'''
88
89
90from xml.dom import minidom
91import datetime
92import copy
93import wxmtxy_axis
94
95
96class Settings:
97    '''handle the XML settings file'''
98
99    def __init__(self, settingsFile=None):
100        '''prepare the settings file
101            @param settingsFile: [string] name of XML file with settings'''
102        self.rootElement = 'wxmtxy'
103        self.Clear()
104        self.SetSettingsFile(settingsFile)
105
106    def GetDb(self):
107        '''@return: database'''
108        return self.db
109
110    def GetSettingsFile(self):
111        '''@return: name of XML settings file'''
112        return self.settingsFile
113
114    def SetSettingsFile(self, thefile):
115        '''set the name of XML settings file
116            @param thefile: [string] name of XML file with settings'''
117        self.settingsFile = thefile
118
119    def Clear(self):
120        '''reset the internal data representation (db) to empty'''
121        self.db = {}
122
123    def NewPair(self, title=''):
124        ''' create space in the database (db) for a new pair
125            and sets defaults for fields
126
127            @param title: [string] the title of the XY_pair set (default="")
128            @return: the index number'''
129        if self.CountPairs() == -1:
130            self.db[u"pairs"] = []
131        pairdb = {}
132        pairdb[u"@name"] = title
133        pairdb[u"@selected"] = False
134        self.db[u'pairs'].append(pairdb)
135        return len(self.db[u'pairs'])-1
136
137    def GetPairTitle(self, pairnum):
138        '''return the name of the XY_pair
139            @param pairnum: [int] index number of the XY_pair'''
140        return self.db[u"pairs"][pairnum][u"@name"]
141
142    def SetPairTitle(self, pairnum, title):
143        '''set the name of the XY_pair
144            @param pairnum: [int] index number of the XY_pair'
145            @param title: [string] name of the XY_pair'''
146        self.db[u"pairs"][pairnum][u"@name"] = title
147
148    def SelectPair(self, pairnum):
149        '''set the "selected" attribute of the pair
150            @param pairnum: [int] index number of the XY_pair'''
151        for pair in self.db[u"pairs"]:   # first, deselect all pairs
152            pair[u"@selected"] = False
153        self.db[u"pairs"][pairnum][u"@selected"] = True
154
155    def GetSelectedPair(self):
156        '''@return: index number of the "selected" pair (-1 if none selected)'''
157        selected = -1
158        try:
159            pairs = self.db[u"pairs"]
160            for pairnum in range(len(pairs)):
161                if pairs[pairnum][u"@selected"]:
162                    selected = pairnum
163                    break
164        except:
165            pass
166        return selected
167
168    def CountPairs(self):
169        '''@return: number of pairs'''
170        try:
171            return len(self.db[u"pairs"])
172        except:
173            return -1
174
175    def NewEpicsConfig(self, pairnum):
176        '''Create internal space for a new EPICS configuration
177            @param pairnum: [int] index number of the XY_pair'''
178        pairdb = self.db[u"pairs"][pairnum]
179        pairdb[u"epics"] = {}
180        epicsdb = pairdb[u"epics"]
181        for axis in ['x', 'y']:
182            epicsdb[axis] = {}
183            axisdb = epicsdb[axis]
184            for field in wxmtxy_axis.field_list:
185                axisdb[field] = ""
186            axisdb[u"isMotorRec"] = False
187
188    def GetEpicsConfig(self, pairnum):
189        '''Get a deep copy Python dictionary of the current EPICS PV config.
190            @param pairnum: [int] index number of the XY_pair
191            @return: the current EPICS configuration'''
192        return copy.deepcopy(self.db[u"pairs"][pairnum][u"epics"])
193
194    def SetEpicsConfig(self, pairnum, config):
195        '''set the current EPICS configuration
196            @param pairnum: [int] index number of the XY_pair
197            @param config: Python dictionary of EPICS PV configuration'''
198        pairdb = self.db[u"pairs"][pairnum]
199        deep = copy.deepcopy(config)
200        pairdb[u"epics"] = deep
201
202    def SetEpicsField(self, pairnum, axis, field, value):
203        '''Define the EPICS config for a specific field
204            @param pairnum: [int] index number of the XY_pair'
205            @param axis: [string] "x" or "y"'
206            @param field: [string] member of wxmtxy_axis.field_list'
207            @param value: [string] value of this field'''
208        try:
209            axisdb = self.db[u"pairs"][pairnum][u"epics"][axis]
210            axisdb[field] = value
211        except:
212            print "Could not assign EPICS field", pairnum, axis, field, value
213
214
215    def NewTab(self, pairnum, title=''):
216        ''' create space in the database (db) pair for a new tab
217            and sets defaults for fields
218
219            @param pairnum: [int] index number of the XY_pair
220            @param title: the title of the pair set (default="")
221            @return the index number'''
222        pairdb = self.db[u"pairs"][pairnum]
223        if not pairdb.has_key(u"tabs"):
224            pairdb[u"tabs"] = []
225        tabdb = {}
226        tabdb[u"@name"] = title
227        tabdb[u"@selected"] = False
228        pairdb[u"tabs"].append(tabdb)
229        return len(pairdb[u"tabs"])-1
230
231    def GetTabTitle(self, pairnum, tabnum):
232        '''return the name of the tab
233            @param pairnum: [int] index number of the XY_pair
234            @param tabnum: [int] index number of the Tab object'''
235        return self.db[u"pairs"][pairnum][u"tabs"][tabnum][u"@name"]
236
237    def SetTabTitle(self, pairnum, tabnum, title):
238        '''set the name attribute of the tab
239            @param pairnum: [int] index number of the XY_pair
240            @param tabnum: [int] index number of the Tab object
241            @param title: [string] title the Tab object'''
242        self.db[u"pairs"][pairnum][u"tabs"][tabnum]["@name"] = title
243
244    def SelectTab(self, pairnum, tabnum):
245        '''set the selected attribute of the pair
246            @param pairnum: [int] index number of the XY_pair
247            @param tabnum: [int] index number of the Tab object'''
248        pairdb = self.db[u"pairs"][pairnum]
249        for tab in pairdb[u"tabs"]:   # first, deselect all tabs
250            tab[u"@selected"] = False
251        pairdb[u"tabs"][tabnum][u"@selected"] = True
252
253    def GetSelectedTab(self, pairnum):
254        '''return the index number of the selected tab
255            @param pairnum: [int] index number of the XY_pair'''
256        selected = -1
257        try:
258            tabs = self.db[u"pairs"][pairnum][u"tabs"]
259            for tabnum in range(len(tabs)):
260                if tabs[tabnum][u"@selected"]:
261                    selected = tabnum
262                    break
263        except:
264            pass
265        return selected
266
267    def CountTabs(self, pairnum):
268        '''return the number of tabs
269            @param pairnum: [int] index number of the XY_pair'''
270        try:
271            return len(self.db[u"pairs"][pairnum][u"tabs"])
272        except:
273            return -1
274
275    def NewRow(self, pairnum, tabnum, title=''):
276        ''' create space in the database (db) pair for a new tab
277            and sets defaults for fields
278
279            @param pairnum: [int] index number of the XY_pair
280            @param tabnum: [int] index number of the Tab object
281            @param title: the title of the Tab object (default="")
282            @return the index number'''
283        tabdb = self.db[u"pairs"][pairnum][u"tabs"][tabnum]
284        if not tabdb.has_key(u"rows"):
285            tabdb[u"rows"] = []
286        rowdb = {}
287        rowdb[u"@name"] = title
288        rowdb[u"@selected"] = False
289        rowdb[u"@x"] = ""
290        rowdb[u"@y"] = ""
291        tabdb[u"rows"].append(rowdb)
292        return len(tabdb[u"rows"])-1
293
294    def GetRowTitle(self, pairnum, tabnum, rownum):
295        '''return the name of the row
296            @param pairnum: [int] index number of the XY_pair
297            @param tabnum: [int] index number of the Tab object
298            @param rownum: [int] index number of the Row object'''
299        tabdb = self.db[u"pairs"][pairnum][u"tabs"][tabnum]
300        return tabdb[u"rows"][rownum][u"@name"]
301
302    def SetRowTitle(self, pairnum, tabnum, rownum, title):
303        '''set the name attribute of the row
304            @param pairnum: [int] index number of the XY_pair
305            @param tabnum: [int] index number of the Tab object
306            @param rownum: [int] index number of the Row object
307            @param title: [string] title the Tab object'''
308        tabdb = self.db[u"pairs"][pairnum][u"tabs"][tabnum]
309        tabdb[u"rows"][rownum][u"@name"] = title
310
311    def GetRowXY(self, pairnum, tabnum, rownum):
312        '''return the name of the row
313            @param pairnum: [int] index number of the XY_pair
314            @param tabnum: [int] index number of the Tab object
315            @param rownum: [int] index number of the Row object'''
316        tabdb = self.db[u"pairs"][pairnum][u"tabs"][tabnum]
317        x = tabdb[u"rows"][rownum][u"@x"]
318        y = tabdb[u"rows"][rownum][u"@y"]
319        return x, y
320
321    def SetRowXY(self, pairnum, tabnum, rownum, x, y):
322        '''set the name attribute of the row
323            @param pairnum: [int] index number of the XY_pair
324            @param tabnum: [int] index number of the Tab object
325            @param x: [float] X axis position
326            @param y: [float] Y axis position'''
327        tabdb = self.db[u"pairs"][pairnum][u"tabs"][tabnum]
328        tabdb[u"rows"][rownum][u"@x"] = x
329        tabdb[u"rows"][rownum][u"@y"] = y
330
331    def CountRows(self, pairnum, tabnum):
332        '''return the number of rows
333            @param pairnum: [int] index number of the XY_pair
334            @param tabnum: [int] index number of the Tab object'''
335        try:
336            return len(self.db[u"pairs"][pairnum][u"tabs"][tabnum][u"rows"])
337        except:
338            return -1
339
340    def GetSelectedRow(self, pairnum, tabnum):
341        '''return the index number of the selected row
342            @param pairnum: [int] index number of the XY_pair
343            @param tabnum: [int] index number of the Tab object'''
344        selected = -1
345        try:
346            rows = self.db[u"pairs"][pairnum][u"tabs"][tabnum][u"rows"]
347            for rownum in range(len(rows)):
348                if rows[rownum][u"@selected"]:
349                    selected = rownum
350                    break
351        except:
352            pass
353        return selected
354
355    def SelectRow(self, pairnum, tabnum, rownum):
356        '''set the selected attribute of the pair
357            @param pairnum: [int] index number of the XY_pair
358            @param tabnum: [int] index number of the Tab object
359            @param rownum: [int] index number of the Row object'''
360        tabdb = self.db[u"pairs"][pairnum][u"tabs"][tabnum]
361        for row in tabdb[u"rows"]:   # first, deselect all rows
362            row[u"@selected"] = False
363        tabdb[u"rows"][rownum][u"@selected"] = True
364
365    def ReadXmlFile(self):
366        '''read the settings from a file into an internal dictionary (self.db)
367
368            @note: this method uses xml.dom.minidom (built into all Pythons)
369            @see: http://docs.python.org/library/xml.dom.minidom.html
370        '''
371        try:
372            doc = minidom.parse(self.settingsFile) # parse an XML file by name
373            assert doc.documentElement.tagName == self.rootElement
374        except IOError:
375            return 'Could not read the XML file: ' + self.settingsFile
376        except AssertionError:
377            return 'XML root element is not ' + self.rootElement
378        #... read all attributes from the root element
379        docElem = doc.documentElement
380        self.Clear()
381        version = self._get_attribute(docElem, "version", "not-specified")
382        try:
383            # only handle v1.0 resource configuration files
384            assert version == u'1.0'
385        except AssertionError:
386            doc.unlink()
387            return 'Cannot handle file version:', version
388        # file verified now
389        for pairNode in docElem.getElementsByTagName("XYpair"):
390            title = self._get_attribute(pairNode, "name", "")
391            selected = self._get_attribute(pairNode, "selected", "")
392            pairnum = self.NewPair(title)
393            if selected.lower() == "true":
394                self.SelectPair(pairnum)
395            self.NewEpicsConfig(pairnum)
396            for EpicsNode in pairNode.getElementsByTagName("EPICS_configuration"):
397                for axisNode in EpicsNode.getElementsByTagName("axis"):
398                    axis = self._get_attribute(axisNode, "name", "")
399                    #isMotorRec
400                    for flagNode in axisNode.getElementsByTagName("flag"):
401                        text = self._get_attribute(flagNode, "isMotorRec", "False")
402                        self.SetEpicsField(pairnum, axis, "isMotorRec", (text == "True"))
403                    for fieldNode in axisNode.getElementsByTagName("field"):
404                        name = self._get_attribute(fieldNode, "name", "")
405                        pv = self._get_attribute(fieldNode, "pv", "")
406                        if (len(name)>0) and (len(pv)>0):
407                            #print pairnum, axis, name, pv
408                            self.SetEpicsField(pairnum, axis, name, pv)
409            for tabNode in pairNode.getElementsByTagName("tab"):
410                title = self._get_attribute(tabNode, "name", "")
411                selected = self._get_attribute(tabNode, "selected", "")
412                tabnum = self.NewTab(pairnum, title)
413                if selected.lower() == "true":
414                    self.SelectTab(pairnum, tabnum)
415                # EPICS settings here
416                for rowNode in tabNode.getElementsByTagName("row"):
417                    title = self._get_attribute(rowNode, "name", "")
418                    selected = self._get_attribute(rowNode, "selected", "")
419                    x = self._get_attribute(rowNode, "x", "")
420                    y = self._get_attribute(rowNode, "y", "")
421                    rownum = self.NewRow(pairnum, tabnum, title)
422                    self.SetRowXY(pairnum, tabnum, rownum, x, y)
423        doc.unlink()  # ensures XML document is disposed cleanly
424        return None
425
426    def _get_attribute(self, node, key, default):
427        '''get a specific attribute or return the default
428            @param node: XML Node object
429            @param key: [string] name of attribute to find
430            @param default: [string] default value to return'''
431        value = default
432        if node.attributes.has_key(key):
433            value = node.attributes[key].value
434        return value
435
436    def SaveXmlFile(self):
437        '''save the internal dictionary (self.db) to an XML file
438           @note: What about using/saving a default stylesheet?
439           @see: http://www.boddie.org.uk/python/XML_intro.html
440        '''
441        out = open(self.settingsFile, 'w')
442        out.write(repr(self))
443        out.close()
444        #
445        # What about a default stylesheet?
446        #
447       
448        return 'Saved settings to ' + self.settingsFile
449
450    def _SetAttr(self, node, attribute, value):
451        '''add attributes that are not empty (but do not strip the whitespace)
452            @param node: XML Node object
453            @param attribute: [string] name of attribute
454            @param value: [string] value of attribute'''
455        if len(value) > 0:
456            node.setAttribute(attribute, value)
457
458    def _makeTextNode(self, doc, tag, value):
459        '''create a text node for the XML file
460            @param doc: [xml.dom.minidom documentElement object]
461            @param tag: [string] element name
462            @param value: [string] element text'''
463        node = doc.createElement(tag)
464        text = doc.createTextNode(value)
465        node.appendChild(text)
466        return node
467
468    def __repr__(self):
469        '''default representation of this structure is XML
470            @return: XML representation of internal database (db)
471            @note: What about a default stylesheet?
472        '''
473        t = datetime.datetime.now()
474        yyyymmdd = t.strftime("%Y-%m-%d")
475        hhmmss = t.strftime("%H:%M:%S")
476
477        # Create the minidom document
478        doc = minidom.Document()
479
480        # Create the root element
481        root = doc.createElement(self.rootElement)
482        self._SetAttr(root, "version", "1.0")
483        self._SetAttr(root, "date", yyyymmdd)
484        self._SetAttr(root, "time", hhmmss)
485        doc.appendChild(root)
486        selectedpairnum = self.GetSelectedPair()
487        for pairnum in range(self.CountPairs()):
488            pairnode = doc.createElement("XYpair")
489            self._SetAttr(pairnode, "name", 
490                   self.GetPairTitle(pairnum))
491            if selectedpairnum == pairnum:
492                self._SetAttr(pairnode, "selected", "True")
493            if self.db[u"pairs"][pairnum].has_key(u"epics"):
494                epicsnode = doc.createElement("EPICS_configuration")
495                epicsdb = self.db[u"pairs"][pairnum][u"epics"]
496                for axis in epicsdb:
497                    axisnode = doc.createElement("axis")
498                    self._SetAttr(axisnode, "name", axis)
499                    field = "isMotorRec"
500                    if field in epicsdb[axis]:
501                        node = doc.createElement("flag")
502                        self._SetAttr(node, field, str(epicsdb[axis][field]))
503                        axisnode.appendChild(node)
504                    for field in wxmtxy_axis.field_list:
505                        if field in epicsdb[axis]:
506                            if len(epicsdb[axis][field])>0:
507                                node = doc.createElement("field")
508                                self._SetAttr(node, "name", field)
509                                self._SetAttr(node, "pv", str(epicsdb[axis][field]))
510                                axisnode.appendChild(node)
511                    epicsnode.appendChild(axisnode)
512                pairnode.appendChild(epicsnode)
513            selectedtabnum = self.GetSelectedTab(pairnum)
514            for tabnum in range(self.CountTabs(pairnum)):
515                tabnode = doc.createElement("tab")
516                self._SetAttr(tabnode, "name", self.GetTabTitle(pairnum, tabnum))
517                if selectedtabnum == tabnum:
518                    self._SetAttr(tabnode, "selected", "True")
519                selectedrownum = self.GetSelectedRow(pairnum, tabnum)
520                for rownum in range(self.CountRows(pairnum, tabnum)):
521                    rownode = doc.createElement("row")
522                    label = self.GetRowTitle(pairnum, tabnum, rownum)
523                    x, y = self.GetRowXY(pairnum, tabnum, rownum)
524                    self._SetAttr(rownode, "name", label)
525                    self._SetAttr(rownode, "x", x)
526                    self._SetAttr(rownode, "y", y)
527                    if selectedrownum == rownum:
528                        self._SetAttr(rownode, "selected", "True")
529                    tabnode.appendChild(rownode)
530                pairnode.appendChild(tabnode)
531            root.appendChild(pairnode)
532        return doc.toprettyxml(indent="  ")
533
534
535if __name__ == '__main__':
536    rc = Settings("examples/test-settings.xml")
537    rc.ReadXmlFile()
538    rc.SetSettingsFile('output-test.xml')
539    rc.SaveXmlFile()
540
541    pj = Settings()
542    pairnum = pj.NewPair("my test pair")
543    tabnum = pj.NewTab(pairnum, title='my test tab')
544    tabnum = pj.NewTab(pairnum, title='another')
545    pj.SelectTab(pairnum, tabnum)
546    tabnum = pj.NewTab(pairnum, title='another')
547    tabnum = pj.NewTab(pairnum, title='another')
548    pairnum = pj.NewPair("another")
549    pj.SelectPair(pairnum)
550    tabnum = pj.NewTab(pairnum, title='another')
551    rownum = pj.NewRow(pairnum, tabnum, "beam center")
552    pj.SelectRow(pairnum, tabnum, rownum)
553    tabnum = pj.NewTab(pairnum, title='another')
554    pairnum = pj.NewPair("another")
555    print str(pj)
Note: See TracBrowser for help on using the repository browser.