forked from Trygve/otime
Trygve
6ecf4ea103
Event har nå fees attributen. Fee kan importeres fra xml påmeldingsfiler og importeres og eksporteres fra .otime filer.
755 lines
26 KiB
Python
755 lines
26 KiB
Python
from copy import copy
|
|
import datetime
|
|
import csv
|
|
import re
|
|
import json
|
|
import xml.etree.ElementTree as ET
|
|
from fpdf import FPDF
|
|
|
|
|
|
# The event object stores all the event data.
|
|
# A .otime file is more or less just a json dump of the Event object.
|
|
class Event:
|
|
def __init__(self, eventid, name, **kwargs):
|
|
self.id = eventid
|
|
self.name = name
|
|
|
|
try:
|
|
self.courses = kwargs['courses']
|
|
except KeyError:
|
|
self.courses = []
|
|
|
|
try:
|
|
self.o_classes = kwargs['o_classes']
|
|
except KeyError:
|
|
self.o_classes = []
|
|
|
|
try:
|
|
self.runners = kwargs['runners']
|
|
except KeyError:
|
|
self.runners = []
|
|
|
|
try:
|
|
self.card_dumps = kwargs['card_dumps']
|
|
except KeyError:
|
|
self.card_dumps = []
|
|
|
|
try:
|
|
self.fees = kwargs['fees']
|
|
except KeyError:
|
|
self.fees = []
|
|
|
|
def add_course(self, *args):
|
|
for n in args:
|
|
self.courses.append(n)
|
|
|
|
def add_o_class(self, *args):
|
|
for n in args:
|
|
self.o_classes.append(n)
|
|
|
|
def add_runners(self, *args):
|
|
for n in args:
|
|
self.runners.append(n)
|
|
|
|
def add_fees(self, *args):
|
|
for n in args:
|
|
self.fees.append(n)
|
|
|
|
def import_xml_entries(self, xml_file):
|
|
self.add_runners(*runners_from_xml_entries(xml_file))
|
|
self.add_fees(*fees_from_xml_entries(xml_file))
|
|
|
|
def import_ttime_cnf(self, ttime_file):
|
|
self.add_course(*courses_from_ttime_conf(ttime_file))
|
|
ttime_file.seek(0)
|
|
self.add_o_class(*classes_from_ttime_conf(ttime_file, self.courses))
|
|
|
|
def import_ttime_db(self, ttime_file):
|
|
csvreader = csv.reader(ttime_file, delimiter=';',)
|
|
runnerarray = []
|
|
for row in csvreader:
|
|
if len(row) == 0 or row[1] == '':
|
|
continue
|
|
runnerarray.append(Runner.from_string(row, self.o_classes))
|
|
self.runners = runnerarray
|
|
|
|
def import_mtr_file(self, mtr_file):
|
|
self.card_dumps = CardDump.list_from_mtr_f(mtr_file)
|
|
|
|
def match_runners_cards(self):
|
|
for r in self.runners:
|
|
for d in self.card_dumps:
|
|
if r.card == d.card:
|
|
r.card_r = d
|
|
def match_runners_o_classes(self):
|
|
for r in self.runners:
|
|
for c in self.o_classes:
|
|
if r.o_class_str == c.name:
|
|
r.o_class = c
|
|
|
|
def match_o_classes_courses(self):
|
|
for oc in self.o_classes:
|
|
for c in self.courses:
|
|
if oc.course_str == c.name:
|
|
oc.course = c
|
|
def match_all(self):
|
|
self.match_runners_cards()
|
|
self.match_runners_o_classes()
|
|
self.match_o_classes_courses()
|
|
|
|
def get_xml_res(self):
|
|
root = ET.Element('ResultList')
|
|
root.set('xmlns', 'http://www.orienteering.org/datastandard/3.0')
|
|
root.set('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
|
|
root.set('iofVersion', '3.0')
|
|
root.set('createTime', datetime.datetime.now().isoformat(timespec='seconds'))
|
|
root.set('creator', 'oTime')
|
|
root.set('status', 'Complete')
|
|
tree = ET.ElementTree(root)
|
|
event = ET.SubElement(root, 'Event')
|
|
xml_child(event, 'Id', self.id)
|
|
xml_child(event, 'Name', self.name)
|
|
for i in self.o_classes:
|
|
# <ClassResult>
|
|
class_result = ET.SubElement(root, 'ClassResult')
|
|
# <Class>
|
|
t = ET.SubElement(class_result, 'Class')
|
|
xml_child(t, 'Name', i.name)
|
|
# <PersonResult>
|
|
runners_same_c = get_runners_in_class(self.runners, i)
|
|
runners_ranked = rank_runners(runners_same_c, i)
|
|
# Put the OK runners first and Active last
|
|
runners_sorted = [i for i in runners_same_c if i not in runners_ranked]
|
|
runners_ranked.extend(runners_sorted)
|
|
|
|
for n in runners_ranked:
|
|
if n.status() == 'Active':
|
|
continue
|
|
person_result = ET.SubElement(class_result, 'PersonResult')
|
|
# <Person>
|
|
person = ET.SubElement(person_result, 'Person')
|
|
xml_child(person, 'Id', n.id)
|
|
# <Name>
|
|
name = ET.SubElement(person, 'Name')
|
|
xml_child(name, 'Family', n.last)
|
|
xml_child(name, 'Given', n.first)
|
|
# </Name>
|
|
# </Person>
|
|
# <Organisation>
|
|
org = ET.SubElement(person_result, 'Organisation')
|
|
xml_child(org, 'Id', n.club_id)
|
|
xml_child(org, 'Name', n.club)
|
|
country = ET.SubElement(org, 'Country')
|
|
# TODO: hent land fra løperobjektet
|
|
country.text = 'Norway'
|
|
country.set('code', 'NOR')
|
|
# </Organisation>
|
|
# <Result>
|
|
result = ET.SubElement(person_result, 'Result')
|
|
|
|
# TODO: Dette bør skrives om til å bruke Runner metoder så mye som mulig.
|
|
if hasattr(n, 'card_r') and len(n.card_r.splits) > 2:
|
|
xml_child(result, 'StartTime', n.card_r.s_time.isoformat())
|
|
xml_child(result, 'FinishTime', n.card_r.f_time.isoformat())
|
|
xml_child(result, 'Time', n.totaltime())
|
|
if n.status() == 'OK':
|
|
# <TimeBehind>
|
|
xml_child(result, 'TimeBehind', n.totaltime() - runners_ranked[0].totaltime())
|
|
# </TimeBehind>
|
|
xml_child(result, 'Position', n.rank(self.runners))
|
|
xml_child(result, 'Status', n.status())
|
|
# <SplitTime>
|
|
# TODO: ta utgangspunkt i løypa, ikke det brikka har stempla
|
|
for code, split in zip(n.card_r.controls, n.card_r.splits):
|
|
st = ET.SubElement(result, 'SplitTime')
|
|
xml_child(st, 'ControlCode', code)
|
|
xml_child(st, 'Time', split)
|
|
if code == n.res_codes()[-1]:
|
|
break
|
|
# </SplitTime>
|
|
elif n.status() == 'Disqualified':
|
|
xml_child(result, 'Status', n.status())
|
|
|
|
controls = copy(n.card_r.controls)
|
|
splits = copy(n.card_r.splits)
|
|
for code, split in zip(n.res_codes(), n.res_splits()):
|
|
st = ET.SubElement(result, 'SplitTime')
|
|
xml_child(st, 'ControlCode', code)
|
|
if split != None:
|
|
xml_child(st, 'Time', split)
|
|
else:
|
|
xml_child(result, 'Status', n.status())
|
|
else:
|
|
xml_child(result, 'Status', n.status())
|
|
# </Result>
|
|
# </PersonResult>
|
|
# </Class>
|
|
ET.indent(root, space=' ', level=0)
|
|
return tree
|
|
|
|
def create_json_file(self):
|
|
rdicts = []
|
|
for runner in self.runners:
|
|
rdicts.append(runner.asdict())
|
|
|
|
cdicts = []
|
|
for course in self.courses:
|
|
cdicts.append(course.asdict())
|
|
|
|
ocdicts = []
|
|
for o_class in self.o_classes:
|
|
ocdicts.append(o_class.asdict())
|
|
|
|
ddicts = []
|
|
for dump in self.card_dumps:
|
|
ddicts.append(dump.asdict())
|
|
|
|
fdicts = []
|
|
for fee in self.fees:
|
|
fdicts.append(fee.asdict())
|
|
|
|
json_data = {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'runners': rdicts,
|
|
'courses': cdicts,
|
|
'o_classes': ocdicts,
|
|
'card_dumps': ddicts,
|
|
'fees': fdicts
|
|
}
|
|
return json.dumps(json_data, sort_keys=True, indent=4)
|
|
|
|
# Get event object from .otime json file
|
|
def from_json(f):
|
|
data = json.load(f)
|
|
runners = []
|
|
for r in data['runners']:
|
|
runners.append(Runner(r['id'], r['first'], r['last'], club_id=r['club_id'], club=r['club'], country=r['country'], card=r['card'], o_class_str=r['o_class_str'], fork=r['fork'],start_time=r['start_time']))
|
|
|
|
courses = []
|
|
for c in data['courses']:
|
|
courses.append(Course(c['name'], c['codes'], forked=c['forked'], variations=c['variations']))
|
|
|
|
o_classes = []
|
|
for c in data['o_classes']:
|
|
o_classes.append(OClass(c['name'], c['course_str'], None))
|
|
|
|
card_dumps = []
|
|
for d in data['card_dumps']:
|
|
card_dumps.append(CardDump(d['card'], d['controls'], d['splits'], datetime.datetime.fromisoformat(d['read_time']), datetime.datetime.fromisoformat(d['s_time']), datetime.datetime.fromisoformat(d['f_time'])))
|
|
|
|
fees = []
|
|
for f in data['fees']:
|
|
fees.append(Fee(f['id'], f['name'], f['currency'], f['amount'], from_birth_date=f['from_birth_date'], to_birth_date=f['to_birth_date']))
|
|
|
|
return Event(data['id'], data['name'], runners=runners, courses=courses, o_classes=o_classes, card_dumps=card_dumps)
|
|
|
|
def create_start_list_pdf(self, file_name):
|
|
pdf = FPDF()
|
|
pdf.add_page()
|
|
pdf.add_font("DejaVuSans", fname="/usr/share/fonts/truetype/DejaVuSans.ttf")
|
|
pdf.set_font("DejaVuSans", size=10)
|
|
line_height = pdf.font_size * 2
|
|
col_width = pdf.epw / 4 # distribute content evenly
|
|
for runner in self.runners:
|
|
pdf.multi_cell(col_width, line_height, runner.fullname(), border=1, ln=3, max_line_height=pdf.font_size, align='L')
|
|
pdf.multi_cell(col_width, line_height, runner.o_class.name, border=1, ln=3, max_line_height=pdf.font_size)
|
|
pdf.multi_cell(col_width, line_height, str(runner.card), border=1, ln=3, max_line_height=pdf.font_size)
|
|
if runner.start_time != None:
|
|
pdf.multi_cell(col_width, line_height, str(runner.start_time), border=1, ln=3, max_line_height=pdf.font_size)
|
|
else:
|
|
pdf.multi_cell(col_width, line_height, '', border=1, ln=3, max_line_height=pdf.font_size)
|
|
pdf.ln(line_height)
|
|
pdf.output(file_name)
|
|
|
|
# The runner object stores all the data specific to a runner.
|
|
class Runner:
|
|
def __init__(self, runner_id, first, last, **kwargs):
|
|
self.id = runner_id
|
|
self.first = first
|
|
self.last = last
|
|
try:
|
|
self.club = kwargs['club']
|
|
except KeyError:
|
|
self.club = None
|
|
|
|
try:
|
|
self.club_id = kwargs['club_id']
|
|
except KeyError:
|
|
self.club_id = None
|
|
|
|
try:
|
|
self.country = kwargs['country']
|
|
except KeyError:
|
|
self.country = None
|
|
|
|
try:
|
|
self.card = kwargs['card']
|
|
except KeyError:
|
|
self.card = 0
|
|
|
|
try:
|
|
self.o_class_str = kwargs['o_class_str']
|
|
except KeyError:
|
|
self.o_class = None
|
|
|
|
try:
|
|
self.o_class = kwargs['o_class']
|
|
except KeyError:
|
|
self.o_class = None
|
|
|
|
try:
|
|
self.fork = kwargs['fork']
|
|
except KeyError:
|
|
self.fork = 0
|
|
|
|
try:
|
|
self.start_time = kwargs['start_time']
|
|
except KeyError:
|
|
self.start_time = None
|
|
|
|
try:
|
|
self.fee_str = kwargs['fee_str']
|
|
except KeyError:
|
|
self.fee_str = None
|
|
|
|
try:
|
|
self.fee = kwargs['fee']
|
|
except KeyError:
|
|
self.fee = None
|
|
|
|
#self.o_class = o_class
|
|
#self.start_time = start_time
|
|
|
|
def from_string(tt_line, o_classes):
|
|
#https://web.archive.org/web/20191229124046/http://wiki.ttime.no/index.php/Developer
|
|
eventorid = tt_line[0]
|
|
country = ''
|
|
name = tt_line[2].split(',')
|
|
try:
|
|
first = name[1].strip()
|
|
except:
|
|
first = ''
|
|
last = name[0]
|
|
try:
|
|
club = tt_line[4]
|
|
except:
|
|
club = "None"
|
|
try:
|
|
card = int(tt_line[6])
|
|
except:
|
|
card = 0
|
|
runner_o_class = None
|
|
try:
|
|
raw_class_str = tt_line[3]
|
|
except:
|
|
# VELDIG MIDLERTIDIG
|
|
runner_o_class = None
|
|
else:
|
|
if raw_class_str != '':
|
|
for i in o_classes:
|
|
if i.name == raw_class_str:
|
|
runner_o_class = i
|
|
break
|
|
else:
|
|
runner_o_class = None
|
|
# TODO: Gjør sånn at den lager nye o klasser om den ikke finnes fra før
|
|
options = tt_line[5].split(',')
|
|
try:
|
|
club_id = options[options.index('A')+3]
|
|
except:
|
|
club_id = 0
|
|
try:
|
|
start_time = options[options.index('U')+1]
|
|
except:
|
|
start_time = None
|
|
return Runner(eventorid, first, last, club=club, club_id=club_id,
|
|
country=country, card=card, o_class_str=raw_class_str,
|
|
o_class=runner_o_class, start_time=start_time)
|
|
|
|
def fullname(self):
|
|
return '{} {}'.format(self.first, self.last)
|
|
|
|
def check_codes(self):
|
|
return contains(self.res_codes(), self.card_r.controls)
|
|
|
|
def totaltime(self):
|
|
f_control = self.res_codes()[-1]
|
|
try:
|
|
index = self.card_r.controls.index(f_control)
|
|
return self.card_r.splits[index]
|
|
except:
|
|
return 0
|
|
|
|
def status(self):
|
|
if hasattr(self, 'card_r') == False or self.o_class == None:
|
|
return 'Active'
|
|
elif self.check_codes():
|
|
return 'OK'
|
|
elif self.check_codes() == False:
|
|
return 'Disqualified'
|
|
|
|
def rank(self, allrunners):
|
|
c_ranked = rank_runners(allrunners, self.o_class)
|
|
return c_ranked.index(self) + 1
|
|
|
|
def res_codes(self):
|
|
if self.o_class.course.forked == False:
|
|
return self.o_class.course.codes
|
|
else:
|
|
return self.o_class.course.variations[self.fork]
|
|
|
|
# TODO: Mange bugs med løyper som har samme post flere ganger
|
|
# Used for making result files and tables
|
|
def res_splits(self):
|
|
if self.status() == 'OK':
|
|
splits_cpy = self.card_r.splits.copy()
|
|
for control in self.card_r.controls:
|
|
if control not in self.res_codes():
|
|
index = self.card_r.controls.index(control)
|
|
split = self.card_r.splits[index]
|
|
splits_cpy.remove(split)
|
|
return splits_cpy
|
|
|
|
else:
|
|
splits_cpy = self.card_r.splits.copy()
|
|
for control in self.card_r.controls:
|
|
if control not in self.res_codes():
|
|
index = self.card_r.controls.index(control)
|
|
split = self.card_r.splits[index]
|
|
splits_cpy.remove(split)
|
|
punches = self.card_r.controls.copy()
|
|
splits = []
|
|
for code in self.res_codes():
|
|
if punches[0] == code:
|
|
splits.append(splits_cpy[0])
|
|
splits_cpy.pop(0)
|
|
punches.pop(0)
|
|
continue
|
|
else:
|
|
splits.append(None)
|
|
return splits
|
|
|
|
def asdict(self):
|
|
return {
|
|
'id': self.id,
|
|
'first': self.first,
|
|
'last': self.last,
|
|
'club_id': self.club_id,
|
|
'club': self.club,
|
|
'country': self.country,
|
|
'card': self.card,
|
|
'o_class_str': self.o_class_str,
|
|
'fork' : self.fork,
|
|
'start_time': self.start_time
|
|
}
|
|
|
|
class CardDump:
|
|
def __init__(self, card, controls, splits, read_time, s_time, f_time):
|
|
self.card = card
|
|
self.controls = controls
|
|
self.splits = splits
|
|
self.read_time = read_time
|
|
self.s_time = s_time
|
|
self.f_time = f_time
|
|
|
|
def __repr__(self):
|
|
return f'card({self.card}) controls({self.controls}) splits({self.splits})'
|
|
|
|
def from_mtr_bytes(datamsg):
|
|
card = int.from_bytes(datamsg[20:23], 'little')
|
|
|
|
#Extract codes and splits:
|
|
controls = []
|
|
splits = []
|
|
splits_offset = 26
|
|
code_numbytes = 1
|
|
time_numbytes = 2
|
|
split_numbytes = code_numbytes + time_numbytes
|
|
for split_index in range(50):
|
|
code_offset = splits_offset + split_index * split_numbytes
|
|
time_offset = code_offset + code_numbytes
|
|
code = int.from_bytes(datamsg[code_offset:code_offset+code_numbytes], 'little')
|
|
time = int.from_bytes(datamsg[time_offset:time_offset+time_numbytes], 'little')
|
|
if code != 0:
|
|
controls.append(code)
|
|
splits.append(time)
|
|
|
|
# Extract start time:
|
|
year = int.from_bytes(datamsg[8:9], 'little')
|
|
month = int.from_bytes(datamsg[9:10], 'little')
|
|
day = int.from_bytes(datamsg[10:11], 'little')
|
|
hours = int.from_bytes(datamsg[11:12], 'little')
|
|
minutes = int.from_bytes(datamsg[12:13], 'little')
|
|
seconds = int.from_bytes(datamsg[13:14], 'little')
|
|
milliseconds = int.from_bytes(datamsg[14:16], 'little')
|
|
|
|
read_time = datetime.datetime(year, month, day, hours, minutes, seconds, milliseconds)
|
|
if len(controls) > 2:
|
|
s_time = read_time - datetime.timedelta(seconds = splits[-1])
|
|
f_time = read_time - (datetime.timedelta(seconds=splits[-1]) + datetime.timedelta(seconds=splits[-2]))
|
|
else:
|
|
s_time = read_time
|
|
f_time = read_time
|
|
|
|
return(CardDump(card, controls, splits, read_time, s_time, f_time))
|
|
|
|
def list_from_mtr_f(mtr_f):
|
|
csvreader = csv.reader(mtr_f)
|
|
|
|
rows = []
|
|
cards = []
|
|
# hver rad er brikkenummer med tilhørende info
|
|
for row in csvreader:
|
|
if len(row) == 0:
|
|
continue
|
|
rows.append(row)
|
|
controls = []
|
|
splits = []
|
|
# postkodene kommer på oddetall fra og med den 11. De blir hevet inn i controls
|
|
for item in row[11::2]:
|
|
if item == '250':
|
|
controls.append(int(item))
|
|
break
|
|
elif item == '000':
|
|
break
|
|
else:
|
|
controls.append(int(item))
|
|
# strekktidene kommer på partall fra og med den 12. De blir hevet i splits.
|
|
for item in row[12::2]:
|
|
if item == '00000':
|
|
break
|
|
else:
|
|
splits.append(int(item))
|
|
# looper gjonnom løperobjektene og legger til poster og strekktider + start og sluttid
|
|
# usikker på om dette er riktig klokeslett
|
|
tl = row[5].split(' ')
|
|
tl[0] = tl[0].split('.')
|
|
tl[0][2] = '20' + tl[0][2]
|
|
tl[0] = list(map(int, tl[0]))
|
|
tl[1] = tl[1].split(':')
|
|
tl[1][2] = float(tl[1][2])
|
|
tl[1] = list(map(int, tl[1]))
|
|
read_time = datetime.datetime(tl[0][2], tl[0][1], tl[0][0], tl[1][0], tl[1][1], tl[1][2])
|
|
if len(controls) > 2 and len(splits) > 2:
|
|
s_time = read_time - datetime.timedelta(seconds = splits[-1])
|
|
f_time = read_time - (datetime.timedelta(seconds = splits[-1]) + datetime.timedelta(seconds = splits[-2]))
|
|
else:
|
|
s_time = read_time
|
|
f_time = read_time
|
|
cards.append(CardDump(int(row[6]), controls, splits, read_time, s_time, f_time))
|
|
return cards
|
|
|
|
def asdict(self):
|
|
return {
|
|
'card': self.card,
|
|
'controls': self.controls,
|
|
'splits': self.splits,
|
|
'read_time': self.read_time.isoformat(),
|
|
's_time': self.s_time.isoformat(),
|
|
'f_time': self.f_time.isoformat()
|
|
}
|
|
|
|
# Stored in Event.courses
|
|
class Course:
|
|
def __init__(self, name, codes, **kwargs):
|
|
self.name = name
|
|
self.codes = codes
|
|
|
|
try:
|
|
self.forked = kwargs['forked']
|
|
except KeyError:
|
|
self.forked = False
|
|
|
|
try:
|
|
self.variations = kwargs['variations']
|
|
except KeyError:
|
|
self.variations = None
|
|
|
|
def __repr__(self):
|
|
return f'name({self.name})'
|
|
|
|
def asdict(self):
|
|
return {
|
|
'name': self.name,
|
|
'codes': self.codes,
|
|
'forked': self.forked,
|
|
'variations': self.variations
|
|
}
|
|
|
|
# Stored in Event.o_classes
|
|
class OClass:
|
|
def __init__(self, name, course_str, course):
|
|
self.name = name
|
|
self.course_str = course_str
|
|
self.course = course
|
|
|
|
def __repr__(self):
|
|
return f'name({self.name})'
|
|
|
|
def asdict(self):
|
|
return {
|
|
'name': self.name,
|
|
'course_str': self.course_str
|
|
}
|
|
|
|
class Fee:
|
|
def __init__(self, fee_id, name, currency, amount, **kwargs):
|
|
self.id = fee_id
|
|
self.name = name
|
|
self.currency = currency
|
|
self.amount = amount
|
|
|
|
try:
|
|
self.from_birth_date = kwargs['from_birth_date']
|
|
except KeyError:
|
|
self.from_birth_date = None
|
|
|
|
try:
|
|
self.to_birth_date = kwargs['to_birth_date']
|
|
except KeyError:
|
|
self.to_birth_date = None
|
|
|
|
def asdict(self):
|
|
return {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'currency': self.currency,
|
|
'amount': self.amount,
|
|
'from_birth_date': self.from_birth_date,
|
|
'to_birth_date': self.to_birth_date
|
|
}
|
|
|
|
# TODO: Take string instead of file.
|
|
def courses_from_ttime_conf(ttime_file):
|
|
courses = []
|
|
conf = ttime_file.readlines()
|
|
for line in conf:
|
|
if '-codes' in line:
|
|
code_list = re.search(r'(?<=\")(.*?)(?=\")', line).group().split(';')
|
|
loops = 0
|
|
for n in code_list:
|
|
n = n.split(',')
|
|
loops += 1
|
|
n = list(map(int, n))
|
|
courses.append(Course('course_'+str(loops), n))
|
|
return courses
|
|
|
|
def classes_from_ttime_conf(ttime_file, courses):
|
|
o_classes = []
|
|
conf = ttime_file.readlines()
|
|
for line in conf:
|
|
if '-courses' in line:
|
|
raw_courselist = re.search(r'(?<=\")(.*?)(?=\")', line).group().split(';')
|
|
loops = 0
|
|
for n in raw_courselist:
|
|
split = n.split(',')
|
|
for i in split:
|
|
o_classes.append(OClass(i, courses[loops].name, courses[loops]))
|
|
loops += 1
|
|
return o_classes
|
|
|
|
def runners_from_xml_entries(xml_file):
|
|
tree = ET.parse(xml_file)
|
|
root = tree.getroot()
|
|
runners = []
|
|
|
|
person_entries = root.findall('./{http://www.orienteering.org/datastandard/3.0}PersonEntry')
|
|
for p_entry in person_entries:
|
|
|
|
rid = p_entry[1][0].text
|
|
|
|
person = p_entry.find('./{http://www.orienteering.org/datastandard/3.0}Person')
|
|
name = person.find('./{http://www.orienteering.org/datastandard/3.0}Name')
|
|
first = name.find('./{http://www.orienteering.org/datastandard/3.0}Given').text
|
|
last = name.find('./{http://www.orienteering.org/datastandard/3.0}Family').text
|
|
|
|
organisation = p_entry.find('./{http://www.orienteering.org/datastandard/3.0}Organisation')
|
|
club_id = organisation.find('./{http://www.orienteering.org/datastandard/3.0}Id').text
|
|
club_name = organisation.find('./{http://www.orienteering.org/datastandard/3.0}Name').text
|
|
club_name_short = organisation.find('./{http://www.orienteering.org/datastandard/3.0}ShortName').text
|
|
country = organisation.find('./{http://www.orienteering.org/datastandard/3.0}Country').attrib['code']
|
|
|
|
class_el = p_entry.find('./{http://www.orienteering.org/datastandard/3.0}Class')
|
|
class_str = class_el.find('./{http://www.orienteering.org/datastandard/3.0}Name').text
|
|
|
|
try:
|
|
card = int(p_entry.find('./{http://www.orienteering.org/datastandard/3.0}ControlCard').text)
|
|
except AttributeError:
|
|
card = None
|
|
|
|
start_time = None
|
|
runners.append(Runner(rid, first, last, club=club_name, club_id=club_id,
|
|
country=country,card=card, o_class_str=class_str,
|
|
start_time=start_time))
|
|
return runners
|
|
|
|
def fees_from_xml_entries(xml_file):
|
|
tree = ET.parse(xml_file)
|
|
root = tree.getroot()
|
|
|
|
allfees = root.findall('.//{http://www.orienteering.org/datastandard/3.0}Fee')
|
|
added_ids = []
|
|
fee_objs = []
|
|
for fee in allfees:
|
|
f_id = fee.find('./{http://www.orienteering.org/datastandard/3.0}Id').text
|
|
if f_id not in added_ids:
|
|
added_ids.append(f_id)
|
|
|
|
fee_id = f_id
|
|
name = fee.find('./{http://www.orienteering.org/datastandard/3.0}Name').text
|
|
currency = fee.find('./{http://www.orienteering.org/datastandard/3.0}Amount').attrib['currency']
|
|
amount = fee.find('./{http://www.orienteering.org/datastandard/3.0}Amount').text
|
|
try:
|
|
from_birth_date = fee.find('./{http://www.orienteering.org/datastandard/3.0}FromDateOfBirth').text
|
|
except AttributeError:
|
|
from_birth_date = None
|
|
try:
|
|
to_birth_date = fee.find('./{http://www.orienteering.org/datastandard/3.0}ToDateOfBirth').text
|
|
except AttributeError:
|
|
to_birth_date = None
|
|
|
|
fee_objs.append(Fee(fee_id, name, currency, amount, from_birth_date=from_birth_date, to_birth_date=to_birth_date))
|
|
return fee_objs
|
|
|
|
# Checks if small list is in big list
|
|
def contains(small, big):
|
|
valid = True
|
|
mark = 0
|
|
map_bl = []
|
|
for i in small:
|
|
for n,control in enumerate(big[mark:]):
|
|
if i == control:
|
|
mark += n
|
|
map_bl.append(mark)
|
|
break
|
|
else:
|
|
valid = False
|
|
if valid:
|
|
return map_bl
|
|
else:
|
|
return False
|
|
|
|
def get_runners_in_class(runners, o_class):
|
|
# Filters out runner objects that dont have the correct o_class
|
|
list_filtrd = []
|
|
for i in runners:
|
|
marker = 0
|
|
if i.o_class == o_class:
|
|
list_filtrd.append(i)
|
|
return list_filtrd
|
|
|
|
def rank_runners(allrunners, o_class):
|
|
runners = get_runners_in_class(allrunners, o_class)
|
|
runners_ranked = []
|
|
for i in runners:
|
|
if i.status() == 'OK':
|
|
runners_ranked.append(i)
|
|
runners_ranked.sort(key=lambda x: x.totaltime())
|
|
return runners_ranked
|
|
|
|
# Used to make creating xml files easier
|
|
def xml_child(parent, tag, content):
|
|
e = ET.SubElement(parent, tag)
|
|
e.text = str(content)
|