# Read the XML file exported by ChatMapper and convert it to JSON # Written by Bernie Roehl, June 2011 (broehl@bernieroehl.com) # Free for commercial as well as non-commercial use. # If you distribute this code, you must include my name and email address, # as well as this message. # This is unsupported software, use at your own risk. import xml.dom.minidom # XML parser import json # JSON exporter import re # regular expressions import sys class Entity: # Actors, Items, Locations, Variables and dialogEntries are all Entities def __init__(self, xmlnode): try: self.ID = int(xmlnode.attributes["ID"].value) except: pass # UserVariable no longer has an ID at all fieldnode = xmlnode.getElementsByTagName("Fields")[0] fields = fieldnode.getElementsByTagName("Field") for f in fields: # get title and replace spaces with underscores title = str(f.getElementsByTagName("Title")[0].firstChild.nodeValue) title = title.replace(" ", "_") # get value and convert it to the appropriate type value = "" # default valueNode = f.getElementsByTagName("Value")[0].firstChild if valueNode: value = str(valueNode.nodeValue) typ = getattr(f, "Type") if typ == "Boolean": value = value.lower() == "true" elif typ == "Number": if value == "": value = 0 else: value = float(value) self.__dict__[title] = value # store title and value in this our dict # Parse the XML file and load the data (Actors, Items, Locations, Variables) Actor, Item, Location, Variable = {}, {}, {}, {} doc = xml.dom.minidom.parse(sys.argv[1]) def getattr(node, name): # utility routine that returns an attribute from an xml node return str(node.attributes[name].value) def process(xmlnode, table): # add a new entity to the appropriate table ent = Entity(xmlnode) table[ent.Name.replace(" ", "_")] = ent # most things are accessed by name for xmlnode in doc.getElementsByTagName("Actor"): process(xmlnode, Actor) for xmlnode in doc.getElementsByTagName("Item"): process(xmlnode, Item) for xmlnode in doc.getElementsByTagName("Location"): process(xmlnode, Location) for xmlnode in doc.getElementsByTagName("UserVariable"): e = Entity(xmlnode) Variable[e.Name] = e.Initial_Value Variable["points"] = 0 # default variable # Load the conversation trees Conversation = {} for xmlnode in doc.getElementsByTagName("Conversation"): ent = Entity(xmlnode) Conversation[ent.ID] = ent # conversations are accessed by id ent.dialogEntries = {} # dialog entries are stored in the conversation trees # Load the dialog entries (stored in the conversation trees) priorities = ["Low", "BelowNormal", "Normal", "AboveNormal", "High"] def luaToJavascript(s): s = s.replace("~=", "!=") s = s.replace(" and ", " && ") s = re.sub("\.(?P[a-zA-Z]*)", "[\"\g\"]", s) s = re.sub("Dialog\[(?P[0-9]*)\]", "(Dialog[\"\g\"] as Boo.Lang.Hash)", s) s = re.sub("Location\[(?P[0-9]*)\]", "(Location[\"\g\"] as Boo.Lang.Hash)", s) s = re.sub("Item\[(?P[0-9]*)\]", "(Item[\"\g\"] as Boo.Lang.Hash)", s) s = re.sub("Actor\[(?P[0-9]*)\]", "(Actor[\"\g\"] as Boo.Lang.Hash)", s) return s for xmlnode in doc.getElementsByTagName("DialogEntry"): ent = Entity(xmlnode) # additional attributes for dialog entries ent.DelaySimStatus = getattr(xmlnode, "DelaySimStatus").lower() == "true" ent.FalseConditionAction = getattr(xmlnode, "FalseCondtionAction") ent.ConditionPriority = priorities.index(getattr(xmlnode, "ConditionPriority")) ent.IsGroup = getattr(xmlnode, "IsGroup").lower() == "true" ent.SimStatus = "Untouched" # handle menu text markup (remove [f] and set a ForceMenuDisplay attribute) ent.ForceMenuDisplay = False if hasattr(ent, "Menu_Text"): ent.ForceMenuDisplay = ent.Menu_Text.find("[f]") >= 0 ent.Menu_Text = re.sub("\[f\]", "", ent.Menu_Text) # handle dialogue markup (remove things in square brackets, map | to newline if hasattr(ent, "Dialogue_Text"): ent.Dialogue_Text = re.sub("\[[^\]]*\]", "", ent.Dialogue_Text) ent.Dialogue_Text = ent.Dialogue_Text.replace("|", "\r\n") # handle optional conditions and scripts ent.condition, ent.script = "", "" node = xmlnode.getElementsByTagName("ConditionsString")[0].firstChild if node: ent.condition = luaToJavascript(str(node.nodeValue).replace("\n", " ")) node = xmlnode.getElementsByTagName("UserScript")[0].firstChild if node: ent.script = luaToJavascript(str(node.nodeValue).replace("\n", ";")) # add to appropriate conversation convNumber = int(getattr(xmlnode, "ConversationID")) conversation = Conversation[convNumber] conversation.dialogEntries[ent.ID] = ent ent.conversation = convNumber if getattr(xmlnode, "IsRoot").lower() == "true": conversation.Root = ent.ID # process links ent.links = [] links = xmlnode.getElementsByTagName("Link") for link in links: ent.links.append(int(getattr(link, "DestinationDialogID"))) everything = { "Item" : Item, "Actor" : Actor, "Location" : Location, "Variable" : Variable, "Conversation" : Conversation } def dumpdef(obj): return obj.__dict__ json.dump(everything, sys.stdout, indent=2, default=dumpdef)