Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
73 / 73
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
SolvResultParser
100.00% covered (success)
100.00%
73 / 73
100.00% covered (success)
100.00%
3 / 3
20
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parse_solv_yearly_results_json
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
9
 parse_solv_event_result_html
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
1 / 1
10
1<?php
2
3namespace Olz\Parsers;
4
5use Olz\Entity\SolvResult;
6
7class SolvResultParser {
8    protected TimeParser $timeParser;
9
10    public function __construct() {
11        $this->timeParser = new TimeParser();
12    }
13
14    /** @return array<int, array{result_list_id: string}> */
15    public function parse_solv_yearly_results_json(string $json_content): array {
16        $hacky_sanitized_json = str_replace(["\n", "\t"], ['', '  '], $json_content);
17        if (!json_validate($hacky_sanitized_json, 512, JSON_INVALID_UTF8_IGNORE)) {
18            $msg = json_last_error_msg();
19            throw new \Exception("Invalid JSON in parse_solv_yearly_results_json (hackyly sanitized): {$msg}\n\n{$hacky_sanitized_json}");
20        }
21        $data = json_decode($hacky_sanitized_json, true, 512, JSON_INVALID_UTF8_SUBSTITUTE);
22
23        if (!$data || !isset($data['ResultLists']) || !is_array($data['ResultLists'])) {
24            return [];
25        }
26
27        $result_by_uid = [];
28        for ($res_ind = 0; $res_ind < count($data['ResultLists']); $res_ind++) {
29            $res = $data['ResultLists'][$res_ind];
30            if (!isset($res['UniqueID']) || $res['UniqueID'] == 0) {
31                continue;
32            }
33            $uid = $res['UniqueID'];
34            // TODO: find better solution to deal with multiple results
35            if (isset($result_by_uid[$uid])) {
36                continue;
37            }
38            $result_by_uid[$uid] = [
39                'result_list_id' => strval($res['ResultListID']),
40            ];
41        }
42        return $result_by_uid;
43    }
44
45    /** @return array<SolvResult> */
46    public function parse_solv_event_result_html(string $html_content, int $event_uid): array {
47        $class_headers_count = preg_match_all('/<b>(?:<p><\/p>)?<a href="results\?type=rang&year=([0-9]+)&rl_id=([0-9]+)&kat=([^"]+)&zwizt=1">([^<]+)<\/a><\/b>\s*<pre>/is', $html_content, $class_matches);
48        $results = [];
49        for ($class_ind = 0; $class_ind < $class_headers_count; $class_ind++) {
50            $class_name = $class_matches[4][$class_ind];
51
52            $found_at = mb_strpos($html_content, $class_matches[0][$class_ind]);
53            $class_header_ends_at = $found_at + mb_strlen($class_matches[0][$class_ind]);
54            $class_body_ends_at = mb_strpos($html_content, '</pre>', $class_header_ends_at);
55            $class_body_length = $class_body_ends_at - $class_header_ends_at;
56            $class_body = mb_substr($html_content, $class_header_ends_at, $class_body_length);
57
58            $does_class_info_match = preg_match("/^\\s*\\(\\s*([0-9\\.]+)\\s*km\\s*,\\s*([0-9]+)\\s*m\\s*,\\s*([0-9]+)\\s*Po\\.\\s*\\)\\s*([0-9]+)\\s*Teilnehmer/", $class_body, $class_info_matches);
59            $class_info = [
60                'distance' => $does_class_info_match ? intval(floatval($class_info_matches[1]) * 1000) : 0,
61                'elevation' => $does_class_info_match ? intval($class_info_matches[2]) : 0,
62                'control_count' => $does_class_info_match ? intval($class_info_matches[3]) : 0,
63                'competitor_count' => $does_class_info_match ? intval($class_info_matches[4]) : 0,
64            ];
65
66            $competitors_count = preg_match_all('/<b>([^<]+)<\/b>/', $class_body, $competitor_matches);
67            for ($competitor_ind = 0; $competitor_ind < $competitors_count; $competitor_ind++) {
68                $competitor_line = $competitor_matches[1][$competitor_ind];
69                $rank = intval(mb_substr($competitor_line, 0, 3));
70                $name = trim(mb_substr($competitor_line, 5, 22));
71                $birth_year = trim(mb_substr($competitor_line, 28, 2));
72                $domicile = trim(mb_substr($competitor_line, 32, 18));
73                $club = trim(mb_substr($competitor_line, 51, 18));
74                $result = $this->timeParser->time_str_to_seconds(trim(mb_substr($competitor_line, 70, 8)));
75
76                $found_at = mb_strpos($class_body, $competitor_matches[0][$competitor_ind]);
77                $competitor_line_ends_at = $found_at + mb_strlen($competitor_matches[0][$competitor_ind]);
78                $splits_ends_at = mb_strpos($class_body, '<b>', $competitor_line_ends_at);
79                if ($splits_ends_at === false) {
80                    $splits_ends_at = mb_strlen($class_body);
81                }
82                $splits_length = $splits_ends_at - $competitor_line_ends_at;
83                $splits = trim(mb_substr($class_body, $competitor_line_ends_at, $splits_length));
84
85                $finish_offset = mb_strpos($splits, ' Zi ');
86                $finish_split = $this->timeParser->time_str_to_seconds(trim(mb_substr($splits, $finish_offset + 4, 6)));
87
88                if ($rank !== 1 || preg_match('/zimmerberg/i', $club)) {
89                    $solv_result = new SolvResult();
90                    $solv_result->setPerson(0);
91                    $solv_result->setEvent($event_uid);
92                    $solv_result->setClass($class_name);
93                    $solv_result->setRank($rank);
94                    $solv_result->setName($name);
95                    $solv_result->setBirthYear($birth_year);
96                    $solv_result->setDomicile($domicile);
97                    $solv_result->setClub($club);
98                    $solv_result->setResult($result);
99                    $solv_result->setSplits($splits);
100                    $solv_result->setFinishSplit($finish_split);
101                    $solv_result->setClassDistance($class_info['distance']);
102                    $solv_result->setClassElevation($class_info['elevation']);
103                    $solv_result->setClassControlCount($class_info['control_count']);
104                    $solv_result->setClassCompetitorCount($class_info['competitor_count']);
105                    $results[] = $solv_result;
106                }
107            }
108        }
109        return $results;
110    }
111}