import re from collections import defaultdict from urllib.parse import urljoin from ansi2html import Ansi2HTMLConverter from django.contrib.auth.models import AbstractUser from django.urls import reverse from django.utils.safestring import mark_safe from lxml.html import Element from judge import lxml_tree from judge.models import Contest, Problem, Profile from judge.ratings import rating_class, rating_progress from . import registry rereference = re.compile(r"\[(r?user):(\w+)\]") def get_user(username, data): if not data: element = Element("span") element.text = username return element element = Element("span", {"class": Profile.get_user_css_class(*data)}) link = Element("a", {"href": reverse("user_page", args=[username])}) link.text = username element.append(link) return element def get_user_rating(username, data): if not data: element = Element("span") element.text = username return element rating = data[1] element = Element( "a", {"class": "rate-group", "href": reverse("user_page", args=[username])} ) if rating: rating_css = rating_class(rating) rate_box = Element("span", {"class": "rate-box " + rating_css}) rate_box.append( Element("span", {"style": "height: %3.fem" % rating_progress(rating)}) ) user = Element("span", {"class": "rating " + rating_css}) user.text = username element.append(rate_box) element.append(user) else: element.text = username return element def get_user_info(usernames): return { name: (rank, rating) for name, rank, rating in Profile.objects.filter( user__username__in=usernames ).values_list("user__username", "display_rank", "rating") } def get_user_from_text(text): user_list = set() for i in rereference.finditer(text): user_list.add(text[i.start() + 6 : i.end() - 1]) return Profile.objects.filter(user__username__in=user_list) reference_map = { "user": (get_user, get_user_info), "ruser": (get_user_rating, get_user_info), } def process_reference(text): # text/tail -> text/tail + elements last = 0 tail = text prev = None elements = [] for piece in rereference.finditer(text): if prev is None: tail = text[last : piece.start()] else: prev.append(text[last : piece.start()]) prev = list(piece.groups()) elements.append(prev) last = piece.end() if prev is not None: prev.append(text[last:]) return tail, elements def populate_list(queries, list, element, tail, children): if children: for elem in children: queries[elem[0]].append(elem[1]) list.append((element, tail, children)) def update_tree(list, results, is_tail=False): for element, text, children in list: after = [] for type, name, tail in children: child = reference_map[type][0](name, results[type].get(name)) child.tail = tail after.append(child) after = iter(reversed(after)) if is_tail: element.tail = text link = element.getnext() if link is None: link = next(after) element.getparent().append(link) else: element.text = text link = next(after) element.insert(0, link) for child in after: link.addprevious(child) link = child @registry.filter def reference(text): tree = lxml_tree.fromstring(text) texts = [] tails = [] queries = defaultdict(list) for element in tree.iter(): if element.text: populate_list(queries, texts, element, *process_reference(element.text)) if element.tail: populate_list(queries, tails, element, *process_reference(element.tail)) results = {type: reference_map[type][1](values) for type, values in queries.items()} update_tree(texts, results, is_tail=False) update_tree(tails, results, is_tail=True) return tree @registry.filter def item_title(item): if isinstance(item, Problem): return item.name elif isinstance(item, Contest): return item.name return "" @registry.function @registry.render_with("user/link.html") def link_user(user): if isinstance(user, Profile): profile = user elif isinstance(user, AbstractUser): profile = user.profile elif type(user).__name__ == "ContestRankingProfile": profile = user else: raise ValueError("Expected profile or user, got %s" % (type(user),)) return {"profile": profile} @registry.function @registry.render_with("user/link-list.html") def link_users(users): return {"users": users} @registry.function @registry.render_with("runtime-version-fragment.html") def runtime_versions(versions): return {"runtime_versions": versions} @registry.filter(name="absolutify") def absolute_links(text, url): tree = lxml_tree.fromstring(text) for anchor in tree.xpath(".//a"): href = anchor.get("href") if href: anchor.set("href", urljoin(url, href)) return tree @registry.function(name="urljoin") def join(first, second, *rest): if not rest: return urljoin(first, second) return urljoin(urljoin(first, second), *rest) @registry.filter(name="ansi2html") def ansi2html(s): return mark_safe(Ansi2HTMLConverter(inline=True).convert(s, full=False))