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

Last change on this file since 2 was 2, checked in by mmc, 18 years ago

Fixed the library.children() method to allow filtering
based on XML node type.

File size: 12.2 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
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="", flavor="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 "flavor" argument to "name" (for name of the tail element),
54        to "type" (for the type of the tail element), to "component"
55        (for the component name "type(name)"), 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 flavor == 'object':
64            return library(node)
65        elif flavor == 'component':
66            return self._node2comp(node)
67        elif flavor == 'name':
68            return self._node2name(node)
69        elif flavor == 'type':
70            return node.tagName
71
72        raise ValueError, "bad flavor '%s': should be object, name, type" % flavor
73
74    # ------------------------------------------------------------------
75    def children(self, path="", flavor="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 "flavor" argument
86        to "name" (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 flavor == 'object':
102            return [library(n) for n in nlist]
103        elif flavor == 'component':
104            return [self._node2comp(n) for n in nlist]
105        elif flavor == 'name':
106            return [self._node2name(n) for n in nlist]
107        elif flavor == 'type':
108            return [n.tagName for n in nlist]
109
110    # ------------------------------------------------------------------
111    def get(self, path=""):
112        """
113        Clients use this to query the value of a node.  If the path
114        is not specified, it returns the value associated with the
115        root node.  Otherwise, it returns the value for the element
116        specified by the path.
117        """
118
119        if path == "":
120            node = self.node
121        else:
122            node = self._find(path)
123
124        if not node:
125            return None
126
127        clist = node.childNodes
128        if len(clist) == 1 and isinstance(clist[0],minidom.Text):
129            return clist[0].nodeValue.strip()
130
131        return None
132
133    # ------------------------------------------------------------------
134    def xml(self):
135        """
136        Clients use this to query the XML representation for this
137        object.
138        """
139        return self.doc.toxml()
140
141    # ------------------------------------------------------------------
142    def _node2name(self, node):
143        """
144        Used internally to create a name for the specified node.
145        If the node doesn't have a specific name ("id" attribute)
146        then a name of the form "type123" is constructed.
147        """
148
149        try:
150            # node has a name? then return it
151            name = node.getAttribute("id")
152        except AttributeError:
153            name = ''
154
155        if name == '':
156            # try to build a name like "type123"
157            type = node.tagName
158            siblings = node.parentNode.getElementsByTagName(type)
159            index = siblings.index(node)
160            if index == 0:
161                name = type
162            else:
163                name = "%s%d" % (type,index)
164
165        return name
166
167    # ------------------------------------------------------------------
168    def _node2comp(self, node):
169        """
170        Used internally to create a path component name for the
171        specified node.  A path component name has the form "type(name)"
172        or just "type##" if the node doesn't have a name.  This name
173        can be used in a path to uniquely address the component.
174        """
175
176        try:
177            # node has a name? then return it
178            name = node.getAttribute("id")
179            if name != '':
180                name = "%s(%s)" % (node.tagName, name)
181        except AttributeError:
182            name = ''
183
184        if name == '':
185            # try to build a name like "type123"
186            type = node.tagName
187            siblings = node.parentNode.getElementsByTagName(type)
188            index = siblings.index(node)
189            if index == 0:
190                name = type
191            else:
192                name = "%s%d" % (type,index)
193
194        return name
195
196    # ------------------------------------------------------------------
197    def _find(self, path, create=0):
198        """
199        Used internally to find a particular element within the
200        root node according to the path, which is a string of
201        the form "type##(name).type##(name). ...", where each
202        "type" is a tag <type>; if the optional ## is specified,
203        it indicates an index for the <type> tag within its parent;
204        if the optional (name) part is included, it indicates a
205        tag of the form <type id="name">.
206
207        By default, it looks for an element along the path and
208        returns None if not found.  If the create flag is set,
209        it creates various elements along the path as it goes.
210        This is useful for "put" operations.
211
212        Returns an object representing the element indicated by
213        the path, or None if the path is not found.
214        """
215
216        if path == "":
217            return self.node
218
219        path = self._path2list(path)
220
221        #
222        # Follow the given path and look for all of the parts.
223        #
224        node = lastnode = self.node
225        for part in path:
226            #
227            # Pick apart "type123(name)" for this part.
228            #
229            match = self.re_pathElem.search(part)
230            if not match:
231                raise ValueError, "bad path component '%s': should have the form 'type123(name)'" % part
232
233            (dummy, type, index, dummy, name) = match.groups()
234
235            if not name:
236                #
237                # If the name is like "type2", then look for elements with
238                # the type name and return the one with the given index.
239                # If the name is like "type", then assume the index is 0.
240                #
241                if not index or index == "":
242                    index = 0
243                else:
244                    index = int(index)
245
246                nlist = node.getElementsByTagName(type)
247                if index < len(nlist):
248                    node = nlist[index]
249                else:
250                    node = None
251            else:
252                #
253                # If the name is like "type(name)", then look for elements
254                # that match the type and see if one has the requested name.
255                # if the name is like "(name)", then look for any elements
256                # with the requested name.
257                #
258                if type:
259                    nlist = node.getElementsByTagName(type)
260                else:
261                    nlist = node.childNodes
262
263                node = None
264                for n in nlist:
265                    try:
266                        tag = n.getAttribute("id")
267                    except AttributeError:
268                        tag = ""
269
270                    if tag == name:
271                        node = n
272                        break
273
274            if not node:
275                if not create:
276                    return None
277
278                #
279                # If the "create" flag is set, then create a node
280                # with the specified "type(name)" and continue on.
281                #
282                match = self.re_pathCreateElem.search(part)
283                if match:
284                    (type, name) = match.groups()
285                else:
286                    type = part
287                    name = ""
288
289                node = self.doc.createElement(type)
290                lastnode.appendChild(node)
291
292                if name:
293                    node.setAttribute("name",name)
294
295            lastnode = node
296
297        # last node is the desired element
298        return node
299
300    # ------------------------------------------------------------------
301    def _path2list(self, path):
302        """
303        Used internally to convert a path name of the form
304        "foo.bar(x.y).baz" to a list of the form ['foo',
305        'bar(x.y)','baz'].  Note that it's careful to preserve
306        dots within ()'s but splits on those outside ()'s.
307        """
308
309        #
310        # Normally, we just split on .'s within the path.  But there
311        # might be some .'s embedded within ()'s in the path.  Change
312        # any embedded .'s to an out-of-band character, then split on
313        # the .'s, and change the embedded .'s back.
314        #
315        while self.re_dots.search(path):
316            path = self.re_dots.sub('\\1\007\\2',path)
317
318        path = re.split('\.', path)
319        return [re.sub('\007','.',elem) for elem in path]
320
321if __name__ == "__main__":
322    lib = library("""<?xml version="1.0"?>
323<device>
324<label>Heterojunction</label>
325
326<recipe>
327  <slab>
328    <thickness>0.1um</thickness>
329    <material>GaAs</material>
330  </slab>
331  <slab>
332    <thickness>0.1um</thickness>
333    <material>Al(0.3)Ga(0.7)As</material>
334  </slab>
335</recipe>
336<field id="donors">
337  <label>Doping</label>
338  <units>/cm3</units>
339  <scale>log</scale>
340  <color>green</color>
341  <component id="Nd+">
342    <constant>1.0e19/cm3</constant>
343    <domain>slab0</domain>
344  </component>
345</field>
346</device>
347""")
348    print lib.get('label')
349    print lib.get('recipe.slab1.thickness')
350    print lib.get('field(donors).component(Nd+).domain')
351    print lib.get('field(donors).component.domain')
Note: See TracBrowser for help on using the repository browser.