From: yomguy Date: Thu, 16 Oct 2008 20:49:58 +0000 (+0000) Subject: Create tools/, add acpi.py X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=fde402fc34353e106ea7abf43821dd7aaad91f43;p=telecaster-cgi.git Create tools/, add acpi.py --- diff --git a/telecaster.py b/telecaster.py index a849655..46f686e 100755 --- a/telecaster.py +++ b/telecaster.py @@ -273,7 +273,7 @@ class WebView: def __init__(self, school_file): self.conf = xml2dict(school_file) self.conf = self.conf['telecaster'] - self.interface = 'eth0' + self.interface = 'eth1' self.ip = get_ip_address(self.interface) self.url = 'http://' + self.ip self.port = self.conf['port'] diff --git a/tools.py b/tools.py deleted file mode 100644 index 63340ad..0000000 --- a/tools.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/python -# *-* coding: utf-8 *-* -""" - teleoddcast - - Copyright (c) 2006-2007 Guillaume Pellerin - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -""" - -import os -import cgi -import shutil -import datetime -import string -import time -from xmltodict import * -from mutagen.oggvorbis import OggVorbis -import socket -import fcntl -import struct - -def get_ip_address(ifname): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - return socket.inet_ntoa(fcntl.ioctl( - s.fileno(), - 0x8915, # SIOCGIFADDR - struct.pack('256s', ifname[:15]) - )[20:24]) - -def get_lines(file): - """Get lines from a file""" - fic = open(file,'r') - lines = fic.readlines() - fic.close() - return lines - -def clean_string(string): - """removes blank spaces and accents""" - string = string.replace(' ','_') - string = string.replace('é','e') - string = string.replace('è','e') - return string - -def xml2dict(conf_file): - confile = open(conf_file,'r') - conf_xml = confile.read() - confile.close() - dict = xmltodict(conf_xml,'utf-8') - return dict - -def get_pid(proc,uid): - """Get a process pid filtered by arguments and uid""" - (list1, list2) = os.popen4('pgrep -n -f -U '+str(uid)+' '+'"'+proc+'"') - pids = list2.readlines() - if pids != '': - for pid in pids: - index = pids.index(pid) - pids[index] = pid[:-1] - return pids - -def get_params_from_lock(lock_file): - lockfile = open(lock_file,'r') - params = lockfile.readline() - params_ok = params.split('_*_') - lockfile.close() - return params_ok - -def dict2tuple(dict): - len_dict = len(dict['conference']) - if len_dict == 1: - return dict['conference']['name'] - else: - tup = [] - for value in dict['conference']: - tup.append(value['name']) - return tup - -def dict2tuple_iso(dict): - len_dict = len(dict['conference']) - if len_dict == 1: - return unicode(dict['conference']['name'],'utf8').encode('iso-8859-1') - else: - tup = [] - for value in dict['conference']: - tup.append(unicode(value['name'],'utf8').encode('iso-8859-1')) - return tup - -def get_conference_from_lock(lock_file): - lockfile = open(lock_file,'r') - conference = lockfile.readline() - conference_l = conference.split('_*_') - conference_dict = {'title': conference_l[0], - 'department': conference_l[1], - 'conference': conference_l[2], - 'session': conference_l[3], - 'professor': conference_l[4], - 'comment': conference_l[5]} - lockfile.close() - return conference_dict - -def str_to_bool(string): - return string == 'true' - -def norm_string(string): - pass diff --git a/tools/acpi.py b/tools/acpi.py new file mode 100644 index 0000000..4cb3db5 --- /dev/null +++ b/tools/acpi.py @@ -0,0 +1,553 @@ +############################################################################## +## +## $Id: acpi.py,v 1.23 2003/12/15 20:04:09 riemer Exp $ +## +## Copyright (C) 2002-2003 Tilo Riemer , +## Luc Sorgue and +## Rds +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions +## are met: +## +## 1. Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## 2. Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in the +## documentation and/or other materials provided with the distribution. +## 3. The name of the author may not be used to endorse or promote products +## derived from this software without specific prior written permission. +## +## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +## +############################################################################### + +import os,stat,sys + + +# genenal constants ########################################################### + +#enums + +VERSION = "0.3.0" + +OFFLINE = 0 +ONLINE = 1 +CHARGING = 2 #implies ONLINE +FAN_OFF = 0 +FAN_ON = 1 + + + +# exceptions ################################################################## + +#exception enums +ERR_GENERIC = -1 +ERR_NO_DEVICE = -2 +ERR_NOT_IMPLEMENTED = -3 +ERR_NO_LOW_LEVEL = -4 +ERR_CONFIGURATION_CHANGED = -5 #reload module? or function reconfigure? +ERR_NOT_ALLOWED = -6 #access for some resource not allowed --> better idea? + +class AcpiError(Exception): + """ACPI exceptions""" + + def __init__(self, errno): + self.errno = errno + + def __str__(self): + if self.errno == ERR_GENERIC: + return "Any ACPI error occured." + elif self.errno == ERR_NO_DEVICE: + return "ACPI is not configured on this host." + elif self.errno == ERR_NOT_IMPLEMENTED: + return "No implementation for this operating system." + elif self.errno == ERR_NO_LOW_LEVEL: + return "Acpi_lowlevel module not found." + elif self.errno == ERR_CONFIGURATION_CHANGED: + return "ACPI configuartion has been changed." + else: + return "Unknown error occured." + + + +# interface ################################################################### + +class Acpi: + """Interface class for ACPI""" + + def __init__(self): + res = sys.platform + if res.find("freebsd4") > -1: + self.acpi = None #throw exception + raise AcpiError, ERR_NOT_IMPLEMENTED + + elif res.find("netbsd1") > -1: + self.acpi = None #throw exception + raise AcpiError, ERR_NOT_IMPLEMENTED + + elif res.find("linux2") > -1: + self.acpi = AcpiLinux() + + elif res.find("linux") > -1: + #some systems return linux instead of linux2. We should + #show a warning or check by ourselves for Linux2 + self.acpi = AcpiLinux() + + else: + self.acpi = None #throw exception (os unknown) + raise AcpiError, ERR_NOT_IMPLEMENTED + + + def identity(self): + """Returns the identity of this module""" + return "acpi.py" + + def version(self): + """Returns the version of this module""" + return VERSION + + def update(self): + """Updates the ACPI state""" + self.acpi.update() + + def percent(self): + """Returns percentage capacity of all batteries""" + return self.acpi.percent() + + def capacity(self): + """Returns capacity of all batteries (in mWh)""" + return self.acpi.capacity() + + def nb_of_batteries(self): + """Returns the number of batteries""" + return self.acpi.nb_of_batteries() + + def charging_state(self): + """Returns ac state (off-/online/charging)""" + return self.acpi.charging_state() + + def estimated_lifetime(self): + """Returns Estimated Lifetime as real number""" + return self.acpi.estimated_lifetime() + + def temperature(self, idx): + """Returns Processor Temperature""" + return self.acpi.temperature(idx) + + def fan_state(self, idx): + """Returns fan states""" + return self.acpi.fan_state(idx) + + +#unfinished: don't use it or better send us improvements ;-) + def frequency(self, idx): + """ Return the frequency of the processor""" + return self.acpi.frequency(idx) + + def performance_states(self, idx): + """ Return a list of available frequencies for the proc """ + return self.acpi.performance_states(idx) + + def set_frequency(self, f): + """ Set the processor frequency - Warning ! Needs root privileges to work """ + return self.acpi.set_frequency(f) + + + +# implementation for Linux #################################################### + +class AcpiLinux: + def __init__(self): + """init ACPI class and check for any ACPI features in /proc/acpi/""" + + #we read all acpi stuff from here + self.proc_acpi_dir = "/proc/acpi" + #self.proc_acpi_dir = "/home/riemer/main/ACPI/proc/acpi" + + self.init_batteries() + self.init_fans() + #self.init_processors() + self.init_temperatures() + + self.update() + + + def update(self): + """Read current states of supported acpi components""" + + self.update_batteries() + self.update_fans() + #self.update_processors() + self.update_temperatures() + + # battery related functions + def init_batteries(self): + """Checks for and initializes the batteries""" + + self.proc_battery_dir = self.proc_acpi_dir + "/battery" + + # empty lists implies no battery, no capacity etc. + self.design_capacity = {} + self.life_capacity = {} + self.present_rate = {} + + # empty list of battery sub directories; implies no batteries available + self.battery_dir_entries = [] + + try: + battery_dir_entries = os.listdir(self.proc_battery_dir) + except OSError: + self.ac_line_state = ONLINE # no batteries: we assume that a cable is plugged in ;-) + return #nothing more to do + + + try: + for i in battery_dir_entries: + mode = os.stat(self.proc_battery_dir + "/" + i)[stat.ST_MODE] + if stat.S_ISDIR(mode): + self.battery_dir_entries.append(i) + except OSError: + # the battery module is not correctly loaded, or is broken. + # currently self.battery_dir_entries has no batteries or only + # the batteries which we could stat + # because the appended dirs should be okay we do not return here + pass + + self.ac_line_state = OFFLINE + +#later: the newer acpi versions seems to generate always two BAT dirs... +#check info for present: no + try: + for i in self.battery_dir_entries: + #print self.proc_battery_dir + "/" + i + "/info" + info_file = open(self.proc_battery_dir + "/" + i + "/info") + line = info_file.readline() + + while len(line) != 0: + if line.find("last full capacity:") == 0: + cap = line.split(":")[1].strip() + try: + self.design_capacity[i] = int(cap.split("m")[0].strip()) + except ValueError: + #no value --> conversion to int failed + self.design_capacity[i] = 0 + + line = info_file.readline() + info_file.close() + except IOError: + #print "No batt info found." + # the battery module is not correctly loaded... the file info should exist. + # wipe out all lists --> no battery infos + self.battery_dir_entries = [] + self.design_capacity = {} + self.life_capacity = {} + self.present_rate = {} + + + def update_batteries(self): + """Read current state of batteries""" + + try: + for i in self.battery_dir_entries: + state_file = open(self.proc_battery_dir + "/" + i + "/state") + line = state_file.readline() + + while len(line) != 0: + if line.find("remaining capacity") == 0: + cap = line.split(":")[1].strip() + try: + self.life_capacity[i] = int(cap.split("m")[0].strip()) + except ValueError: + self.life_capacity[i] = 0 + +# it's possible that in battery/*/info the charging state is unknown +# --> then we must check ac_state... +# iterating over all batteries this way is not smart. better implementation needed +# better are funcs for capacity, acstate and prrate + + # a little bit tricky... if loading of ac driver fails, we cant use info + # from /proc/ac_*/... + # if information in /proc/acpi/battery/*/state is wrong we had to + # track the capacity history. + # I assume that all battery state files get the same state. + if line.find("charging state") == 0: + state = line.split(":")[1].strip() + if state == "discharging": + self.ac_line_state = OFFLINE + elif state == "charging": + self.ac_line_state = CHARGING + else: + self.ac_line_state = ONLINE + + # Read the present energy consumption to + # estimate life time + + if line.find("present rate:") == 0: + try: + pr_rate = float(line.split(":")[1].strip().split("m")[0].strip()) + except ValueError: + pr_rate = 0 + + self.present_rate[i] = pr_rate + + line = state_file.readline() + state_file.close() + except IOError: + raise AcpiError, ERR_CONFIGURATION_CHANGED + + # maybe we should restart init_batteries instead of generating an error ? + # the user may have unplugged the battery. + #init_batteries() + # I prefer raising an exception because we would run into a recursion of + # member funcs what is not a good idea. + # the case that this error occurs should be very rare + + + def init_temperatures(self): + """Initializes temperature stuff""" + + self.proc_thermal_dir = self.proc_acpi_dir + "/thermal_zone" + + # empty list implies no thermal feature supported + self.temperatures = {} + + # empty list of thermal sub directories; implies no thermal infos available + self.thermal_dir_entries = [] + + try: + thermal_dir_entries = os.listdir(self.proc_thermal_dir) + except OSError: + return #nothing more to do + + try: + for i in thermal_dir_entries: + mode = os.stat(self.proc_thermal_dir + "/" + i)[stat.ST_MODE] + if stat.S_ISDIR(mode): + self.thermal_dir_entries.append(i) + except OSError: + # the thermal module is not correctly loaded, or is broken. + # because the appended dirs should be okay we do not return here + pass + + + def update_temperatures(self): + """Read current temperatures""" + + try: + for i in self.thermal_dir_entries: + file = open(self.proc_thermal_dir + "/" + i + "/temperature") + line = file.readline() + while len(line) != 0: + if line.find("temperature") == 0: + self.temperatures[i] = line.split(":")[1].strip() + line = file.readline() + file.close() + except IOError: + raise AcpiError,ERR_CONFIGURATION_CHANGED + + + def init_fans(self): + """Initialize fans""" + + self.proc_fan_dir = self.proc_acpi_dir + "/fan" + + self.fans = {} + + # empty list of fan sub directories; implies no fan infos available + self.fan_dir_entries = [] + + try: + fan_dir_entries = os.listdir(self.proc_fan_dir) + except OSError: + return #nothing more to do + + try: + for i in fan_dir_entries: + mode = os.stat(self.proc_fan_dir + "/" + i)[stat.ST_MODE] + if stat.S_ISDIR(mode): + self.fan_dir_entries.append(i) + except OSError: + # the fan module is not correctly loaded, or is broken. + # because the appended dirs should be okay we do not return here + pass + + + def update_fans(self): + """Read current state of fans""" + + try: + for i in self.fan_dir_entries: + file = open(self.proc_fan_dir + "/" + i + "/state") + line = file.readline() + while len(line) != 0: + if line.find("status") == 0: + if line.split(":")[1].strip() == 'on': + self.fans[i] = FAN_ON + else: + self.fans[i] = FAN_OFF + line = file.readline() + file.close() + except IOError: + raise AcpiError,ERR_CONFIGURATION_CHANGED + + + def init_processors(self): + """Initialize processors""" + + self.proc_processor_dir = self.proc_acpi_dir + "/processor" + +# TODO: adapt it for multiple CPUs --> we need a matrix instead of a vector!!! + self.perf_states = {} #empty list implies no processor support + + # empty list of processor sub directories; implies no processor infos available + self.processor_dir_entries = [] + + try: + processor_dir_entries = os.listdir(self.proc_processor_dir) + except OSError: + return #nothing more to do + + try: + for i in processor_dir_entries: + mode = os.stat(self.proc_processor_dir + "/" + i)[stat.ST_MODE] + if stat.S_ISDIR(mode): + self.processor_dir_entries.append(i) + except OSError: + # the processor module is not correctly loaded, or is broken. + # because the appended dirs should be okay we do not return here + pass + + try: + for i in self.processor_dir_entries: + file = open(self.proc_processor_dir + "/" + i + "/performance") + line = file.readline() + while(len(line)!=0): + if line.find("MHz") > -1: + state = line.split(":")[0].strip().split("P")[-1] + freq = line.split(":")[1].split(",")[0].strip() + self.perf_states[freq] = state + line = file.readline() + file.close() + except IOError: + self.processor_dir_entries = [] + self.perf_states = {} #reset list --> should we throw an exception? No! + return + + + def update_processors(self): + """Read current state of processors""" + + try: + for i in self.processor_dir_entries: + file = open(self.proc_processor_dir + "/" + i + "/performance") + line = file.readline() + + while(len(line)!=0): + if line.find("*") > -1: + self.freq = line.split(":")[1].strip().split(",")[0] + line = f.readline() + file.close() + except IOError: + raise AcpiError,ERR_CONFIGURATION_CHANGED + + + def percent(self): + """Returns percentage capacity of all batteries""" + + life_capacity = 0 + design_capacity = 0 + for i,c in self.life_capacity.items(): + life_capacity = life_capacity + c + design_capacity = design_capacity + self.design_capacity[i] + + if design_capacity == 0: + return 0 + + # should we use try catch instead of the check above? + return (life_capacity * 100) / design_capacity + + + def capacity(self): + """Returns capacity of all batteries""" + capacity = 0 + for i,c in self.life_capacity.items(): + capacity = capacity + c + return capacity + + + def nb_of_batteries(self): + #returns the number of batteries + #if it returns 0, maybe ACPI is not available or + #battery driver is not loaded + return len(self.battery_dir_entries) + + + def charging_state(self): + return self.ac_line_state + + + def estimated_lifetime(self): + + # what should we return if state==charging? + # it's not clean to return a time in one case and any + # English string in another case. + # The user can check for ac-state before call this func + time = 0 + for batt,life_capacity in self.life_capacity.items(): + if self.present_rate[batt] > 0: + time = time + life_capacity/self.present_rate[batt] + return time + + + # we need funcs like max_temperature and average_temperature + def temperature(self, idx): + #print self.temperatures + #print self.thermal_dir_entries[idx] + return self.temperatures(self.thermal_dir_entries(idx)) + + + def fan_state(self, idx): + #print self.fans + return self.fans[self.fan_dir_entries[idx]] + + + def performance_states(self, idx): + return self.perf_states[idx].keys() + + + def frequency(self, idx): + #print self.freq + return self.freq[idx] + + +# TODO: adapt it for multiple CPUs + def set_frequency(self, f): + #I think we should throw exceptions if someone goes wrong here + + if self.perf_states.has_key(f): + state = self.perf_states[f] + try: + pr = os.listdir("/proc/acpi/processor")[0] + except OSError: + raise AcpiError, ERR_NOT_ALLOWED + + try: + f = open("/proc/acpi/processor/"+pr+"/performance","w") + except IOError: + raise AcpiError, ERR_NOT_ALLOWED + + f.write(state) + f.close() + else: + raise AcpiError, ERR_NOT_ALLOWED diff --git a/tools/tools.py b/tools/tools.py new file mode 100644 index 0000000..63340ad --- /dev/null +++ b/tools/tools.py @@ -0,0 +1,118 @@ +#!/usr/bin/python +# *-* coding: utf-8 *-* +""" + teleoddcast + + Copyright (c) 2006-2007 Guillaume Pellerin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import cgi +import shutil +import datetime +import string +import time +from xmltodict import * +from mutagen.oggvorbis import OggVorbis +import socket +import fcntl +import struct + +def get_ip_address(ifname): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack('256s', ifname[:15]) + )[20:24]) + +def get_lines(file): + """Get lines from a file""" + fic = open(file,'r') + lines = fic.readlines() + fic.close() + return lines + +def clean_string(string): + """removes blank spaces and accents""" + string = string.replace(' ','_') + string = string.replace('é','e') + string = string.replace('è','e') + return string + +def xml2dict(conf_file): + confile = open(conf_file,'r') + conf_xml = confile.read() + confile.close() + dict = xmltodict(conf_xml,'utf-8') + return dict + +def get_pid(proc,uid): + """Get a process pid filtered by arguments and uid""" + (list1, list2) = os.popen4('pgrep -n -f -U '+str(uid)+' '+'"'+proc+'"') + pids = list2.readlines() + if pids != '': + for pid in pids: + index = pids.index(pid) + pids[index] = pid[:-1] + return pids + +def get_params_from_lock(lock_file): + lockfile = open(lock_file,'r') + params = lockfile.readline() + params_ok = params.split('_*_') + lockfile.close() + return params_ok + +def dict2tuple(dict): + len_dict = len(dict['conference']) + if len_dict == 1: + return dict['conference']['name'] + else: + tup = [] + for value in dict['conference']: + tup.append(value['name']) + return tup + +def dict2tuple_iso(dict): + len_dict = len(dict['conference']) + if len_dict == 1: + return unicode(dict['conference']['name'],'utf8').encode('iso-8859-1') + else: + tup = [] + for value in dict['conference']: + tup.append(unicode(value['name'],'utf8').encode('iso-8859-1')) + return tup + +def get_conference_from_lock(lock_file): + lockfile = open(lock_file,'r') + conference = lockfile.readline() + conference_l = conference.split('_*_') + conference_dict = {'title': conference_l[0], + 'department': conference_l[1], + 'conference': conference_l[2], + 'session': conference_l[3], + 'professor': conference_l[4], + 'comment': conference_l[5]} + lockfile.close() + return conference_dict + +def str_to_bool(string): + return string == 'true' + +def norm_string(string): + pass diff --git a/tools/xmltodict.py b/tools/xmltodict.py new file mode 100644 index 0000000..b85d556 --- /dev/null +++ b/tools/xmltodict.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +# Easily import simple XML data to Python dictionary +# http://www.gmta.info/publications/parsing-simple-xml-structure-to-a-python-dictionary + +import xml.dom.minidom + +def haschilds(dom): + # Checks whether an element has any childs + # containing real tags opposed to just text. + for childnode in dom.childNodes: + if childnode.nodeName != "#text" and \ + childnode.nodeName != "#cdata-section": + return True + return False + +def indexchilds(dom, enc): + childsdict = dict() + for childnode in dom.childNodes: + name = childnode.nodeName.encode(enc) + if name == "#text" or name == "#cdata-section": + # ignore whitespaces + continue + if haschilds(childnode): + v = indexchilds(childnode, enc) + else: + v = childnode.childNodes[0].nodeValue.encode(enc) + if name in childsdict: + if isinstance(childsdict[name], dict): + # there is multiple instances of this node - convert to list + childsdict[name] = [childsdict[name]] + childsdict[name].append(v) + else: + childsdict[name] = v + return childsdict + +def xmltodict(data, enc=None): + dom = xml.dom.minidom.parseString(data.strip()) + return indexchilds(dom, enc) + + diff --git a/xmltodict.py b/xmltodict.py deleted file mode 100644 index b85d556..0000000 --- a/xmltodict.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python - -# Easily import simple XML data to Python dictionary -# http://www.gmta.info/publications/parsing-simple-xml-structure-to-a-python-dictionary - -import xml.dom.minidom - -def haschilds(dom): - # Checks whether an element has any childs - # containing real tags opposed to just text. - for childnode in dom.childNodes: - if childnode.nodeName != "#text" and \ - childnode.nodeName != "#cdata-section": - return True - return False - -def indexchilds(dom, enc): - childsdict = dict() - for childnode in dom.childNodes: - name = childnode.nodeName.encode(enc) - if name == "#text" or name == "#cdata-section": - # ignore whitespaces - continue - if haschilds(childnode): - v = indexchilds(childnode, enc) - else: - v = childnode.childNodes[0].nodeValue.encode(enc) - if name in childsdict: - if isinstance(childsdict[name], dict): - # there is multiple instances of this node - convert to list - childsdict[name] = [childsdict[name]] - childsdict[name].append(v) - else: - childsdict[name] = v - return childsdict - -def xmltodict(data, enc=None): - dom = xml.dom.minidom.parseString(data.strip()) - return indexchilds(dom, enc) - -