diff --git a/.gitignore b/.gitignore
index 188fd02..709c4bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
/venv/Lib/site-packages/
/Classes/gibberish.py
/venv/Lib/site-packages/
/Classes/gibberish.py
/Classes/notevengibberish.py
/Classes/number3.py
+*.pyc
diff --git a/Classes/AppTourneydraw.py b/Classes/AppTourneydraw.py
new file mode 100644
index 0000000..a3aac99
--- /dev/null
+++ b/Classes/AppTourneydraw.py
@@ -0,0 +1,225 @@
+# Name: Tournament draw generator
+# Author: Somnifuguist (w.wiki/fDy)
+# Date: 10-10-2020
+# Content: Generates wikitext for tennis tournament draws
+
+from ClassSupportFunctions import *
+from GetFlagicon import GetFlagicon
+
+import itertools
+from nameparser import HumanName
+from num2words import num2words
+import re
+
+def GetNameLink(name):
+ # Finds and returns formatted name and wikilinks for given name.
+ name = HumanName(name)
+ name.capitalize(force=True)
+ name = str(name)
+ soup = GetSoup("https://en.wikipedia.org/wiki/" + name.replace(" ", "_"), False).text
+ wikitext = name
+ tennis = ["International Tennis Federation", "Prize money", "Grand Slam", "tennis career", "Wikipedia does not have", "may refer to", "WTA", "ITF", "ATP"]
+ pipe = False
+ if soup != None:
+ if any([f in soup for f in tennis]): # player article exists, or no article exists
+ if "Redirected from" in soup:
+ soup = GetSoup(soup, True)
+ title = str(soup.title.string).replace(" - Wikipedia", "").strip()
+ wikitext = title
+ pipe = True # if name is redirect, pipes wikilink to avoid anachronist names, e.g. using "Margaret Court" instead of "Margaret Smith" before she married.
+ else: # article exists for name but for different person
+ wikitext = name + " (tennis)"
+ pipe = True
+ wikilink = "[[" + wikitext + ("|" + name if pipe else "") + "]]"
+ split_name = name.split(" ")
+ abbr_name = "-".join(f[0] for f in split_name[0].split("-")) + " " + " ".join(split_name[1:]) # reduce name to first name initials + last name, e.g. "J-L Struff"
+ abbr_wikilink = "[[" + wikitext + "|" + abbr_name + "]]"
+ return [name, wikilink, abbr_wikilink]
+
+class Page():
+ def __init__(self):
+ self.text = [] # contains the draw wikitext
+
+class Player():
+ def __init__(self, player):
+ self.name = player[0]
+ self.country = player[1]
+ self.seed = player[2]
+
+class Match():
+ def __init__(self, match, sets):
+ self.parsed = False # match has been checked for retirements, tiebreakers etc.
+ self.teams = [[Player(f) for f in match[0]], [Player(f) for f in match[1]]]
+ self.score = match[2] + [["",""]] * (sets - len(match[2]) + 1) # add empty sets to matches where the full number of sets wasn't needed
+ self.winner = self.score[0][0]
+ if self.score[0][1] == ["w/o"]:
+ self.score[1] = ["", "w/o"] if self.winner else ["w/o", ""] # puts w/o on winner's side
+ self.bye = ["BYE"] in self.score
+ self.sets = sets
+
+class Tournament():
+ def __init__(t, data, format, qual, year):
+ t.data = []
+ t.match_tiebreak = format == 2 # deciding set tiebreak in e.g. 2001 Aus Open Mixed Doubles
+ format = 3 if format == 2 else format
+ for c, round in enumerate(data):
+ sets = 3 if format == 3 or (format == 35 and c < len(data) - 1) or (format == 355 and c < len(data) - 2) else 5 # doesn't handle sets formats like 5353.
+ t.data.append([Match(match, sets) for match in round])
+ t.rounds = len(t.data)
+ t.format = format # number of sets in matches, 35 if final has 5 sets
+ t.doubles = len(data[0][0][0]) == 2
+ t.qual = qual
+ t.byes = [["BYE"] in match[2][1] for match in data[0]]
+ round_names = ["First Round", "Second Round", "Third Round", "Fourth Round", "Fifth Round", "Quarterfinals", "Semifinals", "Final", "Champion" + ("s" if t.doubles else "")]
+ t.round_names = round_names[:t.rounds-1] + ["Qualifying Competition", "Qualified"] if t.qual else round_names[:t.rounds-3] + round_names[-4:] # sometimes called "Qualifying Round"
+
+ # Get dicts for players' wikilinks, and year-accurate flagicons
+ t.name_links = LoadJSON("name_links.json") # database of all name links already looked up
+ t.flagicons = {}
+ teams = [[f for f in match.teams[0]] + [f for f in match.teams[1]] for match in t.data[0]]
+ players = list(itertools.chain.from_iterable(teams)) # list of players in first round i.e. all players
+ for player in players:
+ if player.name not in t.name_links:
+ t.name_links[player.name] = GetNameLink(player.name)
+ if player.country not in t.flagicons:
+ t.flagicons[player.country] = GetFlagicon(player.country, year)
+ SaveJSON("name_links.json", t.name_links)
+ t.lucky_losers = []
+
+ def SplitData(t, n, r):
+ # Splits first r rounds of data into n equal sections.
+ SplitData = []
+ for i in range(n):
+ part = []
+ for j in range(r):
+ matches = int(len(t.data[j])/n)
+ part.append(t.data[j][i*matches:(i+1)*matches])
+ SplitData.append(part)
+ return SplitData
+
+ def MakeSeeds(t, p, sections, seed_links):
+ # Generates and wikitext for seed section of draw article.
+ # If seed_links, link each seed's number to the player's section in draw using {{seeds}}/{{qseeds}} templates.
+ page = []
+ seeds = {} # [team, round reached, final match retired/withdrew]
+ for c, round in enumerate(t.data): # find and add all seeds and their results to seeds dict
+ for match in round:
+ retiredwithdrew = "" if match.bye or str(match.score[0][1][-1]).isdigit() else match.score[0][1][-1]
+ for i in range(2):
+ seed = match.teams[i][0].seed
+ if seed != []:
+ if seed[0].isdigit():
+ seeds[int(seed[0])] = [match.teams[i], (c if i != match.winner else c + 1), retiredwithdrew]
+ def FindSection(sections, seed):
+ # Finds section of draw containing given seed.
+ for c, section in enumerate(sections):
+ for round in section:
+ for match in round:
+ if (match.teams[0][0].seed != [] and match.teams[0][0].seed[0] == str(seed)) or (match.teams[1][0].seed != [] and match.teams[1][0].seed[0] == str(seed)):
+ return str(c + 1)
+ return "0"
+
+ if seeds != {}:
+ page += ["", "==Seeds==", "{{columns-list|colwidth=30em|"]
+ for l in range(1, max(seeds) + 1):
+ number = "# "
+ if seed_links and len(sections) >= 4:
+ section = "{{seeds" + ("q" if t.qual else "") + "|" + str(l) + "|" + FindSection(sections, l) + "}}"
+ number = (section if section != 0 else "# ")
+ else:
+ seed_links = False
+ try: # create text for seed, formatting (bold/italics) depending on result
+ style = ("'''" if seeds[l][1] == t.rounds else "''")
+ reached = t.round_names[seeds[l][1]].replace("Round", "round").replace("Competition", "competition") # upper-case is used in draw templates but not seeds
+ lucky_loser = ", '''''Lucky loser'''''" if seeds[l][0] in [ll[0] for ll in t.lucky_losers] else ""
+ retiredwithdrew = ", retired" if seeds[l][2] == "r" else (", withdrew" if seeds[l][2] == "w/o" else "")
+ name_text = " / ".join([t.flagicons[f.country] + " " + t.name_links[f.name][1] for f in seeds[l][0]])
+ page += [number + (style if style == "'''" else "") + name_text + " " + (style if style == "''" else "") + "(" + reached + retiredwithdrew + lucky_loser + ")" + style]
+ except KeyError: # seed not in draw, usually due to withdrawal
+ page += [number + "''(Withdrew)''"]
+ page += ["}}"] + (["", "{{Seeds explanation}}"] if seed_links else [])
+ p.text = page + p.text
+
+ def MakeQualifiers(t, p):
+ # Generates wikitext for qualifiers.
+ p.text += ["", "==Qualifiers==", "{{columns-list|colwidth=30em|"]
+ for match in t.data[-1]:
+ winner = match.winner
+ p.text += ["# " + " / ".join([t.flagicons[f.country] + " '''" + t.name_links[f.name][1] + "'''" for f in match.teams[winner]])]
+ p.text += ["}}"]
+
+ def MakeLuckyLosers(t, p):
+ # Generates wikitext for lucky losers; not currently used as t.lucky_losers is always empty.
+ if t.lucky_losers != []:
+ p.text += ["", "==Lucky losers==", "{{columns-list|colwidth=30em|"]
+ for ll in t.lucky_losers:
+ p.text += ["# " + t.flagicons[ll[0]] + " '''" + t.name_links[ll[0]][1] + "'''"]
+ p.text += ["}}"]
+
+ def MakeSection(t, p, data, rounds, round_names, format, byes, compact, short_names):
+ # Generates wikitext for section of draw, using standard templates like {{16TeamBracket-Tennis3}}.
+ # Doesn't accommodate for "|byes=1" added with https://en.wikipedia.org/wiki/Wikipedia:Templates_for_discussion/Log/2017_May_10#Template:16TeamBracket-Compact-Tennis35-Byes.
+ p.text += ["{{" + str(2**rounds) + "TeamBracket" + ("-Compact" if compact else "") + "-Tennis" + str(format) + ("-Byes" if byes else "")]
+ for c, round in enumerate(round_names):
+ p.text += ["| RD" + str(c+1) + "=" + round]
+ for j in range(rounds):
+ team_no = 1
+ for match in data[j]:
+ p.text += [""]
+ if not match.parsed: # add sup tags if not already added for retirements/tiebreakers
+ for c, set in enumerate(match.score[1:]):
+ if set[-1] == "Retired":
+ match.score[c+1][not match.score[0][0]] += "r"
+ elif set != ["", ""] and "w/o" not in set and len(set) == 3 and len(set[2]) == 2:
+ match.score[c+1][0] = set[0] + "" + set[2][0] + ""
+ match.score[c+1][1] = set[1] + "" + set[2][1] + ""
+ match.parsed = True
+
+ for i in range(2): # add seed, team name/flag, score parameters for each team in given match
+ team = match.teams[i]
+ bold = "'''" if match.winner == i else ""
+ name_text = "
".join([(t.flagicons[f.country] + " " + (bold + t.name_links[f.name][1 + short_names] + bold) if not match.bye else "") for f in team])
+ rd = "| RD" + str(j+1) + "-"
+ p.text += [rd + "seed" + str(team_no) + "=" + ("" if match.bye else (" " if team[0].seed == [] else "/".join(team[0].seed)))]
+ p.text += [rd + "team" + str(team_no) + "=" + (name_text if name_text != "
" else "")]
+
+ for set in range(match.sets):
+ p_score = "" if match.bye else match.score[set+1][i]
+ won_set = (i == match.score[0][1][set]) if set < len(match.score[0][1]) else 0 # no sets in byes/walkovers
+ p_score = "[" + p_score + "]" if t.match_tiebreak and set == 2 and p_score != "" else p_score # add square brackets for match tiebreak
+ p.text += [rd + "score" + str(team_no) + "-" + str(set+1) + "=" + ("'''" + p_score + "'''" if won_set else p_score)]
+ team_no += 1
+ p.text += ["}}"]
+
+ def MakeDraw(t, p, compact, abbr, seed_links):
+ if t.qual:
+ t.MakeQualifiers(p)
+ t.MakeLuckyLosers(p)
+ p.text += ["", "==Qualifying draw==", "{{Draw key}}"]
+ sections = t.SplitData(len(t.data[-1]), t.rounds)
+ for i in range(len(sections)):
+ ordinal = num2words(i+1, ordinal=True).capitalize()
+ p.text += ["", "===" + ordinal + " qualifier==="] # add section heading before each section
+ t.MakeSection(p, data=sections[i], rounds=t.rounds, round_names=t.round_names[:-1], format=t.format, byes=t.byes, compact=compact, short_names=abbr)
+ else:
+ p.text += ["", "==Draw==", "{{Draw key}}"]
+ parts = int((2**t.rounds)/16) # number of sections of 16 players needed
+ if parts > 1: # "Finals" section needed
+ p.text += ["===Finals==="]
+ final_rounds = {5:2, 6:3, 7:3, 8:4} # number of rounds to show in "Finals" section given the number of rounds in the tournament
+ final_rounds = final_rounds[t.rounds]
+ t.MakeSection(p, data=t.data[-final_rounds:], rounds=final_rounds, round_names=t.round_names[-final_rounds - 1:-1], format=t.format, byes=False, compact=False, short_names=False)
+ sections = t.SplitData(parts, 4)
+ halves = [["===Top half==="], ["===Bottom half==="]]
+ for c, section in enumerate(sections):
+ if parts > 1:
+ p.text += halves[c // (parts // 2)] if c % (parts // 2) == 0 else [] # add half heading before each half
+ p.text += ["====Section " + str(c+1) + "===="] if t.rounds > 5 else [] # add section heading before each section
+ t.MakeSection(p, data=section, rounds=4, round_names=t.round_names[:4], format=(3 if t.format > 5 else t.format), byes=t.byes, compact=compact, short_names=abbr)
+ t.MakeSeeds(p, sections, seed_links)
+
+def TournamentDrawOutput(data, year, format, qual, compact, abbr, seed_links):
+ p = Page()
+ t = Tournament(data=data, format=format, qual=qual, year=year)
+ t.MakeDraw(p, compact=compact, abbr=True, seed_links=seed_links)
+ return "\n".join(p.text)
diff --git a/Classes/ClassSupportFunctions.py b/Classes/ClassSupportFunctions.py
index 8d6761b..40ced09 100644
--- a/Classes/ClassSupportFunctions.py
+++ b/Classes/ClassSupportFunctions.py
@@ -1,5 +1,33 @@
import os
+import json
+import requests
+from bs4 import BeautifulSoup
def GetCountrycode(filepath):
filename = os.path.basename(filepath)
- return filename[0:3].upper()
\ No newline at end of file
+ return filename[0:3].upper()
+
+def GetSoup(path, headers):
+ if headers == True: # want soup for html in path
+ return BeautifulSoup(path, "html.parser")
+ elif headers == False: # want request only
+ return requests.get(path)
+ elif headers == {}:
+ response = requests.get(path).text
+ else:
+ response = requests.get(path, headers=headers).text
+ return BeautifulSoup(response, 'lxml')
+
+def LoadJSON(path):
+ try:
+ with open(path, 'r') as fp:
+ return json.load(fp)
+ except FileNotFoundError:
+ return {}
+
+def SaveJSON(path, data):
+ with open(path, 'w') as fp:
+ json.dump(data, fp, sort_keys=True, indent=4)
+
+def Dedupe(x):
+ return list(dict.fromkeys(x))
diff --git a/Classes/GetFlagicon.py b/Classes/GetFlagicon.py
new file mode 100644
index 0000000..4d849a5
--- /dev/null
+++ b/Classes/GetFlagicon.py
@@ -0,0 +1,99 @@
+# Adapted from https://en.wikipedia.org/wiki/User:Dantheox/flags.rb
+
+def GetFlagicon(nation, year):
+ if nation == "HOL":
+ return "{{flagicon|NED}}"
+ elif nation == "APA":
+ return "{{flagicon|Czechoslovakia}}"
+ elif nation == "AUT" and year <= 1918:
+ return "{{flagicon|AUT|empire}}"
+ elif nation == "BIH" and year <= 1998:
+ return "{{flagicon|BIH|1992}}"
+ elif nation == "BUL" and year <= 1971:
+ return "{{flagicon|BUL|1967}}"
+ elif nation == "BUL" and year <= 1990:
+ return "{{flagicon|BUL|1971}}"
+ elif nation == "CAN" and year <= 1920:
+ return "{{flagicon|CAN|1868}}"
+ elif nation == "CAN" and year <= 1964:
+ return "{{flagicon|CAN|1921}}"
+ elif nation == "CHN" and year <= 1949:
+ return "{{flagicon|Republic of China|1912}}"
+ elif nation == "EGY" and year < 1922:
+ return "{{flagicon|EGY|1882}}"
+ elif nation == "EGY" and year <= 1952:
+ return "{{flagicon|EGY|1922}}"
+ elif nation == "EGY" and year <= 1972:
+ return "{{flagicon|EGY|1952}}"
+ elif nation == "ESP" and year < 1931:
+ return "{{flagicon|ESP|1785}}"
+ elif nation == "ESP" and year < 1939:
+ return "{{flagicon|ESP|1931}}"
+ elif nation == "ESP" and year < 1977:
+ return "{{flagicon|ESP|1939}}"
+ elif nation == "ESP" and year < 1981:
+ return "{{flagicon|ESP|1977}}"
+ elif nation == "GEO" and year <= 2004:
+ return "{{flagicon|GEO|1990}}"
+ elif nation == "GER" and year <= 1918:
+ return "{{flagicon|GER|empire}}"
+ elif nation == "GER" and year <= 1934:
+ return "{{flagicon|GER|Weimar}}"
+ elif nation == "GER" and year <= 1945:
+ return "{{flagicon|GER|Nazi}}"
+ elif nation == "GRE" and year <= 1969:
+ return "{{flagicon|GRE|old}}"
+ elif nation == "HKG" and year <= 1997:
+ return "{{flagicon|HKG|colonial}}"
+ elif nation == "HUN" and year < 1940:
+ return "{{flagicon|HUN|1867}}"
+ elif nation == "HUN" and year <= 1945:
+ return "{{flagicon|HUN|1920}}"
+ elif nation == "HUN" and year <= 1956:
+ return "{{flagicon|HUN|1949}}"
+ elif nation == "IND" and year <= 1946:
+ return "{{flagicon|IND|British}}"
+ elif nation == "IRI" and year <= 1963:
+ return "{{flagicon|IRI|1925}}"
+ elif nation == "IRI" and year <= 1979:
+ return "{{flagicon|IRI|1964}}"
+ elif nation == "IRQ" and year < 1959:
+ return "{{flagicon|IRQ|1924}}"
+ elif nation == "IRQ" and year < 1963:
+ return "{{flagicon|IRQ|1959}}"
+ elif nation == "IRQ" and year < 1991:
+ return "{{flagicon|IRQ|1963}}"
+ elif nation == "IRQ" and year < 2003:
+ return "{{flagicon|IRQ|1991}}"
+ elif nation == "ITA" and year <= 1946:
+ return "{{flagicon|ITA|1861}}"
+ elif nation == "MAS" and year < 1948:
+ return "{{flagicon|MAS|1895}}"
+ elif nation == "MAS" and year <= 1963:
+ return "{{flagicon|MAS|1948}}"
+ elif nation == "MEX" and year <= 1933:
+ return "{{flagicon|MEX|1916}}"
+ elif nation == "MEX" and year <= 1968:
+ return "{{flagicon|MEX|1934}}"
+ elif nation == "RHO" and year <= 1967:
+ return "{{flagicon|Rhodesia|1964}}"
+ elif nation == "ROM" and year <= 1947:
+ return "{{flagicon|ROM|1867}}"
+ elif nation == "ROM" and year <= 1989:
+ return "{{flagicon|ROM|1947}}"
+ elif nation == "RSA" and year <= 1927:
+ return "{{flagicon|RSA|1910}}"
+ elif nation == "RSA" and year <= 1994:
+ return "{{flagicon|RSA|1928}}"
+ elif nation == "SRI" and year <= 1971:
+ return "{{flagicon|SRI}}"
+ elif nation == "VIE" and year <= 1974:
+ return "{{flagicon|South Vietnam}}"
+ elif nation == "YUG" and year <= 1941:
+ return "{{flagicon|Kingdom of Yugoslavia}}"
+ elif nation == "YUG" and year <= 2002:
+ return "{{flagicon|FR Yugoslavia}}"
+ elif nation == "" or nation == "ITF" or nation == "LAG":
+ return ""
+ else:
+ return "{{flagicon|" + nation + "}}"
diff --git a/Classes/ScrapeTournamentITF.py b/Classes/ScrapeTournamentITF.py
new file mode 100644
index 0000000..35fb3d6
--- /dev/null
+++ b/Classes/ScrapeTournamentITF.py
@@ -0,0 +1,148 @@
+# Name: ITF Tournament scraper
+# Author: Somnifuguist (w.wiki/fDy)
+# Date: 10-10-2020
+# Content: Scrapes data from ITF printable draws
+
+import itertools
+import re
+
+from ClassSupportFunctions import GetSoup
+
+def ExtractTeam(team):
+ # Extracts [name, country code, [seeds]] for player(s) in team from string.
+ team = team.text.strip(" \n").replace("\xa0", " ")
+ players = team.split(" / ")
+ team_data = []
+ for player in players:
+ if player == "":
+ team_data.append(["","",[]])
+ elif player == "BYE":
+ team_data.append(["BYE", "", []])
+ else:
+ country = re.search(r"\([A-Z]{3}\)", player).group()[1:4]
+ seed = []
+ digit = re.search(r"\[\d{1,2}\]", player)
+ if digit:
+ seed.append(digit.group()[1:-1])
+ types = ["LL", "WC", "Q", "PR", "Alt", "SR", "A"]
+ for t in types:
+ if re.search(r"\(" + t + "\)", player):
+ seed.append(t)
+ name = (player.replace("(" + country + ")", "").replace("(" + (seed[-1] if seed != [] else "") + ")", "").replace("[" + (seed[0] if seed != [] else "") + "]", "").strip(" ").replace(",", ""))
+ team_data.append([name, country, seed])
+ return team_data
+
+def ExtractScore(score, match, winners):
+ # Extracts [[winner, [set winners]], [set 1 scores], [set 2 scores], ...] from string
+ # Set scores = [player 0 score, player 1 score, [tiebreak scores if tiebreak]/"Retired" if retirement]
+ # Player and scores ordered by order in draw.
+ # e.g. the score 6-4 5-7 7-6(5) 6-0, with player 1 as winner = [[1, [1, 0, 1, 1]], [4, 6], [7, 5], [6, 7, [5, 7]], [0, 6]]
+ # Walkovers = [[winner, ["w/o"]], ["Walkover"]]
+ # Byes = [[winner, []], ["BYE"]]
+ winner = 0 if match[0][0][0] in winners and match[0][0][0] != "BYE" else 1
+ score = score.text.strip(" |\n")
+ score = [s.split("-") for s in score.split(" ")]
+
+ if match[0][0][0] == "BYE" or match[1][0][0] == "BYE":
+ new_score = [[winner, []], ["BYE"]]
+ elif score[0][0] == "Walkover":
+ new_score = [[winner, ["w/o"]], ["Walkover"]]
+ else:
+ new_score = [[winner, []]]
+ for set in score:
+ set = [f.replace("[", "").replace("]", "") for f in set]
+ if set == ["Retired"]:
+ if max(int(new_score[-1][0]), int(new_score[-1][1])) > 5 and abs(int(new_score[-1][0]) - int(new_score[-1][1])) > 1:
+ new_score.append(['0', '0', "Retired"]) # retirement happened after set finished
+ else:
+ new_score[-1] += set # retirement happened mid-set
+ elif set != [""]:
+ tiebreaker = re.search(r"\(\d{1,}\)", set[1])
+ if tiebreaker:
+ tie_l = int(tiebreaker.group()[1:-1])
+ tie_w = tie_l + 2 if tie_l > 4 else 7
+ if set[0] == '7':
+ set = ['7', '6', [str(tie_w), str(tie_l)]]
+ else:
+ set = ['6', '7', [str(tie_l), str(tie_w)]]
+ new_score.append(set)
+
+ if winner == 1: # reverse score order, as scores are always ordered with winner first
+ for c, f in enumerate(new_score[1:]):
+ if "Retired" in f:
+ new_score[c+1] = f[:-1][::-1] + [f[-1]]
+ elif len(f) == 3 and len(f[2]) == 2: # tiebreak
+ new_score[c+1] = f[:-1][::-1] + [f[-1][::-1]]
+ else:
+ f.reverse()
+ for set in new_score[1:]: # fill new_score[0][1] with winner of each set, "r" if team retires
+ if 'Retired' in set:
+ new_score[0][1].append("r")
+ else:
+ new_score[0][1].append(int(int(set[0])',
+ '
',
+ '
',
+ '
']
+ bye = ['BYE
',
+ 'BYE
',
+ 'BYE
',
+ 'BYE
']
+ html = ">".join([f.strip("\t\n ") for f in str(soup).split(">")])
+ for i in range(4):
+ html = html.replace(empty[i], bye[i])
+ soup = GetSoup(html, True)
+ return soup
+
+def ScrapeTournamentITF(url, qual, doubles):
+ soup = GetSoup(url, {})
+ try:
+ data = ExtractTournament(soup, qual=qual, doubles=doubles)
+ except IndexError: # html is missing name in tournament bracket
+ soup = FixByes(soup)
+ data = ExtractTournament(soup, qual=qual, doubles=doubles)
+ return data
diff --git a/Classes/app.py b/Classes/app.py
index b2e00fb..4fe6a32 100644
--- a/Classes/app.py
+++ b/Classes/app.py
@@ -1,79 +1,111 @@
from flask import Flask, render_template, request, redirect, url_for
from Settings import Config
from AppPlayerTourneywins import TournamentWinsOutput, GetTournamentWins
from AppPlayerWorldranking import GetWorldRanking, RankingOutput
-from forms import FormPlayerWorldranking, FormPlayerTournamentwins
+from AppTourneydraw import TournamentDrawOutput
+from ScrapeTournamentITF import ScrapeTournamentITF
+from forms import FormPlayerWorldranking, FormPlayerTournamentwins, FormTournamentdraw
#Initiate Flask with config
app = Flask(__name__)
app.config.from_object(Config)
@app.route('/')
def home():
return render_template('about.html')
@app.route('/about/')
def about():
return render_template('about.html')
@app.route('/playerworldranking/', methods=['GET', 'POST'])
def playerworldranking():
form = FormPlayerWorldranking()
if request.method == 'POST':
org = request.form.get('org')
type = request.form.get('type')
cut = request.form.get('cut')
language = request.form.get('language')
date = request.form.get('date')
return redirect(url_for('outputranking', org=org, type=type, cut=cut, language=language, date=date))
return render_template('playerworldranking.html', form=form)
@app.route('/playerwins/', methods=['GET', 'POST'])
def playerwins():
form = FormPlayerTournamentwins()
if request.method == 'POST':
org = request.form.get('org')
type = request.form.get('type')
player = request.form.get('player')
language = request.form.get('language')
level = request.form.get('level')
return redirect(url_for('outputplayerwins', org=org, type=type, player=player, language=language, level=level))
return render_template('playerwins.html', form=form)
-@app.route('/tourneydraw/')
+@app.route('/tourneydraw/', methods=['GET', 'POST'])
def tourneydraw():
- #org = request.args.get('org', default = 1, type = str)
- #type = request.args.get('type', default = '*', type = str)
- #year = request.args.get('cut', default = '*', type = str)
- #language = request.args.get('language', default = '*', type = str)
- return render_template('tourneydraw.html')
+ form = FormTournamentdraw()
+ if request.method == 'POST':
+ language = request.form.get('language')
+ org = request.form.get('org')
+ url = request.form.get('url')
+ year = request.form.get('year')
+ doubles = request.form.get('doubles')
+ format = request.form.get('format')
+ qual = request.form.get('qual')
+ compact = request.form.get('compact')
+ abbr = request.form.get('abbr')
+ seed_links = request.form.get('seed_links')
+ return redirect(url_for('outputtourneydraw', org=org, url=url, year=year, doubles=doubles, format=format, qual=qual, compact=compact, abbr=abbr, seed_links=seed_links))
+ return render_template('tourneydraw.html', form=form)
@app.route('/outputranking/', methods=['GET', 'POST'])
def outputranking():
#Get variables from form
org = request.args.get('org', type = str)
type = request.args.get('type', type = str)
cut = request.args.get('cut', default='100', type = int)
language = request.args.get('language', type = str)
date = request.args.get('date', type = str)
#Run GetWorldRanking()
ranking = GetWorldRanking(org, type, cut, language, date)
result = RankingOutput(ranking, org, type, cut, language, date)
return render_template('outputranking.html', result=result)
@app.route('/outputplayerwins/', methods=['GET', 'POST'])
def outputplayerwins():
#Get variables from form
org = request.args.get('org', type = str)
type = request.args.get('type', type = str)
player = request.args.get('player', type = str)
language = request.args.get('language', type = str)
level = request.args.get('level', type = int)
#Run GetTournamentWins()
wins = GetTournamentWins(org, player, type, level)
result = TournamentWinsOutput(wins, language, type)
return render_template('outputplayerwins.html', result=result)
+@app.route('/outputtourneydraw/', methods=['GET', 'POST'])
+def outputtourneydraw():
+ #Get variables from form
+ language = request.args.get('language', type = int)
+ org = request.args.get('org', type = str)
+ url = request.args.get('url', type = str)
+ year = request.args.get('year', type = int)
+ doubles = request.args.get('doubles', type = int)
+ format = request.args.get('format', type = int)
+ qual = request.args.get('qual', type = int)
+ compact = request.args.get('compact', type = int)
+ abbr = request.args.get('abbr', type = int)
+ seed_links = request.args.get('seed_links', type = int)
+ if "https://event.itftennis.com/itf/web/usercontrols/tournaments/tournamentprintabledrawsheets.aspx?" in url:
+ # Scrape data, then create draw
+ data = ScrapeTournamentITF(url=url, qual=qual, doubles=doubles)
+ draw = TournamentDrawOutput(data=data, year=year, format=format, qual=qual, compact=compact, abbr=abbr, seed_links=seed_links)
+ else: # extremely basic input validation
+ draw = "Invalid URL, should be in format: https://event.itftennis.com/itf/web/usercontrols/tournaments/tournamentprintabledrawsheets.aspx?"
+ return render_template('outputtourneydraw.html', result=draw)
+
if __name__ == '__main__':
- app.run(debug=True)
\ No newline at end of file
+ app.run(debug=True)
diff --git a/Classes/forms.py b/Classes/forms.py
index a9ca978..2f65779 100644
--- a/Classes/forms.py
+++ b/Classes/forms.py
@@ -1,24 +1,37 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, SelectField, IntegerField
from wtforms.validators import DataRequired
class FormPlayerWorldranking(FlaskForm):
org = SelectField('Ranking Organisation', choices=[('atp', 'ATP'), ('wta', 'WTA')])
type = SelectField('Match Type', choices=[('singles', 'singles'), ('doubles', 'doubles')])
language = SelectField('Wikipedia Language', choices=[('en', 'en'), ('de', 'de'), ('cs', 'cs'), ('es', 'es'),
('fr', 'fr'), ('it', 'it'), ('ja', 'ja'), ('nl', 'nl'),
('pl', 'pl'), ('pt', 'pt'), ('ro', 'ro'), ('ru', 'ru'),
('sv', 'sv'), ('zh', 'zh')])
cut = IntegerField('Ranking Cut', validators=[DataRequired()])
date = StringField('Date', validators=[DataRequired()])
submit = SubmitField('Request')
class FormPlayerTournamentwins(FlaskForm):
org = SelectField('Ranking Organisation', choices=[('atp', 'ATP')])
type = SelectField('Match Type', choices=[('singles', 'singles'), ('doubles', 'doubles')])
player = StringField('Player-ID', validators=[DataRequired()])
language = SelectField('Wikipedia Language', choices=[('de', 'de'), ('nl', 'nl')])
level = SelectField('Minimum Tournament Level', choices=[(1, 'ITF Futures'), (2, 'ATP Challenger'), (3, 'ATP World Tour 250 & 500'),
(4, 'ATP World Tour 1000'), (5, 'ATP Tour Finals & Grand Slams & Olympics')])
- submit = SubmitField('Request')
\ No newline at end of file
+ submit = SubmitField('Request')
+
+class FormTournamentdraw(FlaskForm):
+ language = SelectField('Wikipedia language', choices=[('en', 'en')])
+ org = SelectField('Organisation', choices=[('itf', 'ITF')])
+ url = StringField('Tournament link', validators=[DataRequired()])
+ year = IntegerField('Year', validators=[DataRequired()])
+ doubles = SelectField('Tournament type', choices=[(0, 'singles'), (1, 'doubles')])
+ format = SelectField('Match format', choices=[(3, 'best of 3'), (5, 'best of 5'), (2, 'best of 3; tiebreak deciding set'), (35, 'best of 3; best of 5 final')])
+ qual = SelectField('Qualifying', choices=[(0, 'no'), (1, 'yes')])
+ compact = SelectField('Compact draws', choices=[(1, 'yes'), (0, 'no')])
+ abbr = SelectField('Abbreviated names (e.g. R Federer)', choices=[(1, 'yes'), (0, 'no')])
+ seed_links = SelectField('Seed links', choices=[(1, 'yes'), (0, 'no')])
+ submit = SubmitField('Request')
diff --git a/Classes/requirements.txt b/Classes/requirements.txt
index 3ad3def..5ac811b 100644
--- a/Classes/requirements.txt
+++ b/Classes/requirements.txt
@@ -1,18 +1,24 @@
SPARQLWrapper
appdirs
beautifulsoup4
certifi
chardet
idna
isodate
+itertools
flask
+json
mypy-extensions
+nameparser
+num2words
+os
pip
pycountries
pyparsing
qwikidata
rdflib
+re
requests
six
soupsieve
urllib3
diff --git a/Classes/templates/outputtourneydraw.html b/Classes/templates/outputtourneydraw.html
new file mode 100644
index 0000000..5b24608
--- /dev/null
+++ b/Classes/templates/outputtourneydraw.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+{% set active_page = "tourneydraw" %}
+
+{% block content %}
+
+
Tournament draw
+
+
+ Draw:
+
+
+
+
+
+
+{% endblock %}
diff --git a/Classes/templates/tourneydraw.html b/Classes/templates/tourneydraw.html
index 01198f0..99c9593 100644
--- a/Classes/templates/tourneydraw.html
+++ b/Classes/templates/tourneydraw.html
@@ -1,9 +1,54 @@
{% extends "base.html" %}
{% set active_page = "tourneydraw" %}
{% block content %}
-
Tournament Draws
-
Under construction
+
Tournament draw generator
-{% endblock %}
\ No newline at end of file
+
+{% endblock %}