[1] | 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 |
---|
[115] | 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. |
---|
[1] | 12 | # ====================================================================== |
---|
| 13 | from xml.dom import minidom |
---|
[43] | 14 | import re, string, time |
---|
[1] | 15 | |
---|
| 16 | class 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('(\([^\)]*)\.([^\)]*\))') |
---|
[4] | 26 | re_pathElem = re.compile('^(([a-zA-Z_]+#?)([0-9]*))?(\(([^\)]+)\))?$') |
---|
[1] | 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 | # ------------------------------------------------------------------ |
---|
[11] | 45 | def element(self, path="", as="object"): |
---|
[1] | 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 |
---|
[11] | 56 | the "as" argument to "id" (for name of the tail element), |
---|
[1] | 57 | to "type" (for the type of the tail element), to "component" |
---|
[4] | 58 | (for the component name "type(id)"), or to "object" |
---|
[1] | 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 | |
---|
[11] | 66 | if as == 'object': |
---|
[1] | 67 | return library(node) |
---|
[11] | 68 | elif as == 'component': |
---|
[1] | 69 | return self._node2comp(node) |
---|
[11] | 70 | elif as == 'id': |
---|
[1] | 71 | return self._node2name(node) |
---|
[11] | 72 | elif as == 'type': |
---|
[1] | 73 | return node.tagName |
---|
| 74 | |
---|
[11] | 75 | raise ValueError, "bad as value '%s': should be component, id, object, type" % as |
---|
[1] | 76 | |
---|
| 77 | # ------------------------------------------------------------------ |
---|
[11] | 78 | def children(self, path="", as="object", type=None): |
---|
[1] | 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 |
---|
[2] | 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. |
---|
[1] | 86 | |
---|
| 87 | By default, this method returns a list of objects representing |
---|
[11] | 88 | the children. This is changed by setting the "as" argument |
---|
[4] | 89 | to "id" (for tail names of all children), to "type" (for the |
---|
[1] | 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 | |
---|
[2] | 101 | if type: |
---|
| 102 | nlist = [n for n in nlist if n.nodeName == type] |
---|
| 103 | |
---|
[11] | 104 | if as == 'object': |
---|
[1] | 105 | return [library(n) for n in nlist] |
---|
[11] | 106 | elif as == 'component': |
---|
[1] | 107 | return [self._node2comp(n) for n in nlist] |
---|
[11] | 108 | elif as == 'id': |
---|
[1] | 109 | return [self._node2name(n) for n in nlist] |
---|
[11] | 110 | elif as == 'type': |
---|
[1] | 111 | return [n.tagName for n in nlist] |
---|
| 112 | |
---|
[11] | 113 | raise ValueError, "bad as value '%s': should be component, id, object, type" % as |
---|
| 114 | |
---|
[1] | 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 | |
---|
[3] | 124 | node = self._find(path) |
---|
[1] | 125 | if not node: |
---|
| 126 | return None |
---|
| 127 | |
---|
| 128 | clist = node.childNodes |
---|
[5] | 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 |
---|
[1] | 138 | |
---|
| 139 | # ------------------------------------------------------------------ |
---|
[3] | 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): |
---|
[22] | 170 | node.appendChild(value.node.cloneNode(1)) |
---|
[3] | 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 | # ------------------------------------------------------------------ |
---|
[1] | 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 |
---|
[4] | 236 | specified node. A path component name has the form "type(id)" |
---|
[1] | 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 |
---|
[4] | 266 | the form "typeNN(id).typeNN(id). ...", where each |
---|
| 267 | "type" is a tag <type>; if the optional NN is specified, |
---|
[1] | 268 | it indicates an index for the <type> tag within its parent; |
---|
[4] | 269 | if the optional (id) part is included, it indicates a |
---|
| 270 | tag of the form <type id="id">. |
---|
[1] | 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 | |
---|
[4] | 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 | |
---|
[1] | 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 | # |
---|
[4] | 298 | # Pick apart "type123(id)" for this part. |
---|
[1] | 299 | # |
---|
| 300 | match = self.re_pathElem.search(part) |
---|
| 301 | if not match: |
---|
[4] | 302 | raise ValueError, "bad path component '%s': should have the form 'type123(id)'" % part |
---|
[1] | 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 | # |
---|
[4] | 324 | # If the name is like "type(id)", then look for elements |
---|
[1] | 325 | # that match the type and see if one has the requested name. |
---|
[4] | 326 | # if the name is like "(id)", then look for any elements |
---|
[1] | 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 |
---|
[4] | 351 | # with the specified "type(id)" and continue on. |
---|
| 352 | # If the type is "type#", then create a node with |
---|
| 353 | # an automatic number. |
---|
[1] | 354 | # |
---|
| 355 | match = self.re_pathCreateElem.search(part) |
---|
| 356 | if match: |
---|
| 357 | (type, name) = match.groups() |
---|
| 358 | else: |
---|
| 359 | type = part |
---|
| 360 | name = "" |
---|
| 361 | |
---|
[4] | 362 | if type.endswith('#'): |
---|
| 363 | type = type.rstrip('#') |
---|
| 364 | node = self.doc.createElement(type) |
---|
[1] | 365 | |
---|
[4] | 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 |
---|
[6] | 371 | |
---|
[4] | 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 | |
---|
[1] | 383 | if name: |
---|
[4] | 384 | node.setAttribute("id",name) |
---|
[1] | 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 | |
---|
| 412 | if __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') |
---|