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

Last change on this file since 829 was 115, checked in by mmc, 19 years ago

Updated all copyright notices.

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