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

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

initial import

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