]>
Commit | Line | Data |
---|---|---|
88564339 AS |
1 | <?php |
2 | /** | |
3 | * gpxreader.php | |
4 | * | |
5 | * Copyright 2018 Andy Street <andy@street.me.uk> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
20 | * MA 02110-1301, USA. | |
21 | * | |
22 | * | |
23 | */ | |
24 | ||
25 | namespace libgpx; | |
26 | ||
8fadecc6 AS |
27 | use \DateTimeImmutable; |
28 | use \XMLReader; | |
88564339 AS |
29 | |
30 | /** | |
31 | * Read GPX files. | |
32 | * | |
33 | * @author Andy Street <andy@street.me.uk> | |
34 | */ | |
35 | class GPXReader | |
36 | { | |
37 | ||
38 | /** | |
8fadecc6 | 39 | * Options used for XMLReader instances. |
88564339 | 40 | */ |
8fadecc6 | 41 | const XMLREADER_OPTIONS = LIBXML_NOERROR | LIBXML_NOWARNING; |
88564339 AS |
42 | |
43 | /** | |
44 | * Read GPX from a string. | |
45 | * | |
8fadecc6 | 46 | * @param string $data The GPX XML data. |
88564339 | 47 | * @return GPX The GPX data. |
88564339 AS |
48 | * @throws MalformedGPXException If the GPX file is invalid. |
49 | */ | |
8fadecc6 | 50 | public static function readString(string $data) |
88564339 | 51 | { |
8fadecc6 | 52 | return self::read($data, true); |
88564339 AS |
53 | } |
54 | ||
55 | /** | |
56 | * Read GPX from a file. | |
57 | * | |
58 | * This function is an alias of `GPXReader::readURI()`. | |
59 | * | |
60 | * @param string $filename The name of the file containing GPX XML data. | |
61 | * @return GPX The GPX data. | |
88564339 AS |
62 | * @throws MalformedGPXException If the GPX file is invalid. |
63 | */ | |
8fadecc6 | 64 | public static function readFile(string $filename) |
88564339 | 65 | { |
8fadecc6 | 66 | return self::read($filename, false); |
88564339 AS |
67 | } |
68 | ||
69 | /** | |
70 | * Read GPX from a URI. | |
71 | * | |
72 | * @param string $uri The location of the file containing GPX XML data. | |
73 | * @return GPX The GPX data. | |
88564339 AS |
74 | * @throws MalformedGPXException If the GPX file is invalid. |
75 | */ | |
8fadecc6 | 76 | public static function readURI(string $uri) |
88564339 | 77 | { |
8fadecc6 | 78 | return self::read($uri, false); |
88564339 AS |
79 | } |
80 | ||
81 | /** | |
8fadecc6 | 82 | * Read a GPX file. |
88564339 | 83 | * |
8fadecc6 AS |
84 | * @param string $src The source of the GPX data. |
85 | * @param boolean $fromString If true `src` is treated as a string containing | |
86 | * the XML to parse otherwise it is treated as a URI to load data from. | |
87 | * @return GPX The loaded GPX data. | |
88 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
88564339 | 89 | */ |
8fadecc6 | 90 | public static function read(string $src, bool $fromString) |
88564339 | 91 | { |
8fadecc6 AS |
92 | // Register stream wrapper |
93 | if (!$fromString) { | |
94 | $wrappers = stream_get_wrappers(); | |
95 | do { | |
96 | $wrapper = uniqid('libgpx-'); | |
97 | } while (in_array($wrapper, $wrappers)); | |
98 | if (!stream_wrapper_register($wrapper, '\libgpx\PeekStream')) | |
99 | throw new MalformedGPXException( | |
100 | 'Unable to register stream.' | |
101 | ); | |
102 | } | |
88564339 | 103 | try { |
8fadecc6 AS |
104 | // Load data from file |
105 | $xml = new XMLReader(); | |
106 | if ($fromString) { | |
107 | if (!@$xml->XML($src, null, self::XMLREADER_OPTIONS)) | |
108 | throw new MalformedGPXException( | |
109 | 'Unable to read XML string.' | |
110 | ); | |
111 | } else { | |
112 | if (!@$xml->open($wrapper . '://' . $src, null, self::XMLREADER_OPTIONS)) | |
113 | throw new MalformedGPXException( | |
114 | sprintf('Unable to read source ā%sā', $src) | |
115 | ); | |
88564339 AS |
116 | } |
117 | ||
8fadecc6 AS |
118 | // Namespace detection |
119 | $namespace = self::detectRootNamespace( | |
120 | $fromString ? $src : PeekStream::peek($src) | |
121 | ); | |
122 | switch ($namespace) { | |
88564339 | 123 | case libgpx::NAMESPACE_GPX_1_0: |
8fadecc6 | 124 | $xml->setSchema(__DIR__ . DIRECTORY_SEPARATOR . 'gpx-1-0.xsd'); |
88564339 AS |
125 | break; |
126 | case libgpx::NAMESPACE_GPX_1_1: | |
8fadecc6 | 127 | $xml->setSchema(__DIR__ . DIRECTORY_SEPARATOR . 'gpx-1-1.xsd'); |
88564339 | 128 | break; |
8fadecc6 AS |
129 | case null: |
130 | throw new MalformedGPXException( | |
131 | 'Supplied XML is not namespaced.' | |
132 | ); | |
133 | default : | |
134 | throw new MalformedGPXException( | |
135 | sprintf('Unsupported namespace: %s', $namespace) | |
136 | ); | |
88564339 AS |
137 | } |
138 | ||
8fadecc6 AS |
139 | // Read XML |
140 | while ($valid = ($xml->read() && $xml->isValid())) { | |
141 | if ($xml->nodeType == XMLReader::ELEMENT) break; | |
142 | } | |
143 | if (!$valid) | |
144 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
145 | $gpx = self::readGPX($xml); | |
88564339 | 146 | } finally { |
8fadecc6 | 147 | if (!$fromString) stream_wrapper_unregister($wrapper); |
88564339 | 148 | } |
8fadecc6 | 149 | return $gpx; |
88564339 AS |
150 | } |
151 | ||
152 | /** | |
8fadecc6 | 153 | * Detect the namespace of the XML root element. |
88564339 | 154 | * |
8fadecc6 AS |
155 | * @param string $data The XML to extract namespace from. |
156 | * @return string The namespace of the root element. | |
157 | * @throws MalformedGPXException If it was not possible to detect the namespace. | |
88564339 | 158 | */ |
8fadecc6 AS |
159 | protected static function detectRootNamespace(string $data) { |
160 | $xml = new XMLReader(); | |
161 | $xml->XML($data, null, self::XMLREADER_OPTIONS); | |
162 | $namespace = false; | |
163 | while ($xml->read()) { | |
164 | if ($xml->nodeType == XMLReader::ELEMENT) { | |
165 | $namespace = $xml->namespaceURI; | |
166 | break; | |
167 | } | |
88564339 | 168 | } |
8fadecc6 AS |
169 | if ($namespace === false) |
170 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
171 | $xml->close(); | |
172 | return $namespace; | |
88564339 AS |
173 | } |
174 | ||
175 | /** | |
8fadecc6 | 176 | * Reads a gpx type from an XML document. |
88564339 | 177 | * |
8fadecc6 AS |
178 | * @param XMLReader $xml An XML document with the cursor positioned at the |
179 | * start of the gpx type. | |
180 | * @return GPX A GPX data structure. | |
181 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
88564339 | 182 | */ |
8fadecc6 | 183 | protected static function readGPX(XMLReader $xml) |
88564339 | 184 | { |
8fadecc6 AS |
185 | $gpx = new GPX(); |
186 | $gpx->setCreator($xml->getAttribute('creator')); | |
187 | $ns = $xml->namespaceURI; | |
188 | $inMetadata = false; | |
189 | while ($valid = ($xml->read() && $xml->isValid())) { | |
190 | if ($xml->nodeType == XMLReader::ELEMENT) { | |
191 | if ($xml->namespaceURI == $ns) { | |
192 | switch ($xml->localName) { | |
193 | case 'metadata': | |
194 | $inMetadata = true; | |
195 | break; | |
196 | case 'name': | |
197 | $gpx->setName($xml->readString()); | |
198 | break; | |
199 | case 'desc': | |
200 | $gpx->setDesc($xml->readString()); | |
201 | break; | |
202 | case 'author': | |
203 | if ($inMetadata) { | |
204 | $gpx->setAuthor(self::readPerson($xml)); | |
205 | } else { | |
206 | $person = new Person(); | |
207 | $person->setName($xml->readString()); | |
208 | $gpx->setAuthor($person); | |
209 | } | |
210 | break; | |
211 | case 'email': | |
212 | $person = $gpx->getAuthor(); | |
213 | if ($person !== null) $gpx->setAuthor($person = new Person()); | |
214 | $person->setEmail($xml->readString()); | |
215 | break; | |
216 | case 'copyright': | |
217 | $gpx->setCopyright(self::readCopyright($xml)); | |
218 | break; | |
219 | case 'link': | |
220 | $gpx->getLinks()[] = self::readLink($xml); | |
221 | break; | |
222 | case 'url': | |
223 | $gpx->getLinks()[] = new Link($xml->readString()); | |
224 | break; | |
225 | case 'urlname': | |
226 | $links = $gpx->getLinks(); | |
227 | if (count($links) > 0) | |
228 | $links[0]->setText($xml->readString()); | |
229 | break; | |
230 | case 'time': | |
231 | $gpx->setTime(new DateTimeImmutable($xml->readString())); | |
232 | break; | |
233 | case 'keywords': | |
234 | $keywords = $gpx->getKeywords(); | |
235 | foreach (explode(',', $xml->readString()) as $keyword) | |
236 | $keywords[] = trim($keyword); | |
237 | break; | |
238 | case 'wpt': | |
239 | $gpx->getWaypoints()[] = self::readPoint($xml); | |
240 | break; | |
241 | case 'rte': | |
242 | $gpx->getRoutes()[] = self::readRoute($xml); | |
243 | break; | |
244 | case 'trk': | |
245 | $gpx->getTracks()[] = self::readTrack($xml); | |
246 | break; | |
247 | case 'extensions': | |
248 | $exts = ( | |
249 | $inMetadata | |
250 | ? $gpx->getMetadataExtensions() | |
251 | : $gpx->getExtensions() | |
252 | ); | |
253 | foreach (self::readExtensions($xml) as $ext) { | |
254 | $exts[] = $ext; | |
255 | } | |
256 | break; | |
257 | } | |
258 | } else { | |
259 | $gpx->getExtensions()[] = self::readExtension($xml); | |
88564339 | 260 | } |
8fadecc6 AS |
261 | } elseif ( |
262 | $xml->nodeType == XMLReader::END_ELEMENT | |
263 | && $xml->namespaceURI == $ns | |
264 | ) { | |
265 | switch ($xml->localName) { | |
266 | case 'metadata': | |
267 | $inMetadata = false; | |
268 | break; | |
269 | case 'gpx': | |
270 | break 2; | |
88564339 | 271 | } |
8fadecc6 AS |
272 | } |
273 | } | |
274 | if (!$valid) | |
275 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
276 | return $gpx; | |
88564339 AS |
277 | } |
278 | ||
279 | /** | |
8fadecc6 | 280 | * Reads a rte type from an XML document. |
88564339 | 281 | * |
8fadecc6 AS |
282 | * @param XMLReader $xml An XML document with the cursor positioned at the |
283 | * start of the rte type. | |
284 | * @return Route A Route data structure. | |
285 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
88564339 | 286 | */ |
8fadecc6 | 287 | protected static function readRoute(XMLReader $xml) |
88564339 | 288 | { |
8fadecc6 AS |
289 | $route = new Route(); |
290 | if ($xml->isEmptyElement) return $route; | |
291 | $ns = $xml->namespaceURI; | |
292 | while ($valid = ($xml->read() && $xml->isValid())) { | |
293 | if ($xml->nodeType == XMLReader::ELEMENT) { | |
294 | if ($xml->namespaceURI == $ns) { | |
295 | switch ($xml->localName) { | |
296 | case 'name': | |
297 | $route->setName($xml->readString()); | |
298 | break; | |
299 | case 'cmt': | |
300 | $route->setComment($xml->readString()); | |
301 | break; | |
302 | case 'desc': | |
303 | $route->setDescription($xml->readString()); | |
304 | break; | |
305 | case 'src': | |
306 | $route->setSource($xml->readString()); | |
307 | break; | |
308 | case 'link': | |
309 | $route->getLinks()[] = self::readLink($xml); | |
310 | break; | |
311 | case 'url': | |
312 | $route->getLinks()[] = new Link($xml->readString()); | |
313 | break; | |
314 | case 'urlname': | |
315 | $links = $route->getLinks(); | |
316 | if (count($links) > 0) | |
317 | $links[0]->setText($xml->readString()); | |
318 | break; | |
319 | case 'number': | |
320 | $route->setNumber($xml->readString()); | |
321 | break; | |
322 | case 'type'; | |
323 | $route->setType($xml->readString()); | |
324 | break; | |
325 | case 'extensions': | |
326 | $exts = $route->getExtensions(); | |
327 | foreach (self::readExtensions($xml) as $ext) { | |
328 | $exts[] = $ext; | |
329 | } | |
330 | break; | |
331 | case 'rtept': | |
332 | $route->getPoints()[] = self::readPoint($xml); | |
333 | break; | |
88564339 | 334 | } |
8fadecc6 AS |
335 | } else { |
336 | $route->getExtensions()[] = self::readExtension($xml); | |
88564339 | 337 | } |
8fadecc6 AS |
338 | } elseif ($xml->nodeType == XMLReader::END_ELEMENT) { |
339 | if ($xml->namespaceURI == $ns && $xml->localName == 'rte') break; | |
7c465a82 | 340 | } |
f17d2566 | 341 | } |
8fadecc6 AS |
342 | if (!$valid) |
343 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
344 | return $route; | |
f17d2566 AS |
345 | } |
346 | ||
347 | /** | |
8fadecc6 | 348 | * Reads a trk type from an XML document. |
f17d2566 | 349 | * |
8fadecc6 AS |
350 | * @param XMLReader $xml An XML document with the cursor positioned at the |
351 | * start of the trk type. | |
352 | * @return Track A Track data structure. | |
353 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
f17d2566 | 354 | */ |
8fadecc6 | 355 | protected static function readTrack(XMLReader $xml) |
f17d2566 | 356 | { |
8fadecc6 AS |
357 | $track = new Track(); |
358 | $ns = $xml->namespaceURI; | |
359 | while ($valid = ($xml->read() && $xml->isValid())) { | |
360 | if ($xml->nodeType == XMLReader::ELEMENT) { | |
361 | if ($xml->namespaceURI == $ns) { | |
362 | switch ($xml->localName) { | |
363 | case 'name': | |
364 | $track->setName($xml->readString()); | |
365 | break; | |
366 | case 'cmt': | |
367 | $track->setComment($xml->readString()); | |
368 | break; | |
369 | case 'desc': | |
370 | $track->setDescription($xml->readString()); | |
371 | break; | |
372 | case 'src': | |
373 | $track->setSource($xml->readString()); | |
374 | break; | |
375 | case 'link': | |
376 | $track->getLinks()[] = self::readLink($xml); | |
377 | break; | |
378 | case 'url': | |
379 | $track->getLinks()[] = new Link($xml->readString()); | |
380 | break; | |
381 | case 'urlname': | |
382 | $links = $track->getLinks(); | |
383 | if (count($links) > 0) | |
384 | $links[0]->setText($xml->readString()); | |
385 | break; | |
386 | case 'number': | |
387 | $track->setNumber($xml->readString()); | |
388 | break; | |
389 | case 'type'; | |
390 | $track->setType($xml->readString()); | |
391 | break; | |
392 | case 'extensions': | |
393 | $exts = $track->getExtensions(); | |
394 | foreach (self::readExtensions($xml) as $ext) { | |
395 | $exts[] = $ext; | |
396 | } | |
397 | break; | |
398 | case 'trkseg': | |
399 | $track->getSegments()[] = self::readTrackSegment($xml); | |
400 | break; | |
401 | } | |
402 | } else { | |
403 | $track->getExtensions()[] = self::readExtension($xml); | |
404 | } | |
405 | } elseif ($xml->nodeType == XMLReader::END_ELEMENT) { | |
406 | if ($xml->namespaceURI == $ns && $xml->localName == 'trk') break; | |
407 | } | |
35c309cc | 408 | } |
8fadecc6 AS |
409 | if (!$valid) |
410 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
411 | return $track; | |
35c309cc AS |
412 | } |
413 | ||
414 | /** | |
8fadecc6 | 415 | * Reads a trkseg type from an XML document. |
35c309cc | 416 | * |
8fadecc6 AS |
417 | * @param XMLReader $xml An XML document with the cursor positioned at the |
418 | * start of the trkseg type. | |
419 | * @return TrackSegment A TrackSegment data structure. | |
420 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
35c309cc | 421 | */ |
8fadecc6 | 422 | protected static function readTrackSegment(XMLReader $xml) |
35c309cc | 423 | { |
8fadecc6 AS |
424 | $segment = new TrackSegment(); |
425 | $ns = $xml->namespaceURI; | |
426 | while ($valid = ($xml->read() && $xml->isValid())) { | |
427 | if ($xml->nodeType == XMLReader::ELEMENT) { | |
428 | if ($xml->namespaceURI == $ns) { | |
429 | switch ($xml->localName) { | |
430 | case 'trkpt': | |
431 | $segment->getPoints()[] = self::readPoint($xml); | |
432 | break; | |
433 | case 'extensions': | |
434 | $exts = $segment->getExtensions(); | |
435 | foreach (self::readExtensions($xml) as $ext) { | |
436 | $exts[] = $ext; | |
437 | } | |
438 | break; | |
439 | } | |
35c309cc | 440 | } |
8fadecc6 AS |
441 | } elseif ($xml->nodeType == XMLReader::END_ELEMENT) { |
442 | if ($xml->namespaceURI == $ns && $xml->localName == 'trkseg') break; | |
443 | } | |
444 | } | |
445 | if (!$valid) | |
446 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
447 | return $segment; | |
35c309cc AS |
448 | } |
449 | ||
88564339 | 450 | /** |
8fadecc6 | 451 | * Reads a wpt type from an XML document. |
7c465a82 | 452 | * |
8fadecc6 AS |
453 | * @param XMLReader $xml An XML document with the cursor positioned at the |
454 | * start of the wpt type. | |
455 | * @return Point A Point data structure. | |
456 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
88564339 | 457 | */ |
8fadecc6 | 458 | protected static function readPoint(XMLReader $xml) |
88564339 | 459 | { |
8fadecc6 AS |
460 | $point = new Point( |
461 | $xml->getAttribute('lat'), | |
462 | $xml->getAttribute('lon') | |
463 | ); | |
464 | if ($xml->isEmptyElement) return $point; | |
465 | $ns = $xml->namespaceURI; | |
466 | $name = $xml->localName; | |
467 | while ($valid = ($xml->read() && $xml->isValid())) { | |
468 | if ($xml->nodeType == XMLReader::ELEMENT) { | |
469 | if ($xml->namespaceURI == $ns) { | |
470 | switch ($xml->localName) { | |
471 | case 'ele': | |
472 | $point->setEle($xml->readString()); | |
473 | break; | |
474 | case 'time': | |
475 | $point->setTime(new DateTimeImmutable($xml->readString())); | |
476 | break; | |
477 | case 'magvar': | |
478 | $point->setMagvar($xml->readString()); | |
479 | break; | |
480 | case 'geoidheight': | |
481 | $point->setGeoidHeight($xml->readString()); | |
482 | break; | |
483 | case 'name': | |
484 | $point->setName($xml->readString()); | |
485 | break; | |
486 | case 'cmt': | |
487 | $point->setComment($xml->readString()); | |
488 | break; | |
489 | case 'desc': | |
490 | $point->setDescription($xml->readString()); | |
491 | break; | |
492 | case 'src': | |
493 | $point->setSource($xml->readString()); | |
494 | break; | |
495 | case 'link': | |
496 | $point->getLinks()[] = self::readLink($xml); | |
497 | break; | |
498 | case 'url': | |
499 | $point->getLinks()[] = new Link($xml->readString()); | |
500 | break; | |
501 | case 'urlname': | |
502 | $links = $point->getLinks(); | |
503 | if (count($links) > 0) | |
504 | $links[0]->setText($xml->readString()); | |
505 | break; | |
506 | case 'sym': | |
507 | $point->setSymbol($xml->readString()); | |
508 | break; | |
509 | case 'type': | |
510 | $point->setType($xml->readString()); | |
511 | break; | |
512 | case 'fix': | |
513 | $point->setFix($xml->readString()); | |
514 | break; | |
515 | case 'sat': | |
516 | $point->setSatellites($xml->readString()); | |
517 | break; | |
518 | case 'hdop': | |
519 | $point->setHdop($xml->readString()); | |
520 | break; | |
521 | case 'vdop': | |
522 | $point->setVdop($xml->readString()); | |
523 | break; | |
524 | case 'pdop': | |
525 | $point->setPdop($xml->readString()); | |
526 | break; | |
527 | case 'ageofdgpsdata': | |
528 | $point->setAgeOfDGPSData($xml->readString()); | |
529 | break; | |
530 | case 'dgpsid': | |
531 | $point->setDGPSId($xml->readString()); | |
532 | break; | |
533 | case 'extensions': | |
534 | $exts = $point->getExtensions(); | |
535 | foreach (self::readExtensions($xml) as $ext) { | |
536 | $exts[] = $ext; | |
537 | } | |
538 | break; | |
539 | } | |
88564339 | 540 | } else { |
8fadecc6 | 541 | $point->getExtensions()[] = self::readExtension($xml); |
88564339 | 542 | } |
8fadecc6 AS |
543 | } elseif ($xml->nodeType == XMLReader::END_ELEMENT) { |
544 | if ($xml->namespaceURI == $ns && $xml->localName == $name) break; | |
7c465a82 | 545 | } |
88564339 | 546 | } |
8fadecc6 AS |
547 | if (!$valid) |
548 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
549 | return $point; | |
88564339 AS |
550 | } |
551 | ||
09a36623 | 552 | /** |
8fadecc6 | 553 | * Reads a person type from an XML document. |
09a36623 | 554 | * |
8fadecc6 AS |
555 | * @param XMLReader $xml An XML document with the cursor positioned at the |
556 | * start of the person type. | |
557 | * @return Person A Person data structure. | |
558 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
09a36623 | 559 | */ |
8fadecc6 | 560 | protected static function readPerson(XMLReader $xml) |
09a36623 | 561 | { |
8fadecc6 AS |
562 | $person = new Person(); |
563 | if ($xml->isEmptyElement) return $person; | |
564 | $name = $xml->localName; | |
565 | while ($valid = ($xml->read() && $xml->isValid())) { | |
566 | if ($xml->nodeType == XMLReader::ELEMENT) { | |
567 | switch ($xml->localName) { | |
568 | case 'name': | |
569 | $person->setName($xml->readString()); | |
570 | break; | |
571 | case 'email': | |
572 | $person->setEmail( | |
573 | $xml->getAttribute('id') . '@' . $xml->getAttribute('domain') | |
574 | ); | |
575 | break; | |
576 | case 'link': | |
577 | $person->setLink(self::readLink($xml)); | |
578 | break; | |
09a36623 | 579 | } |
8fadecc6 AS |
580 | } elseif ($xml->nodeType == XMLReader::END_ELEMENT) { |
581 | if ($xml->localName == $name) break; | |
582 | } | |
09a36623 | 583 | } |
8fadecc6 AS |
584 | if (!$valid) |
585 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
586 | return $person; | |
09a36623 AS |
587 | } |
588 | ||
88564339 | 589 | /** |
8fadecc6 | 590 | * Reads a link type from an XML document. |
88564339 | 591 | * |
8fadecc6 AS |
592 | * @param XMLReader $xml An XML document with the cursor positioned at the |
593 | * start of the link type. | |
594 | * @return Link A Link data structure. | |
595 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
88564339 | 596 | */ |
8fadecc6 | 597 | protected static function readLink(XMLReader $xml) |
88564339 | 598 | { |
8fadecc6 AS |
599 | $link = new Link($xml->getAttribute('href')); |
600 | if ($xml->isEmptyElement) return $link; | |
601 | while ($valid = ($xml->read() && $xml->isValid())) { | |
602 | if ($xml->nodeType == XMLReader::ELEMENT) { | |
603 | switch ($xml->localName) { | |
604 | case 'text': | |
605 | $link->setText($xml->readString()); | |
606 | break; | |
607 | case 'type': | |
608 | $link->setType($xml->readString()); | |
609 | break; | |
610 | } | |
611 | } elseif ($xml->nodeType == XMLReader::END_ELEMENT) { | |
612 | if ($xml->localName == 'link') break; | |
88564339 | 613 | } |
88564339 | 614 | } |
8fadecc6 AS |
615 | if (!$valid) |
616 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
617 | return $link; | |
88564339 AS |
618 | } |
619 | ||
620 | /** | |
8fadecc6 | 621 | * Reads a copyright type from an XML document. |
88564339 | 622 | * |
8fadecc6 AS |
623 | * @param XMLReader $xml An XML document with the cursor positioned at the |
624 | * start of the copyright type. | |
625 | * @return Copyright A Copyright data structure. | |
626 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
88564339 | 627 | */ |
8fadecc6 | 628 | protected static function readCopyright(XMLReader $xml) |
88564339 | 629 | { |
8fadecc6 AS |
630 | $copyright = new Copyright($xml->getAttribute('author')); |
631 | if ($xml->isEmptyElement) return $copyright; | |
632 | $name = $xml->localName; | |
633 | while ($valid = ($xml->read() && $xml->isValid())) { | |
634 | if ($xml->nodeType == XMLReader::ELEMENT) { | |
635 | switch ($xml->localName) { | |
636 | case 'year': | |
637 | $copyright->setYear($xml->readString()); | |
638 | break; | |
639 | case 'license': | |
640 | $copyright->setLicense($xml->readString()); | |
641 | break; | |
642 | } | |
643 | } elseif ($xml->nodeType == XMLReader::END_ELEMENT) { | |
644 | if ($xml->localName == $name) break; | |
645 | } | |
88564339 | 646 | } |
8fadecc6 AS |
647 | if (!$valid) |
648 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
649 | return $copyright; | |
88564339 AS |
650 | } |
651 | ||
f528d248 | 652 | /** |
8fadecc6 | 653 | * Reads an extensions type (GPX 1.1 only) from an XML document. |
f528d248 | 654 | * |
8fadecc6 AS |
655 | * @param XMLReader $xml An XML document with the cursor positioned at the |
656 | * start of the extensions type. | |
657 | * @return string[] An array of XML snippets describing the extensions. | |
658 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
f528d248 | 659 | */ |
8fadecc6 | 660 | protected static function readExtensions(XMLReader $xml) |
f528d248 | 661 | { |
8fadecc6 AS |
662 | $extensions = []; |
663 | if (!$xml->isEmptyElement) { | |
664 | $xml->read(); | |
665 | do { | |
666 | switch ($xml->nodeType) { | |
667 | case XMLReader::ELEMENT: | |
668 | $extensions[] = $xml->readOuterXML(); | |
669 | break; | |
670 | case XMLReader::END_ELEMENT: | |
671 | break 2; | |
672 | } | |
673 | } while ($valid = ($xml->next() && $xml->isValid())); | |
674 | } | |
675 | if (!$valid) | |
676 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
677 | return $extensions; | |
f528d248 AS |
678 | } |
679 | ||
680 | /** | |
8fadecc6 | 681 | * Reads a extension (GPX 1.0 only) from an XML document. |
f528d248 | 682 | * |
8fadecc6 AS |
683 | * @param XMLReader $xml An XML document with the cursor positioned at the |
684 | * start of the extension element. | |
685 | * @return string An XML snippet describing the extension. | |
686 | * @throws MalformedGPXException If there was a problem parsing the XML. | |
f528d248 | 687 | */ |
8fadecc6 | 688 | protected static function readExtension(XMLReader $xml) |
f528d248 | 689 | { |
8fadecc6 AS |
690 | $extension = $xml->readOuterXML(); |
691 | $depth = 0; | |
692 | while ($valid = ($xml->read() && $xml->isValid())) { | |
693 | if ($xml->nodeType == XMLReader::END_ELEMENT && $depth-- == 0) { | |
694 | break; | |
695 | } elseif ($xml->nodeType == XMLReader::ELEMENT && !$xml->isEmptyElement) { | |
696 | $depth++; | |
697 | } | |
698 | } | |
699 | if (!$valid) | |
700 | throw new MalformedGPXException(libxml_get_last_error()->message); | |
701 | return $extension; | |
f528d248 AS |
702 | } |
703 | ||
88564339 | 704 | } |