#!/usr/bin/env python # $Id$ # boincxml.py - xml utilities for boinc import sys import xml.dom.minidom def append_new_element(parent_node, name): new_element = xml.dom.minidom.Element(name) if isinstance(parent_node,xml.dom.minidom.Document): new_element.ownerDocument = parent_node else: assert(parent_node.ownerDocument) new_element.ownerDocument = parent_node.ownerDocument parent_node.appendChild(new_element) return new_element def get_elements(node, name): return node.getElementsByTagName(name) def get_element(node, name, optional=True): try: return get_elements(node,name)[0] except IndexError: if optional: return append_new_element(node, name) raise SystemExit("ERROR: Couldn't find xml node <%s>"% name) def _None2Str(object): if object == None: return '' else: return object def get_element_data(node): return node and _None2Str(node.firstChild and node.firstChild.data) def get_element_int(node, default=0): try: return int(get_element_data(node)) except: return default def get_child_elements(node): return filter(lambda node: node.nodeType == node.ELEMENT_NODE, node.childNodes) def set_element(node, new_data): if node.firstChild and node.firstChild.data: node.firstChild.data = str(new_data) else: # different versions of pyxml have different interface (!) try: new_data_node = xml.dom.minidom.Text() except: new_data_node = xml.dom.minidom.Text('') new_data_node.data = str(new_data) new_data_node.ownerDocument = node.ownerDocument node.appendChild(new_data_node) def strip_white_space(node): ''' strip white space from text nodes, and remove empty nodes. ''' # the [:] below is to copy the list since removing children modifies the # list. for child in node.childNodes[:]: if child.nodeType == child.TEXT_NODE: child.data = child.data.strip() if not child.data:# and (child.nextSibling or child.previousSibling): node.removeChild(child) else: strip_white_space(child) def get_elements_as_dict(node): dict = {} for child in get_child_elements(node): # note: str() changes from unicode to single-byte strings dict[str(child.nodeName)] = get_element_data(child) return dict class ConfigDict: def __init__(self, dom_node): self._node = dom_node self._name = self._node.nodeName self.__dict__.update(get_elements_as_dict(self._node)) def save(self): for key in self.__dict__: if key.startswith('_'): continue set_element( get_element(self._node,key,1), str(self.__dict__[key]) ) def debug_print(self): for key in self.__dict__.keys(): print key.rjust(15), '=', self.__dict__[key] class ConfigDictList(list): def __init__(self, dom_node, item_class=ConfigDict): self._node = dom_node list.__init__(self, map(item_class, get_child_elements(self._node))) def save(self): map(ConfigDict.save, self) def make_node_and_append(self, name): '''Make a new ConfigDict and append it. Returns new ConfigDict.''' new_element = append_new_element(self._node, name) new_cd = ConfigDict(new_element) self.append(new_cd) return new_cd def remove_node(self, item): self.remove(item) self._node.removeChild(item._node) class XMLConfig: '''Base class for xml config files''' def __init__(self, filename = None): # default_filename should be defined by children self.filename = filename or self.default_filename def _init_empty_xml(self): self.xml = xml.dom.minidom.parseString(self.default_xml) def init_empty(self): self._init_empty_xml() self._get_elements() return self def read(self, failopen_ok=False): """Read the XML object's file source and return self.""" try: self.xml = xml.dom.minidom.parse(self.filename) strip_white_space(self.xml) except IOError, e: if not failopen_ok: # raise raise Exception("%s: Couldn't parse XML config\n%s: %s"%(sys.argv[0],sys.argv[0],e)) print >>sys.stderr, "Warning:", e # self.xml = xml.dom.minidom.Document() self._init_empty_xml() self._get_elements() return self def _get_elements(self): pass def write(self, output=None): """Write XML data to filename, or other stream.""" self._set_elements() if not output: output = open(self.filename,'w') self.xml.writexml(output, "", " ", "\n") print >>output return self def _set_elements(self): pass