From ccfafc5da4b300f9b66dec21b4e2c9c78adbb218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Mon, 23 Mar 2026 16:59:49 +0100 Subject: [PATCH] fix: allow hotel-less travel data addresses #869ckkwh7 --- src/BusProNet/XmlLoader/HotelLoader.php | 6 +++ src/BusProNet/XmlParser/TravelParser.php | 12 +++--- tests/BusProNet/XmlLoader/HotelLoaderTest.php | 31 +++++++++++++ .../BusProNet/XmlParser/TravelParserTest.php | 43 +++++++++++++++++++ 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 tests/BusProNet/XmlLoader/HotelLoaderTest.php diff --git a/src/BusProNet/XmlLoader/HotelLoader.php b/src/BusProNet/XmlLoader/HotelLoader.php index 76b7fc2..8d50b87 100644 --- a/src/BusProNet/XmlLoader/HotelLoader.php +++ b/src/BusProNet/XmlLoader/HotelLoader.php @@ -91,6 +91,12 @@ class HotelLoader extends AbstractLoader public function patchHotelDetails(Travel $travel): void { $hotelId = $travel->hotelId; + if (null === $hotelId) { + $travel->hotel = null; + + return; + } + try { $hotel = $this->loadById($hotelId); $travel->hotel = $hotel; diff --git a/src/BusProNet/XmlParser/TravelParser.php b/src/BusProNet/XmlParser/TravelParser.php index f043c0a..01837a8 100644 --- a/src/BusProNet/XmlParser/TravelParser.php +++ b/src/BusProNet/XmlParser/TravelParser.php @@ -60,7 +60,9 @@ class TravelParser extends AbstractParser $travel = new Travel(); $travel->id = (int) $this->getRequiredAttrValue($node, 'idbuspro', 'travel termin node'); - $travel->hotelId = (int) $this->getRequiredAttrValue($hotelNode, 'idbuspro', 'travel hotel node'); + $travel->hotelId = null !== $hotelNode + ? (int) $this->getRequiredAttrValue($hotelNode, 'idbuspro', 'travel hotel node') + : null; $travel->label = $this->getStringOrNullValue($node->filterXPath('.//text')); $travel->dateFrom = $dateFrom; $travel->dateTo = $dateTo; @@ -80,7 +82,7 @@ class TravelParser extends AbstractParser ->getAdditionalServices($node->filterXPath('.//lei_sonstiges[not(ancestor::hotel)]/leistung')); $travel->transportationServices = $this ->getTransportationServices($node->filterXPath('.//lei_befoerderung/leistung')); - $travel->rooms = $this->getRooms($hotelNode); + $travel->rooms = null !== $hotelNode ? $this->getRooms($hotelNode) : []; $travel->pickups = $this->getPickups($node->filterXPath('.//zustiege/zustieg'), $dateFrom, true); $travel->dropOffs = $this->getPickups($node->filterXPath('.//zustiege_rueck/zustieg_rueck'), $dateTo, false); @@ -319,16 +321,16 @@ class TravelParser extends AbstractParser * @param Crawler $node The XML node containing hotel data * @param int|null $hotelId Optional hotel ID to filter by * - * @return Crawler The hotel XML node + * @return Crawler|null The hotel XML node or null if none exists and no specific hotel was requested */ - public function getHotelNode(Crawler $node, ?int $hotelId): Crawler + public function getHotelNode(Crawler $node, ?int $hotelId): ?Crawler { // In case not hotel id is provided, take the first hotel node (which is most probably the only one) if (null === $hotelId) { $hotelNode = $node->filterXPath('.//hotel'); if (0 === $hotelNode->count()) { - throw new \InvalidArgumentException('No hotel node found in travel XML.'); + return null; } return $hotelNode->first(); diff --git a/tests/BusProNet/XmlLoader/HotelLoaderTest.php b/tests/BusProNet/XmlLoader/HotelLoaderTest.php new file mode 100644 index 0000000..aa71c2c --- /dev/null +++ b/tests/BusProNet/XmlLoader/HotelLoaderTest.php @@ -0,0 +1,31 @@ +createMock(FilesystemOperator::class); + $filesystem + ->expects($this->never()) + ->method('read'); + + $loader = new HotelLoader(new ArrayAdapter(), $filesystem); + $travel = new Travel(); + $travel->hotelId = null; + $travel->hotel = null; + + $loader->patchHotelDetails($travel); + + $this->assertNull($travel->hotel); + } +} diff --git a/tests/BusProNet/XmlParser/TravelParserTest.php b/tests/BusProNet/XmlParser/TravelParserTest.php index 3e748c5..49d0e42 100644 --- a/tests/BusProNet/XmlParser/TravelParserTest.php +++ b/tests/BusProNet/XmlParser/TravelParserTest.php @@ -184,6 +184,49 @@ class TravelParserTest extends TestCase $this->assertTrue($additionalServices[183660]->autoBook); } + public function testParseTravelWithoutHotelNode(): void + { + $xmlContent = ' + + + + PG TSG Irlich + -50,00 + + + 7 Übernachtungen im 3* Posthotel + 999,00 + Frei + + + + + Bus-Hinfahrt + 5,00 + Frei + HIN + + + + + + + +'; + + $crawler = new Crawler($xmlContent); + $travelNode = $crawler->filterXPath('//reise/termin')->first(); + $travel = $this->parser->parse($travelNode); + + $this->assertSame(12162, $travel->id); + $this->assertSame('PGTSGI280326', $travel->code); + $this->assertNull($travel->hotelId); + $this->assertSame([], $travel->rooms); + $this->assertArrayHasKey(188858, $travel->additionalServices); + $this->assertArrayHasKey(188850, $travel->transportationServices); + $this->assertArrayHasKey(873, $travel->pickups); + } + public function testParseAdditionalServicesUsesTerminLevelOnly(): void { $xmlContent = '