Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
131 / 131 |
|
100.00% |
10 / 10 |
CRAP | |
100.00% |
1 / 1 |
XmlParser | |
100.00% |
131 / 131 |
|
100.00% |
10 / 10 |
35 | |
100.00% |
1 / 1 |
load | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
parseXml | |
100.00% |
44 / 44 |
|
100.00% |
1 / 1 |
4 | |||
parseGenerator | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
parseConferenceColor | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
parseTracks | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
parseEvents | |
100.00% |
33 / 33 |
|
100.00% |
1 / 1 |
9 | |||
parseRecording | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
getFirstXpathContent | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
3 | |||
getListFromSequence | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
getSchedule | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Engelsystem\Helpers\Schedule; |
6 | |
7 | use Carbon\Carbon; |
8 | use SimpleXMLElement; |
9 | |
10 | class XmlParser |
11 | { |
12 | protected SimpleXMLElement $scheduleXML; |
13 | |
14 | protected Schedule $schedule; |
15 | |
16 | public function load(string $xml): bool |
17 | { |
18 | $scheduleXML = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOWARNING | LIBXML_NOERROR); |
19 | if (!$scheduleXML) { |
20 | return false; |
21 | } |
22 | |
23 | $this->scheduleXML = $scheduleXML; |
24 | $this->parseXml(); |
25 | return true; |
26 | } |
27 | |
28 | /** |
29 | * Parse the predefined XML content |
30 | * |
31 | * See also https://c3voc.de/wiki/schedule |
32 | * |
33 | * According to https://github.com/voc/schedule/blob/master/validator/xsd/schedule.xml.xsd |
34 | */ |
35 | protected function parseXml(): void |
36 | { |
37 | $version = $this->getFirstXpathContent('version'); |
38 | $generator = $this->parseGenerator($this->scheduleXML); |
39 | $color = $this->parseConferenceColor($this->scheduleXML); |
40 | $tracks = $this->parseTracks($this->scheduleXML); |
41 | |
42 | $conference = new Conference( |
43 | $this->getFirstXpathContent('conference/title'), |
44 | $this->getFirstXpathContent('conference/acronym'), |
45 | $this->getFirstXpathContent('conference/start'), |
46 | $this->getFirstXpathContent('conference/end'), |
47 | (int) $this->getFirstXpathContent('conference/days'), |
48 | $this->getFirstXpathContent('conference/timeslot_duration'), |
49 | $this->getFirstXpathContent('conference/base_url'), |
50 | $this->getFirstXpathContent('conference/logo'), |
51 | $this->getFirstXpathContent('conference/url'), |
52 | $this->getFirstXpathContent('conference/time_zone_name'), |
53 | $color, |
54 | $tracks, |
55 | ); |
56 | |
57 | $days = []; |
58 | foreach ($this->scheduleXML->xpath('day') as $day) { |
59 | $rooms = []; |
60 | |
61 | foreach ($day->xpath('room') as $roomElement) { |
62 | $guid = (string) $roomElement->attributes()['guid']; |
63 | $room = new Room( |
64 | (string) $roomElement->attributes()['name'], |
65 | !empty($guid) ? $guid : null |
66 | ); |
67 | |
68 | $events = $this->parseEvents($roomElement->xpath('event'), $room, $tracks); |
69 | $room->setEvents($events); |
70 | $rooms[] = $room; |
71 | } |
72 | |
73 | $data = $day->attributes(); |
74 | $days[] = new Day( |
75 | (string) $data['date'], |
76 | new Carbon((string) $data['start']), |
77 | new Carbon((string) $data['end']), |
78 | (int) $data['index'], |
79 | $rooms |
80 | ); |
81 | } |
82 | |
83 | $this->schedule = new Schedule( |
84 | $version, |
85 | $conference, |
86 | $days, |
87 | $generator |
88 | ); |
89 | } |
90 | |
91 | protected function parseGenerator(SimpleXMLElement $scheduleXML): ?ScheduleGenerator |
92 | { |
93 | $generatorData = $scheduleXML->xpath('generator'); |
94 | if (!isset($generatorData[0])) { |
95 | return null; |
96 | } |
97 | |
98 | $data = $generatorData[0]->attributes(); |
99 | return new ScheduleGenerator( |
100 | (string) $data['name'] ?? null, |
101 | (string) $data['version'] ?? null, |
102 | ); |
103 | } |
104 | |
105 | protected function parseConferenceColor(SimpleXMLElement $scheduleXML): ?ConferenceColor |
106 | { |
107 | $conferenceColorData = $scheduleXML->xpath('conference/color'); |
108 | if (!isset($conferenceColorData[0])) { |
109 | return null; |
110 | } |
111 | |
112 | $data = collect($conferenceColorData[0]->attributes())->map(fn($value) => (string) $value); |
113 | $additionalData = $data->collect()->forget(['primary', 'background']); |
114 | return new ConferenceColor( |
115 | $data['primary'] ?? null, |
116 | $data['background'] ?? null, |
117 | $additionalData->isNotEmpty() ? $additionalData->toArray() : [] |
118 | ); |
119 | } |
120 | |
121 | /** |
122 | * @return ConferenceTrack[] |
123 | */ |
124 | protected function parseTracks(SimpleXMLElement $scheduleXML): array |
125 | { |
126 | $tracksData = $scheduleXML->xpath('conference/track'); |
127 | if (!isset($tracksData[0])) { |
128 | return []; |
129 | } |
130 | |
131 | $tracks = []; |
132 | foreach ($tracksData as $trackData) { |
133 | $data = $trackData->attributes(); |
134 | $tracks[] = new ConferenceTrack( |
135 | (string) $data['name'], |
136 | (string) $data['color'] ?? null, |
137 | (string) $data['slug'] ?? null, |
138 | ); |
139 | } |
140 | return $tracks; |
141 | } |
142 | |
143 | /** |
144 | * @param SimpleXMLElement[] $eventElements |
145 | * @param ConferenceTrack[] $tracks |
146 | */ |
147 | protected function parseEvents(array $eventElements, Room $room, array $tracks): array |
148 | { |
149 | $events = []; |
150 | |
151 | foreach ($eventElements as $event) { |
152 | $persons = $this->getListFromSequence($event, 'persons', 'person', 'id'); |
153 | $links = $this->getListFromSequence($event, 'links', 'link', 'href'); |
154 | $attachments = $this->getListFromSequence($event, 'attachments', 'attachment', 'href'); |
155 | |
156 | $recording = $this->parseRecording($event); |
157 | $trackName = $this->getFirstXpathContent('track', $event); |
158 | $track = collect($tracks)->where('name', $trackName)->first() ?: new ConferenceTrack($trackName); |
159 | |
160 | $events[] = new Event( |
161 | (string) $event->attributes()['guid'], |
162 | (int) $event->attributes()['id'], |
163 | $room, |
164 | $this->getFirstXpathContent('title', $event), |
165 | $this->getFirstXpathContent('subtitle', $event), |
166 | $this->getFirstXpathContent('type', $event), |
167 | new Carbon($this->getFirstXpathContent('date', $event)), |
168 | $this->getFirstXpathContent('start', $event), |
169 | $this->getFirstXpathContent('duration', $event), |
170 | $this->getFirstXpathContent('abstract', $event), |
171 | $this->getFirstXpathContent('slug', $event), |
172 | $track, |
173 | $this->getFirstXpathContent('logo', $event) ?: null, |
174 | $persons, |
175 | $this->getFirstXpathContent('language', $event) ?: null, |
176 | $this->getFirstXpathContent('description', $event) ?: null, |
177 | $recording, |
178 | $links, |
179 | $attachments, |
180 | $this->getFirstXpathContent('url', $event) ?: null, |
181 | $this->getFirstXpathContent('video_download_url', $event) ?: null, |
182 | $this->getFirstXpathContent('feedback_url', $event) ?: null, |
183 | ); |
184 | } |
185 | |
186 | return $events; |
187 | } |
188 | |
189 | protected function parseRecording(SimpleXMLElement $event): ?EventRecording |
190 | { |
191 | $recordingElement = $event->xpath('recording'); |
192 | if (!isset($recordingElement[0])) { |
193 | return null; |
194 | } |
195 | |
196 | $element = $recordingElement[0]; |
197 | return new EventRecording( |
198 | $this->getFirstXpathContent('license', $element) ?: '', |
199 | $this->getFirstXpathContent('optout', $element) != 'false', |
200 | $this->getFirstXpathContent('url', $element) ?: null, |
201 | $this->getFirstXpathContent('link', $element) ?: null, |
202 | ); |
203 | } |
204 | |
205 | protected function getFirstXpathContent(string $path, ?SimpleXMLElement $xml = null): string |
206 | { |
207 | $element = ($xml ?: $this->scheduleXML)->xpath($path); |
208 | |
209 | return $element ? (string) $element[0] : ''; |
210 | } |
211 | |
212 | /** |
213 | * Resolves a list from a sequence of elements |
214 | */ |
215 | protected function getListFromSequence( |
216 | SimpleXMLElement $element, |
217 | string $firstElement, |
218 | string $secondElement, |
219 | string $idAttribute |
220 | ): array { |
221 | $items = []; |
222 | |
223 | foreach ($element->xpath($firstElement) as $element) { |
224 | foreach ($element->xpath($secondElement) as $item) { |
225 | $items[(string) $item->attributes()[$idAttribute]] = (string) $item; |
226 | } |
227 | } |
228 | |
229 | return $items; |
230 | } |
231 | |
232 | public function getSchedule(): Schedule |
233 | { |
234 | return $this->schedule; |
235 | } |
236 | } |