source: trunk/python/Rappture/library.py @ 96

Last change on this file since 96 was 43, checked in by mmc, 19 years ago
  • Added a new Rappture.result() function to the Python library. This makes it easy to finalize results. See examples/graph for example use.
  • Added examples/graph to illustrate a simple tool with a string and two numbers for input.
  • Fixed the XY graph to show crosshairs and pop-up info when you mouse over particular points.
  • Fixed Rappture::exec so that it doesn't add stray newlines when a program has lots (more than 8k) of output.
  • Fixed the analyzer to recognize the <tool><analyzer> tag. When set to "last", this automatically clears the last result. Handy for programs like SPICE, where you don't compare much, but keep running one new simulation after another.
  • Fixed <string> entries to enable the Simulate button after each editing keystroke.
File size: 15.4 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: library - provides access to the XML library
3#
4#  These routines make it easy to load the XML description for a
5#  series of tool parameters and browse through the results.
6# ======================================================================
7#  AUTHOR:  Michael McLennan, Purdue University
8#  Copyright (c) 2005  Purdue Research Foundation, West Lafayette, IN
9# ======================================================================
10from xml.dom import minidom
11import re, string, time
12
13class library:
14    """
15    A Rappture library object represents an entire XML document or
16    part of an XML tree.  It simplifies the interface to the XML
17    package, giving the caller an easier mechanism for finding,
18    querying, and setting values in the tree.
19    """
20
21    re_xml = re.compile('<\?[Xx][Mm][Ll]')
22    re_dots = re.compile('(\([^\)]*)\.([^\)]*\))')
23    re_pathElem = re.compile('^(([a-zA-Z_]+#?)([0-9]*))?(\(([^\)]+)\))?$')
24    re_pathCreateElem = re.compile('^([^\(]+)\(([^\)]+)\)$')
25
26    # ------------------------------------------------------------------
27    def __init__(self, elem):
28        if isinstance(elem, minidom.Node):
29            # if the input is a node, then wrap up this node
30            self.doc = elem.ownerDocument
31            self.node = elem
32        elif self.re_xml.match(elem):
33            # if the input is a string, then parse the string
34            self.doc = minidom.parseString(elem)
35            self.node = self.doc.documentElement
36        else:
37            # if the input is a file, then parse the file
38            self.doc = minidom.parse(elem)
39            self.node = self.doc.documentElement
40
41    # ------------------------------------------------------------------
42    def element(self, path="", as="object"):
43        """
44        Clients use this to query a particular element within the
45        entire data structure.  The path is a string of the form
46        "structure.box(source).corner".  This example represents
47        the tag <corner> within a tag <box id="source"> within a
48        a tag <structure>, which must be found at the top level
49        within this document.
50
51        By default, this method returns an object representing the
52        DOM node referenced by the path.  This is changed by setting
53        the "as" argument to "id" (for name of the tail element),
54        to "type" (for the type of the tail element), to "component"
55        (for the component name "type(id)"), or to "object"
56        for the default (an object representing the tail element).
57        """
58
59        node = self._find(path)
60        if not node:
61            return None
62
63        if as == 'object':
64            return library(node)
65        elif as == 'component':
66            return self._node2comp(node)
67        elif as == 'id':
68            return self._node2name(node)
69        elif as == 'type':
70            return node.tagName
71
72        raise ValueError, "bad as value '%s': should be component, id, object, type" % as
73
74    # ------------------------------------------------------------------
75    def children(self, path="", as="object", type=None):
76        """
77        Clients use this to query the children of a particular element
78        within the entire data structure.  This is just like the
79        element() method, but it returns the children of the element
80        instead of the element itself.  If the optional type argument
81        is specified, then the return list is restricted to children
82        of the specified type.
83
84        By default, this method returns a list of objects representing
85        the children.  This is changed by setting the "as" argument
86        to "id" (for tail names of all children), to "type" (for the
87        types of all children), to "component" (for the path component
88        names of all children), or to "object" for the default (a list
89        child objects).
90        """
91
92        node = self._find(path)
93        if not node:
94            return None
95
96        nlist = [n for n in node.childNodes if not n.nodeName.startswith('#')]
97
98        if type:
99            nlist = [n for n in nlist if n.nodeName == type]
100
101        if as == 'object':
102            return [library(n) for n in nlist]
103        elif as == 'component':
104            return [self._node2comp(n) for n in nlist]
105        elif as == 'id':
106            return [self._node2name(n) for n in nlist]
107        elif as == 'type':
108            return [n.tagName for n in nlist]
109
110        raise ValueError, "bad as value '%s': should be component, id, object, type" % as
111
112    # ------------------------------------------------------------------
113    def get(self, path=""):
114        """
115        Clients use this to query the value of a node.  If the path
116        is not specified, it returns the value associated with the
117        root node.  Otherwise, it returns the value for the element
118        specified by the path.
119        """
120
121        node = self._find(path)
122        if not node:
123            return None
124
125        clist = node.childNodes
126        rval = None
127        for node in [n for n in clist if isinstance(n,minidom.Text)]:
128            if rval == None:
129                rval = node.nodeValue
130            else:
131                rval += node.nodeValue
132        if rval:
133            rval = rval.strip()
134        return rval
135
136    # ------------------------------------------------------------------
137    def put(self, path="", value="", id=None, append=0):
138        """
139        Clients use this to set the value of a node.  If the path
140        is not specified, it sets the value for the root node.
141        Otherwise, it sets the value for the element specified
142        by the path.  If the value is a string, then it is treated
143        as the text within the tag at the tail of the path.  If it
144        is a DOM node or a library, then it is inserted into the
145        tree at the specified path.
146
147        If the optional id is specified, then it sets the identifier
148        for the tag at the tail of the path.  If the optional append
149        flag is specified, then the value is appended to the current
150        value.  Otherwise, the value replaces the current value.
151        """
152
153        node = self._find(path,create=1)
154
155        if append:
156            nlist = [n for n in node.childNodes
157                       if not n.nodeType == n.TEXT_NODE]
158        else:
159            nlist = node.childNodes
160
161        for n in nlist:
162            node.removeChild(n)
163
164        if value:
165            # if there's a value, then add it to the node
166            if isinstance(value, library):
167                node.appendChild(value.node.cloneNode(1))
168            elif isinstance(value, minidom.Node):
169                node.appendChild(value)
170            elif value and value != '':
171                n = self.doc.createTextNode(value)
172                node.appendChild(n)
173
174        # if there's an id, then set it on the node
175        if id:
176            node.setAttribute("id",id)
177
178    # ------------------------------------------------------------------
179    def remove(self, path=""):
180        """
181        Clients use this to remove the specified node.  Removes the
182        node from the tree and returns it as an element, so you can
183        put it somewhere else if you like.
184        """
185
186        node = self._find(path)
187        if not node:
188            return None
189
190        rval = library('<?xml version="1.0"?>' + node.toxml())
191        node.parentNode.removeChild(node)
192
193        return rval
194
195    # ------------------------------------------------------------------
196    def xml(self):
197        """
198        Clients use this to query the XML representation for this
199        object.
200        """
201        return self.doc.toxml()
202
203    # ------------------------------------------------------------------
204    def _node2name(self, node):
205        """
206        Used internally to create a name for the specified node.
207        If the node doesn't have a specific name ("id" attribute)
208        then a name of the form "type123" is constructed.
209        """
210
211        try:
212            # node has a name? then return it
213            name = node.getAttribute("id")
214        except AttributeError:
215            name = ''
216
217        if name == '':
218            # try to build a name like "type123"
219            type = node.tagName
220            siblings = node.parentNode.getElementsByTagName(type)
221            index = siblings.index(node)
222            if index == 0:
223                name = type
224            else:
225                name = "%s%d" % (type,index)
226
227        return name
228
229    # ------------------------------------------------------------------
230    def _node2comp(self, node):
231        """
232        Used internally to create a path component name for the
233        specified node.  A path component name has the form "type(id)"
234        or just "type##" if the node doesn't have a name.  This name
235        can be used in a path to uniquely address the component.
236        """
237
238        try:
239            # node has a name? then return it
240            name = node.getAttribute("id")
241            if name != '':
242                name = "%s(%s)" % (node.tagName, name)
243        except AttributeError:
244            name = ''
245
246        if name == '':
247            # try to build a name like "type123"
248            type = node.tagName
249            siblings = node.parentNode.getElementsByTagName(type)
250            index = siblings.index(node)
251            if index == 0:
252                name = type
253            else:
254                name = "%s%d" % (type,index)
255
256        return name
257
258    # ------------------------------------------------------------------
259    def _find(self, path, create=0):
260        """
261        Used internally to find a particular element within the
262        root node according to the path, which is a string of
263        the form "typeNN(id).typeNN(id). ...", where each
264        "type" is a tag <type>; if the optional NN is specified,
265        it indicates an index for the <type> tag within its parent;
266        if the optional (id) part is included, it indicates a
267        tag of the form <type id="id">.
268
269        By default, it looks for an element along the path and
270        returns None if not found.  If the create flag is set,
271        it creates various elements along the path as it goes.
272        This is useful for "put" operations.
273
274        If you include "#" instead of a specific number, a node
275        will be created automatically with a new number.  For example,
276        the path "foo.bar#" called the first time will create "foo.bar",
277        the second time "foo.bar1", the third time "foo.bar2" and
278        so forth.
279
280        Returns an object representing the element indicated by
281        the path, or None if the path is not found.
282        """
283
284        if path == "":
285            return self.node
286
287        path = self._path2list(path)
288
289        #
290        # Follow the given path and look for all of the parts.
291        #
292        node = lastnode = self.node
293        for part in path:
294            #
295            # Pick apart "type123(id)" for this part.
296            #
297            match = self.re_pathElem.search(part)
298            if not match:
299                raise ValueError, "bad path component '%s': should have the form 'type123(id)'" % part
300
301            (dummy, type, index, dummy, name) = match.groups()
302
303            if not name:
304                #
305                # If the name is like "type2", then look for elements with
306                # the type name and return the one with the given index.
307                # If the name is like "type", then assume the index is 0.
308                #
309                if not index or index == "":
310                    index = 0
311                else:
312                    index = int(index)
313
314                nlist = node.getElementsByTagName(type)
315                if index < len(nlist):
316                    node = nlist[index]
317                else:
318                    node = None
319            else:
320                #
321                # If the name is like "type(id)", then look for elements
322                # that match the type and see if one has the requested name.
323                # if the name is like "(id)", then look for any elements
324                # with the requested name.
325                #
326                if type:
327                    nlist = node.getElementsByTagName(type)
328                else:
329                    nlist = node.childNodes
330
331                node = None
332                for n in nlist:
333                    try:
334                        tag = n.getAttribute("id")
335                    except AttributeError:
336                        tag = ""
337
338                    if tag == name:
339                        node = n
340                        break
341
342            if not node:
343                if not create:
344                    return None
345
346                #
347                # If the "create" flag is set, then create a node
348                # with the specified "type(id)" and continue on.
349                # If the type is "type#", then create a node with
350                # an automatic number.
351                #
352                match = self.re_pathCreateElem.search(part)
353                if match:
354                    (type, name) = match.groups()
355                else:
356                    type = part
357                    name = ""
358
359                if type.endswith('#'):
360                    type = type.rstrip('#')
361                    node = self.doc.createElement(type)
362
363                    # find the last node of same type and append there
364                    pos = None
365                    for n in lastnode.childNodes:
366                        if n.nodeName == type:
367                            pos = n
368
369                    if pos:
370                        pos = pos.nextSibling
371
372                    if pos:
373                        lastnode.insertBefore(node,pos)
374                    else:
375                        lastnode.appendChild(node)
376                else:
377                    node = self.doc.createElement(type)
378                    lastnode.appendChild(node)
379
380                if name:
381                    node.setAttribute("id",name)
382
383            lastnode = node
384
385        # last node is the desired element
386        return node
387
388    # ------------------------------------------------------------------
389    def _path2list(self, path):
390        """
391        Used internally to convert a path name of the form
392        "foo.bar(x.y).baz" to a list of the form ['foo',
393        'bar(x.y)','baz'].  Note that it's careful to preserve
394        dots within ()'s but splits on those outside ()'s.
395        """
396
397        #
398        # Normally, we just split on .'s within the path.  But there
399        # might be some .'s embedded within ()'s in the path.  Change
400        # any embedded .'s to an out-of-band character, then split on
401        # the .'s, and change the embedded .'s back.
402        #
403        while self.re_dots.search(path):
404            path = self.re_dots.sub('\\1\007\\2',path)
405
406        path = re.split('\.', path)
407        return [re.sub('\007','.',elem) for elem in path]
408
409if __name__ == "__main__":
410    lib = library("""<?xml version="1.0"?>
411<device>
412<label>Heterojunction</label>
413
414<recipe>
415  <slab>
416    <thickness>0.1um</thickness>
417    <material>GaAs</material>
418  </slab>
419  <slab>
420    <thickness>0.1um</thickness>
421    <material>Al(0.3)Ga(0.7)As</material>
422  </slab>
423</recipe>
424<field id="donors">
425  <label>Doping</label>
426  <units>/cm3</units>
427  <scale>log</scale>
428  <color>green</color>
429  <component id="Nd+">
430    <constant>1.0e19/cm3</constant>
431    <domain>slab0</domain>
432  </component>
433</field>
434</device>
435""")
436    print lib.get('label')
437    print lib.get('recipe.slab1.thickness')
438    print lib.get('field(donors).component(Nd+).domain')
439    print lib.get('field(donors).component.domain')
Note: See TracBrowser for help on using the repository browser.