]> git.street.me.uk Git - andy/gpx.git/blame - src/libgpx/gpxreader.php
Rewrite GPXReader class
[andy/gpx.git] / src / libgpx / gpxreader.php
CommitLineData
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
25namespace libgpx;
26
8fadecc6
AS
27use \DateTimeImmutable;
28use \XMLReader;
88564339
AS
29
30/**
31 * Read GPX files.
32 *
33 * @author Andy Street <andy@street.me.uk>
34 */
35class 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}