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): user, profile = user.user, user elif isinstance(user, AbstractUser): profile = user.profile elif type(user).__name__ == 'ContestRankingProfile': user, profile = user.user, user else: raise ValueError('Expected profile or user, got %s' % (type(user),)) return {'user': user, '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))