#!/usr/bin/python # -*- coding: utf-8 -*- try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET import time class Race: """A single race event.""" def __init__(self, name=None, date=None, fiso_code=0): """""" self.name = name self.date = date self.classes = {} # Key = classes_id, Values = the Class itself. self.runners = {} # Key = runner_id, Values = the Runner itself. self.legs = {} # Key = legname, Values = the leg itself. self.clubs = {} # Key = club_id, Values = the Club itself. self.ns = '' # Current iof file namespace. self.statuses = [] # List of all NON 'OK' statuses found. self.unk_cnt = 0 # A counter to name/list the ones without a fiso id. self.org_cnt = 0 # A counter to name/list the ones without an organisation self.avoid_unk = False # Used to skip the ones without a valid Fiso_id. self.fiso_code = fiso_code def load_from_iofxml(self, filename_=None, class_remap=None, clubs_ko=None, avoid_unk=False): if class_remap is None: class_remap = {} if clubs_ko is None: clubs_ko = [] if filename_ is None: return self.avoid_unk = avoid_unk # print('Loading:', filename_) xmlf = ET.ElementTree(file=filename_) root = xmlf.getroot() self.ns = root.tag.split('}')[0] + '}' self.name = (root.find('{0}Event/{0}Name'.format(self.ns))).text dt = (root.find('{0}Event/{0}StartTime/{0}Date'.format(self.ns))).text tm = (root.find('{0}Event/{0}StartTime/{0}Time'.format(self.ns))).text self.date = '{} {}'.format(dt, tm) for child_of_root in root: clean_tag = child_of_root.tag.split("}")[1] if 'ClassResult' == clean_tag: for res in child_of_root: clean_res = res.tag.split("}")[1] if 'Class' == clean_res: id_ = res.find('{0}Id'.format(self.ns)).text # print('Class id:', id_) if id_ in class_remap: id_ = class_remap[id_] # print('Remaped to:', id_) name = res.find('{0}Name'.format(self.ns)) if id_ in self.classes: now_category = self.classes[id_] else: now_category = Class(name=name.text, id_=id_) self.classes[id_] = now_category if 'Course' == clean_res: course_len = res.find('{0}Length'.format(self.ns)) course_clib = res.find('{0}Climb'.format(self.ns)) now_category.distance = float(course_len.text) now_category.climb = float(course_clib.text) if 'PersonResult' == clean_res: self._load_a_runner(res, now_category, clubs_ko=clubs_ko) def _load_a_runner(self, pers_res, class_, clubs_ko=[]): """ It loads the runner details from an ElemnetTree. :param pers_res: the base element found (the PersonResult element) :type pers_res: ElementTree :return: the runner object :rtype: Runner """ fisoid = pers_res.find('{0}Person/{0}Id'.format(self.ns)) ng = pers_res.find('{0}Person/{0}Name/{0}Given'.format(self.ns)) nf = pers_res.find('{0}Person/{0}Name/{0}Family'.format(self.ns)) clubid = pers_res.find('{0}Organisation/{0}Id'.format(self.ns)) club_name = pers_res.find('{0}Organisation/{0}Name'.format(self.ns)) bib = pers_res.find('{0}Result/{0}BibNumber'.format(self.ns)) time = pers_res.find('{0}Result/{0}Time'.format(self.ns)) position = pers_res.find('{0}Result/{0}Position'.format(self.ns)) score = pers_res.find('{0}Result/{0}Score'.format(self.ns)) status = pers_res.find('{0}Result/{0}Status'.format(self.ns)) sicard = pers_res.find('{0}Result/{0}ControlCard'.format(self.ns)) current_runner = None if fisoid.text is None: if self.avoid_unk: return if '{Libero}' == nf.text: return runner_id = 'UNK{:03d}'.format(self.unk_cnt) print('#### UNK ####' * 10) print(ng.text, nf.text) self.unk_cnt += 1 else: runner_id = fisoid.text if club_name is None or club_name.text is None or clubid is None or clubid.text is None: club_str = 'ORG{:03d}'.format(self.org_cnt) clubid_str = club_str self.org_cnt += 1 else: club_str = club_name.text clubid_str = clubid.text if 'OK' == status.text.upper() and clubid_str not in clubs_ko: # print fisoid.text, ng.text, nf.text,\ # clubid.text, club_name.text, bib.text,\ # time.text, position.text, score.text, status.text if clubid_str not in self.clubs: new_club = Club(name=club_str, id_=clubid_str) self.clubs[clubid_str] = new_club current_runner = Runner(name=ng.text, class_=class_.id_, club=club_str, total=float(time.text), bib=int(bib.text), fisoid=runner_id, place=int(position.text), score=float(score.text), family=nf.text, sicard=int(sicard.text), clubid=clubid_str ) last_time = 0.0 previous_cn = 0 self.runners[current_runner.fisoid] = current_runner # print current_runner results = pers_res.find('{0}Result'.format(self.ns)) # visit_deep(results, 1000) for spl in results.findall('{0}SplitTime'.format(self.ns)): cne = spl.find('{0}ControlCode'.format(self.ns)) splte = spl.find('{0}Time'.format(self.ns)) if cne is not None and cne.text is not None and splte is not None and splte.text is not None: cn = cne.text # @MC to float directly PLEASE! splt = splte.text tsplit = float(splt) - last_time last_time = float(splt) self.__leg_to_insert(previous_cn, cn, tsplit, current_runner) previous_cn = int(cn) else: break # do add the last (implicit) split time tsplit = current_runner.time - last_time self.__leg_to_insert(previous_cn, 999, tsplit, current_runner) # Last control point receives the name '999' else: if status.text not in self.statuses: self.statuses.append(status.text) if current_runner is not None: # print 'Adding {} {}'.format(current_runner,current_runner.fisoid) class_.runnersid.append(current_runner.fisoid) # do add the current runner to the class list if class_.min is None or class_.min > current_runner.time: class_.min = current_runner.time if class_.max is None or class_.max < current_runner.time: class_.max = current_runner.time class_.tot_t += current_runner.time class_.avg = class_.tot_t / float(len(class_.runnersid)) return current_runner def __leg_to_insert(self, from_, to_, time_, runner): """It computes the leg name, then adds/updates the leg in the legs list""" nfrom = int(from_) runner.add_cn(nfrom, time_) legname = "%03d-%03d" % (nfrom, int(to_)) if legname not in self.legs: # Do instantiate a new leg if it is a new one newleg = Leg(legname) self.legs[legname] = newleg self.legs[legname].add_a_leg(time_, runner.class_, runner) def adj_scores(self, k_factor=1.0): """It copmutes the score foreach runner""" for clid in self.classes: cl = self.classes[clid] if len(cl.runnersid) > 0: # print '{}: winner time= {:6.1f}'.format(cl.id_, cl.min), for runid in cl.runnersid: new_score = k_factor * 100.0 * cl.min / self.runners[runid].time # print ' {}({:7.3f})'.format(runid, new_score), # if self.runners[runid].score != 0.0 and self.runners[runid].score != new_score: # print '!!!Score Mismatch: new={:7.3f} {}'.format(new_score, self.runners[runid]) self.runners[runid].score = new_score # print class Class: """A class defining a Category""" def __init__(self, name=None, id_=None, distance=None, climb=None, kmsf=None): self.name = name self.id_ = id_ self.distance = distance self.climb = climb self.kmsf = kmsf self.runnersid = [] # A list of runners.fifoid 's belonging to the class self.min = None self.max = None self.tot_t = 0.0 self.avg = 0.0 def __str__(self): cl_string = 'Cat: ' if self.name is not None: # cl_string += '{:6.6s} ({:3s})'.format(self.name.encode('utf-8'), self.id_) cl_string += '{:6.6s} ({:3s})'.format(self.name, self.id_) else: cl_string += "Sconos" if self.distance is not None: cl_string += ' Lunghezza: %.0f' % float(self.distance) if self.climb is not None: cl_string += ' Dislivello: %.0f ' % float(self.climb) if self.kmsf is not None: cl_string += ' kmsf: %.0f ' % float(self.kmsf) nr = len(self.runnersid) if nr > 0: cl_string += 'winner: {:6.1f} average: {:6.1f} last: {:4.0f} runners({:2d})'.format( self.min, self.avg, self.max, nr, ) for runner in self.runnersid: cl_string += ' {}'.format(runner) return cl_string class Club: """A class defining a Club""" def __init__(self, name=None, id_=None, country=None, region=None): self.name = name self.id_ = id_ self.country = country self.region = region class Runner: """An object describing a single runner""" def __init__(self, name=None, class_=None, club=None, bib=0, total=0.0, place=0, score=0.0, family=None, fisoid=None, sicard=None, clubid=None): self.name = name self.family = family self.fisoid = fisoid self.class_ = class_ self.club = club self.bib = bib self.time = total self.place = place self.score = score self.sicard = int(sicard) self.clubid = clubid self.cn_seq = [(0, 0.0), ] # each list items is a tuple with the control number and the time (cn=0 for start # for k, v in self.__dict__.items(): # print('{}= {} type={}'.format(k, v, type(v))) def add_cn(self, c_n=None, tmm=0.0): """Method to add a splittime/controlpoint in a sigle race""" if c_n is not None: self.cn_seq.append((c_n, tmm)) def full_name(self, given_first=False): if given_first: return '{} {}'.format(self.name, self.family.upper()) else: return '{} {}'.format(self.family.upper(), self.name) # if given_first: # return '{} {}'.format(self.name.encode('utf-8'), self.family.encode('utf-8').upper()) # else: # return '{} {}'.format(self.family.encode('utf-8').upper(), self.name.encode('utf-8')) def __str__(self): # runner_str = '' # try: # runner_str = '{:8.8} "{:10.10} {:10.10}" Categoria:{:5}'.format( # self.fisoid, # self.name, # self.family.upper(), # self.class_, # ) # except: # print('%' * 100) # for k, v in self.__dict__.items(): # print('{}= {} type={}'.format(k, v, type(v))) # print('/' * 100) runner_str = ('{:8.8} "{:10.10} {:10.10}" Categoria:{:5.5} Posizione:{:3d}' # ' Tempo:{:6.1f} Pettorale:{:4} score:{:7.3f} sicard:{} Società :{} ' ' Tempo: {:5} Pettorale:{:4} score:{:7.3f} sicard:{} Società :{} ' ).format( self.fisoid, self.name, self.family.upper(), self.class_, self.place, time.strftime('%H:%M:%S', time.gmtime(self.time)), self.bib, self.score, self.sicard, self.club, ) # print(type(self.sicard), type(self.bib)) for j in range(1, len(self.cn_seq)): runner_str += '[%d/%.0f]' % self.cn_seq[j] return runner_str class Leg: """A Leg in a race. It will hold a list of classes and a list of runners involved in that leg. """ def __init__(self, name=None): self.name = name self.runners = [] self.classes = [] self.min = None self.max = None self.tot_t = 0.0 self.avg = 0.0 def add_a_leg(self, tempo_, class_, runner): self.runners.append((tempo_, class_, runner)) if self.max is None or self.max < tempo_: self.max = tempo_ if self.min is None or self.min > tempo_: self.min = tempo_ self.tot_t += tempo_ self.avg = self.tot_t / float(len(self.runners)) if class_ not in self.classes: self.classes.append(class_) def __str__(self): leg_str = ('Tratta: {:.7s} corridori: {:3d} migliore: {:3.0f}' ' media: {:5.1f} peggiore: {:3.0f} Categorie({:2d}):' ).format( self.name.encode('utf-8'), len(self.runners), self.min, self.avg, self.max, len(self.classes), ) for cl in self.classes: leg_str += ' ' + cl.encode('utf-8') return leg_str # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # test if __name__ == '__main__': # filename = 'c:/MC/O/Bologna_2017/201713.xml' filename = 'C:/Mc/Python/PyPrjs/TrofeoGPeroni2018/01_Carrega_2018199.xml' gara = Race() gara.load_from_iofxml(filename) print('Race name: ', gara.name) print(' Classes: ', len(gara.classes)) print(' Runners: ', len(gara.runners)) print(' Legs: ', len(gara.legs)) print(' Unrecognized statuses: ', gara.statuses) def get_runners(rrr): return len(gara.legs[rrr].runners) """ for ll in sorted(gara.legs, key=get_runners): theleg = gara.legs[ll] print theleg print '-' * 70 for rr in gara.runners: print gara.runners[rr] print '+' * 70 for cl in gara.classes: print gara.classes[cl] print '/\\' * 35 """ gara.adj_scores() print('[-]' * 35) for rr in gara.runners: print(gara.runners[rr])