#!/usr/bin/python import sys, os, re, os.path import tokenize, token, types import xml.dom.minidom, time from types import ListType, DictType, IntType, FloatType wowdir = '/home/gregstoll/.cedega/World of Warcraft/c_drive/Program Files/World of Warcraft' accountName = 'GREGSTOLL' serverName = 'Alleria' guildName = 'Gwydden Telaid' privateDataString = '' SeqSep = object() EndOfSeq = object() ratingTable = {5: 0, 6: 2000} for rank in range(7, 18): ratingTable[rank] = 5000 * (rank - 6) # Maps the heading in the lua heading to the displayed header headings = [('Name', 'Name'), ('Note', 'Note'), ('Level', 'Level'), ('Class', 'Class'), ('Rank', 'Guild Rank'), ('LastOnline', 'Last Online')] xmlheadings = [({'entries':['Professions/Profession[0]/@name', 'Professions/Profession[0]/Rank', 'Professions/Profession[0]/MaxRank'], 'joins':[': ', '/']}, 'Prof 1'), ({'entries':['Professions/Profession[1]/@name', 'Professions/Profession[1]/Rank', 'Professions/Profession[1]/MaxRank'], 'joins':[': ', '/']}, 'Prof 2'), (['PVPRankName', 'PVPRank'], 'Rank'), (['PVPApproxRating', 'PVPApproxRating'], 'Rating (approx)'), (['PVPLifetimeHighestRankName', 'PVPLifetimeHighestRank'], 'Highest Rank'), ({'entries':['PVPLifetimeHK','PVPLifetimeDK'], 'joins':['/']}, 'Lifetime HK/DK'), ({'entries':['PVPWeekHK', 'PVPWeekHonor'], 'joins':['/']}, 'This Week HK/Honor'), ({'entries':['PVPLastWeekHK', 'PVPLastWeekHonor'], 'joins':['/']}, 'Last Week HK/Honor')] class GuildData: def __init__(self): self.members = {} self.attributes = {} class GuildDataParser: def __init__(self, lines): self.lines = lines def readline(self): try: l = self.lines.pop(0) + "\n" return l except: return "" def printToken(self, ttype, tstr): #print "Got token: %s (type is %d)" % (tstr, ttype) pass def parseObject(self): tkns = tokenize.generate_tokens(self.readline) (n,v) = self.parseExpression(tkns) return v def getNonNLToken(self, tkns): (ttype, tstr, ps, pe, lne) = tkns.next() while (ttype == tokenize.NL): (ttype, tstr, ps, pe, lne) = tkns.next() return (ttype, tstr, ps, pe, lne) def strNumEval(self, ttype, tstr): if ttype == token.NUMBER: return str(tstr) elif ttype == token.STRING: if ((tstr.startswith('"') and tstr.endswith('"')) or (tstr.startswith("'") and tstr.endswith("'"))): return str(tstr[1:-1]) else: return tstr else: raise "Called strNumEval on something of type %d" % ttype def parseExpression(self, tkns): (ttype, tstr, ps, pe, lne) = self.getNonNLToken(tkns) self.printToken(ttype, tstr) if ttype == token.OP: if tstr == '[': # Starting a string name = '' (ttype, tstr, ps, pe, lne) = self.getNonNLToken(tkns) self.printToken(ttype, tstr) while ttype == token.STRING or ttype == token.NUMBER: name = name + self.strNumEval(ttype, tstr) (ttype, tstr, ps, pe, lne) = self.getNonNLToken(tkns) self.printToken(ttype, tstr) if (tstr != ']'): raise "Expected ']', got %s" % tstr (ttype, tstr, ps, pe, lne) = self.getNonNLToken(tkns) self.printToken(ttype, tstr) if (tstr != '='): raise "Expected '=', got %s" % tstr data = self.parseValue(tkns) #print "parseExpr returning (%s,%s)" % (str(name), str(data)) return (name, data) elif tstr in ["}", "]"]: return (None, EndOfSeq) self.printToken(ttype, tstr) raise "uh-oh!" def parseValue(self, tkns): (ttype, tstr, ps, pe, lne) = self.getNonNLToken(tkns) self.printToken(ttype, tstr) if ttype in [token.STRING, token.NUMBER]: return self.strNumEval(ttype, tstr) elif ttype == token.NAME: return self.parseName(tstr) elif ttype == token.OP: if tstr == "-": return - self.parseValue(tkns) elif tstr == "[": # FODO - this is wrong, but it never happens return self.parseArray(tkns) elif tstr == "{": return self.parseNewObj(tkns) elif tstr in ["}", "]"]: return EndOfSeq elif tstr == ",": return SeqSep else: raise "parseValue found '%s'" % tstr else: return None pass def parseNewObj(self, tkns): obj = {} obj = self.parseArrayExpressions(tkns) # parseArrayExpressions consumes the ending }, so we're done. return obj def parseArrayExpressions(self, tkns): a = {} #try: if(1): while 1: (n,v) = self.parseExpression(tkns) if v == EndOfSeq: return a else: a[n] = (v) v = self.parseValue(tkns) if v == EndOfSeq: return a; elif v != SeqSep: raise "expected ',' but found: '%s'" % v #except: #raise "expected ']'"; def parseArray(self, tkns): a = []; try: while 1: v = self.parseValue(tkns) if v == EndOfSeq: return a else: a.append(v) v = self.parseValue(tkns) if v == EndOfSeq: return a; elif v != SeqSep: raise"expected ',' but found: '%s'" % v except: raise "expected ']'"; def clearOrCreateXMLElement(document, node, elementTag): tags = node.getElementsByTagName(elementTag) if (len(tags) == 0): tagToUse = document.createElement(elementTag) node.appendChild(tagToUse) else: tagToUse = tags[0] while (tagToUse.firstChild): child = tagToUse.firstChild tagToUse.removeChild(child) child.unlink() return tagToUse def updateOrAddXMLElement(document, node, elementTag, elementValue): tagToUse = clearOrCreateXMLElement(document, node, elementTag) tagToUse.appendChild(document.createTextNode(elementValue)) def getText(nodelist): rc = "" for node in nodelist: if node.nodeType == node.TEXT_NODE: rc = rc + node.data return rc """ This is not really XPath, but it's similar. / delimits nodes @attr means to get the attribute from the current node Profession[0] means to get the 0th node marked Profession """ def getXPathFromNode(node, xpathName): xpathList = xpathName.split('/') curNode = node arrayRe = re.compile(r'^(.*?)\[(\d+)\]$') for path in xpathList: if (path.startswith('@')): return curNode.getAttribute(path[1:]) tagIndex = 0 arrayMatch = arrayRe.match(path) if (arrayMatch): tagIndex = int(arrayMatch.group(2)) path = arrayMatch.group(1) curNode = curNode.getElementsByTagName(path)[tagIndex] return getText(curNode.childNodes) def updateGuildFile(guildFile, encoding): guildLines = guildFile.readlines() guildFile.close() guildLines = [line.decode(encoding).encode('utf-8') for line in guildLines] # Massage the data to make it consistent. while(guildLines[0].strip() == ''): guildLines = guildLines[1:] firstLineMatch = re.match(r'^(\w+)', guildLines[0]) if (firstLineMatch): guildLines[0] = '["' + firstLineMatch.group(1) + '"]' + guildLines[0][firstLineMatch.end(1):] #dataRe = re.compile(r'^\s*[".*?"]\s*=\s*(.*)$') guildData = GuildData() guildParser = GuildDataParser(guildLines) output = guildParser.parseObject() #print "output is %s" % output if (serverName not in output): # We have nothing to do - this is other guild data. return personData = output[serverName]['Data'] guildData = output[serverName]['Guild'] if ('Name' not in guildData or guildData['Name'] != guildName): return motd = guildData['MOTD'] memberData = output[serverName]['Guild']['Members'] members = memberData.keys() members.sort() # Store new data guildPersistantData = xml.dom.minidom.parse('guildData.xml') documentElem = guildPersistantData.documentElement #guildDataNode = documentElem.getElementsByTagName("guildData")[0] guildDataNode = documentElem guildMembers = documentElem.getElementsByTagName("character") currentTime = int(personData['Time']) # Check the time we last updated. lastTime = int(getXPathFromNode(guildDataNode, 'time')) if (lastTime > currentTime): updateGeneralData = False else: updateGeneralData = True updateOrAddXMLElement(guildPersistantData, guildDataNode, 'time', str(currentTime)) for key in personData: if (key in members): # See if we have data for this member already. guildMemberDOM = None for guildMember in guildMembers: if (guildMember.getAttribute('name').encode('latin1') == key.decode('utf-8').encode('latin1')): guildMemberDOM = guildMember #print "DOM for %s is %s" % (key, guildMemberDOM) curData = personData[key] #print curData lastUpdatedTime = 0 if guildMemberDOM == None: guildMemberDOM = guildPersistantData.createElement('character') guildMemberDOM.setAttribute('name', key.decode('utf-8')) documentElem.appendChild(guildMemberDOM) else: # I don't know why this happens, but we need to write new # data if it does. try: lastUpdatedTime = getXPathFromNode(guildMemberDOM, 'Time') except IndexError: lastUpdatedTime = 0 # Don't update if this is older data. curUpdatedTime = 0 if ('Time' in curData): curUpdatedTime = curData['Time'] if (curUpdatedTime > lastUpdatedTime): # Do PVP stuff. for name in ['PVPRank', 'PVPRankName', 'PVPLifetimeHighestRank', 'PVPLifetimeHK', 'PVPLifetimeDK', 'Time', 'PVPLifetimeHighestRankName', 'PVPLastWeekHonor', 'PVPLastWeekHK', 'PVPWeekHonor', 'PVPWeekHK']: if (name in curData): updateOrAddXMLElement(guildPersistantData, guildMemberDOM, name, curData[name]) # approximate rating if ('PVPRankProgress' in curData): curRank = int(curData['PVPRank']) if (curRank < 5): approxRating = 0 elif (curRank in ratingTable and ((curRank+1) not in ratingTable)): approxRating = ratingTable[curRank] else: approxRating = ratingTable[curRank] + (ratingTable[curRank+1] - ratingTable[curRank]) * float(curData['PVPRankProgress']) updateOrAddXMLElement(guildPersistantData, guildMemberDOM, 'PVPApproxRating', str(int(approxRating))) # Do professions stuff professionsDOM = clearOrCreateXMLElement(guildPersistantData, guildMemberDOM, 'Professions') professions = curData['Professions'] # Sort the professions by name professionKeys = professions.keys() professionKeys.sort() for profession in professionKeys: professionDOM = guildPersistantData.createElement('Profession') professionDOM.setAttribute('name', profession) rank = guildPersistantData.createElement('Rank') rank.appendChild(guildPersistantData.createTextNode(professions[profession]['Rank'])) professionDOM.appendChild(rank) maxrank = guildPersistantData.createElement('MaxRank') maxrank.appendChild(guildPersistantData.createTextNode(professions[profession]['MaxRank'])) professionDOM.appendChild(maxrank) professionsDOM.appendChild(professionDOM) newXML = guildPersistantData.toxml('utf-8') #print newXML dataFile = open('guildData.xml', 'w') for line in newXML: dataFile.write(line) dataFile.close() guildPersistantData = xml.dom.minidom.parse('guildData.xml') guildMembers = documentElem.getElementsByTagName("character") guildTable = [] htmlFile = open('index.html', 'r') htmlLines = htmlFile.readlines() htmlFile.close() htmlLines = [x.strip() for x in htmlLines] updateTime = time.localtime(float(personData['Time'])) firstLine = htmlLines.index('') lastLine = htmlLines.index('') oldGuildTable = htmlLines[firstLine+1:lastLine] #print "updateGeneralData is %s" % str(updateGeneralData) if (updateGeneralData): guildTable.append('

