Python:KeyValues
From Devicenull's Code
This is a basic keyvalues parser. KeyValues are used by the source engine.
import string class PropertyNotFound(exceptions.Exception): def __init__(self,property): self.property = property def __str__(self): print "","PropertyNotFound: %s" % (self.property) class KeyNotFound(exceptions.Exception): def __init__(self,key): self.key = key def __str__(self): print "","KeyNotFound: %s" % (self.key) class Key: def __init__(self,name): self.name = name self.properties = {} self.subkeys = [] def getPropertyValue(self,property_name): if not self.properties.has_key(property_name): raise PropertyNotFound(property_name) return self.properties[property_name] def setPropertyValue(self,property_name,property_value): self.properties[property_name] = [property_value] def setMultiplePropertyValue(self,property_name,property_value,offset=-1): if offset == -1: self.properties[property_name].append(property_value) else: self.properties[property_name][offset] = property_value def getSubKey(self,key_name): for cur in self.subkeys: if cur.name == key_name: return cur raise KeyNotFound(key_name) def setSubKey(self,key_value): self.subkeys.append(key_value) def __str__(self): ret = "\"%s\"\n{" % (self.name) #cant believe I'm doing this, but valves parser is stupid for cur in self.properties.keys(): if cur == "GameBin": for val in self.properties[cur]: ret = "%s\n\t\"%s\"\t\"%s\"" % (ret,cur,val) for cur in self.properties.keys(): if cur != "GameBin": for val in self.properties[cur]: ret = "%s\n\t\"%s\"\t\"%s\"" % (ret,cur,val) for cur in self.subkeys: child = "%s" % (cur) for c in child.split("\n"): ret = "%s\n\t%s" % (ret,c) ret = "%s\n}" % (ret) return ret class KV_Events: # This will ocstring once at a minimum for the parent section # It is called again for any child section def onSectionBegin(self,section_name): pass # This will ocstring once at a minimum for the parent section # It is called when any section ends def onSectionEnd(self): pass # This is called once for each property present def onProperty(self,property_name,property_value): pass # This will be called for any comment present def onComment(self,comment): pass BEGIN = 0 IN_NAME = 1 DONE_NAME = 2 IN_VALUE = 3 IN_COMMENT = 4 IN_ML_COMMENT = 5 class KeyValuesParser: def __init__(self,event_receiver): self.events = event_receiver def parse(self,string): done = 0 depth = 0 name = "" value = "" state = BEGIN total_len = len(string) i = 0 while i < total_len: if state == BEGIN: if string[i].isspace(): i = i + 1 continue elif string[i] == '"': # starting a name string state = IN_NAME elif string[i].isalnum(): state = IN_NAME name = string[i] elif string[i] == '}': # a section has ended self.events.onSectionEnd() elif string[i] == '/' and string[i+1] == '/': # beginning a comment # skip the second slash i = i + 1 state = IN_COMMENT elif string[i] == '/' and string[i+1] == '*': # beginning a multiline comment i = i + 1 state = IN_ML_COMMENT else: #print 'Unexpected character, ignoring' pass elif state == IN_NAME: if string[i] == '"' or string[i].isspace(): # done reading a name state = DONE_NAME else: name = "%s%s" % (name,string[i]) # we are done reading in a name, and trying to figure out what to # do next (either section name, or property) elif state == DONE_NAME: if string[i].isspace(): i = i + 1 continue elif string[i] == '{': # starting a new section, call the handler.. self.events.onSectionBegin(name) # and clear the name name = "" state = BEGIN elif string[i] == '"' : state = IN_VALUE else: state = IN_VALUE value = string[i] # we are reading in the text of a value elif state == IN_VALUE: if string[i] == '/' and string[i+1] == '/': self.events.onProperty(name,value.strip()) name = value = "" state = IN_COMMENT if string[i] == '"' or string[i] == "\n" or string[i] == "\r": self.events.onProperty(name,value) name = value = "" state = BEGIN else: value = "%s%s" % (value,string[i]) elif state == IN_COMMENT: if string[i] == "\n" or string[i] == "\r": # comment is done! self.events.onComment(value) value = "" state = BEGIN else: value = "%s%s" % (value,string[i]) elif state == IN_ML_COMMENT: if string[i] == '*' and string[i+1] == '/': # ML comment is done i = i + 1 self.events.onComment(value) value = "" state = BEGIN else: value = "%s%s" % (value,string[i]) i = i + 1 # Event handler for tree builder class class KV_TreeBuilder: def __init__(self): self.parent = None self.keystack = [] self.cur_sectn = None def onSectionBegin(self,section_name): if self.parent == None: self.parent = Key(section_name) self.keystack.append(self.parent) self.cur_sectn = self.parent else: sectn = Key(section_name) self.keystack.append(sectn) self.cur_sectn.subkeys.append(sectn) self.cur_sectn = sectn def onSectionEnd(self): self.keystack.pop() if len(self.keystack) > 0: self.cur_sectn = self.keystack[-1] def onProperty(self,property_name,property_value): # combine multiple properties into one try: text = self.cur_sectn.getPropertyValue(property_name) self.cur_sectn.setMultiplePropertyValue(property_name,property_value,-1) except: self.cur_sectn.setPropertyValue(property_name,property_value) def onComment(self,comment): # comments are stripped from the resulting tree, so do nothing pass class KeyValuesTreeBuilder: def __init__(self): self.events = KV_TreeBuilder() self.parser = KeyValuesParser(self.events) def parse(self,kv_string): self.parser.parse(kv_string) return self.events.parent