MOTD: ' + motd + '

') guildTable.append('

Last updated: %s

' % time.asctime(updateTime)) guildTable.append('') guildTable.append(' ') for (h1, h2) in headings: guildTable[-1] = guildTable[-1] + '' % (h1.lower(), h2) for (h1Data, h2) in xmlheadings: if (isinstance(h1Data, ListType)): thClass = h1Data[0].lower() elif (isinstance(h1Data, DictType)): thClass = h1Data['entries'][0] guildTable[-1] = guildTable[-1] + '' % (thClass, h2) guildTable[-1] = guildTable[-1] + '' for member in members: guildTable.append(' ') guildTable[-1] = guildTable[-1] + getPublicMemberData(member, memberData[member]) guildTable[-1] = guildTable[-1] + privateDataString # Find xml character data for this member. guildMemberDOM = None for guildMember in guildMembers: guildMemberName = guildMember.getAttribute('name').encode('utf-8') if (guildMemberName == member): guildMemberDOM = guildMember guildTable[-1] = guildTable[-1] + getPrivateMemberData(member, guildMemberDOM).encode('latin1') guildTable[-1] = guildTable[-1] + '' guildTable.append('
%s%s
') else: # Start from the existing table and just update existingLineRe = re.compile(r'^(\s*)(.*?)(.*?' + privateDataString + ')', re.IGNORECASE) for oldLine in oldGuildTable: existingLineMatch = existingLineRe.match(oldLine) if (existingLineMatch): member = existingLineMatch.group(2) newLine = existingLineMatch.group(0) guildMemberDOM = None for guildMember in guildMembers: guildMemberName = guildMember.getAttribute('name').encode('utf-8') if (guildMemberName == member): guildMemberDOM = guildMember newLine = newLine + getPrivateMemberData(member, guildMemberDOM).encode('latin1') newLine = newLine + '' guildTable.append(newLine) else: guildTable.append(oldLine) # Add the table to the html file htmlFile = open('index.html', 'r') htmlLines = htmlFile.readlines() htmlFile.close() htmlLines = [x.strip() for x in htmlLines] firstLine = htmlLines.index('') lastLine = htmlLines.index('') # Delete the table in between del htmlLines[firstLine+1:lastLine] # Insert the table there! htmlLines[firstLine+1:firstLine+1] = guildTable htmlFile = open('index.html', 'w') for line in htmlLines: htmlFile.write(line + '\n') htmlFile.close() def getPublicMemberData(member, memberData): toReturn = '' for (h1, h2) in headings: if h1 == 'LastOnline': lastOnlineData = memberData[h1] if (len(lastOnlineData) == 0): toAdd = '' + '0' * 5 + 'now' else: if (int(lastOnlineData['year']) > 0): toAdd = str(lastOnlineData['year']) + ' years' elif (int(lastOnlineData['month']) > 0): toAdd = str(lastOnlineData['month']) + ' months' elif (int(lastOnlineData['day']) > 0): toAdd = str(lastOnlineData['day']) + ' days' else: toAdd = str(lastOnlineData['hour']) + ' hours' # Hack to make it sort right. hours = int(lastOnlineData['hour']) + 24 * (int(lastOnlineData['day']) + 30 * (int(lastOnlineData['month']) + 12 * int(lastOnlineData['year']))) hours = hours + 1 #hours = str(hours) #if (len(hours) < 5): # hours = '0' * (5 - len(hours)) + hours toAdd = '%05d' % hours + toAdd elif h1 == 'Rank': funkySort = '%02d' % int(memberData['RankIndex']) toAdd = funkySort + memberData['Rank'] else: toAdd = memberData[h1] toReturn = toReturn + '%s' % (h1.lower(), toAdd) return toReturn def getPrivateMemberData(member, guildMemberDOM): toReturn = '' for (h1Data, h2) in xmlheadings: toAdd = '' tdClass = '' if (guildMemberDOM): if (isinstance(h1Data, ListType)): if (len(h1Data) > 1): try: sortValue = int(getXPathFromNode(guildMemberDOM, h1Data[1])) except IndexError: sortValue = 0 toAdd = toAdd + '%05d' % sortValue try: value = getXPathFromNode(guildMemberDOM, h1Data[0]) except IndexError: value = '' toAdd = toAdd + value tdClass = h1Data[0].lower() elif (isinstance(h1Data, DictType)): gotNonEmptyValue = False for i in range(0, len(h1Data['entries'])): try: value = getXPathFromNode(guildMemberDOM, h1Data['entries'][i]) if (value and value != ''): gotNonEmptyValue = True except IndexError: value = '' # Do sorting if (i == 0): sortValue = None try: sortValue = int(value) except ValueError: sortValue = None if (isinstance(sortValue, IntType)): sortValue = '%05d' % sortValue if (sortValue): toAdd = toAdd + '%s' % sortValue toAdd = toAdd + value if (i != len(h1Data['entries']) - 1): toAdd = toAdd + h1Data['joins'][i] if (not gotNonEmptyValue): toAdd = '' tdClass = h1Data['entries'][0].lower() else: print "h2 is %s" % h2 toReturn = toReturn + '%s' % (tdClass, toAdd) return toReturn if (__name__ == '__main__'): updateGuildFile(open(os.path.join(wowdir,'WTF/Account', accountName, 'SavedVariables/GuildInfoTable.lua'), 'r'), 'utf-8')