feat: harden xml parsing to avoid uncaught errors
This commit is contained in:
1 parent
3849ae306c
commit
3234c78f0d
9 files changed
+229
-147
No files matched your search
@@ -49,36 +49,40 @@ class TravelParser extends AbstractParser
|
||||
*/
|
||||
public function parse(Crawler $node, ?int $hotelId = null): Travel
|
||||
{
|
||||
if (0 === $node->count()) {
|
||||
throw new \InvalidArgumentException('Cannot parse travel from empty XML node.');
|
||||
}
|
||||
|
||||
$hotelNode = $this->getHotelNode($node, $hotelId);
|
||||
|
||||
$dateFrom = $this->stringToDate($node->attr('termin'));
|
||||
$dateTo = $this->stringToDate($node->attr('bis'));
|
||||
$dateFrom = $this->stringToDate($this->getRequiredAttrValue($node, 'termin', 'travel termin node'));
|
||||
$dateTo = $this->stringToDate($this->getRequiredAttrValue($node, 'bis', 'travel termin node'));
|
||||
|
||||
$travel = new Travel();
|
||||
$travel->id = (int) $node->attr('idbuspro');
|
||||
$travel->hotelId = (int) $hotelNode->attr('idbuspro');
|
||||
$travel->label = $this->getStringOrNullValue($node->filterXPath('//text'));
|
||||
$travel->id = (int) $this->getRequiredAttrValue($node, 'idbuspro', 'travel termin node');
|
||||
$travel->hotelId = (int) $this->getRequiredAttrValue($hotelNode, 'idbuspro', 'travel hotel node');
|
||||
$travel->label = $this->getStringOrNullValue($node->filterXPath('.//text'));
|
||||
$travel->dateFrom = $dateFrom;
|
||||
$travel->dateTo = $dateTo;
|
||||
$travel->code = $node->attr('code');
|
||||
$travel->code = $this->getAttrOrNullValue($node, 'code');
|
||||
|
||||
// Parse product code from parent reise node using DOM
|
||||
$domNode = $node->getNode(0);
|
||||
if (null !== $domNode && null !== $domNode->parentNode) {
|
||||
if (null !== $domNode && $domNode->parentNode instanceof \DOMElement) {
|
||||
$travel->productCode = $domNode->parentNode->getAttribute('code');
|
||||
}
|
||||
|
||||
$travel->type = $node->attr('reiseart');
|
||||
$travel->priceFrom = $this->stringToFloat($this->getStringOrNullValue($node->filterXPath('//abpreis')));
|
||||
$travel->selectionGroups = $this->getSelectionGroups($node->filterXPath('//selektiongruppe'));
|
||||
$travel->type = $this->getAttrOrNullValue($node, 'reiseart');
|
||||
$travel->priceFrom = $this->stringToFloat($this->getStringOrNullValue($node->filterXPath('.//abpreis')));
|
||||
$travel->selectionGroups = $this->getSelectionGroups($node->filterXPath('.//selektiongruppe'));
|
||||
// Parse only termin-level additional services; exclude hotel-nested duplicates that can override flags.
|
||||
$travel->additionalServices = $this
|
||||
->getAdditionalServices($node->filterXPath('.//lei_sonstiges[not(ancestor::hotel)]/leistung'));
|
||||
$travel->transportationServices = $this
|
||||
->getTransportationServices($node->filterXPath('//lei_befoerderung/leistung'));
|
||||
->getTransportationServices($node->filterXPath('.//lei_befoerderung/leistung'));
|
||||
$travel->rooms = $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);
|
||||
$travel->pickups = $this->getPickups($node->filterXPath('.//zustiege/zustieg'), $dateFrom, true);
|
||||
$travel->dropOffs = $this->getPickups($node->filterXPath('.//zustiege_rueck/zustieg_rueck'), $dateTo, false);
|
||||
|
||||
// Merge drop-off prices into pickups for split pricing support
|
||||
$this->mergeDropOffPricesIntoPickups($travel->pickups, $travel->dropOffs);
|
||||
@@ -103,19 +107,19 @@ class TravelParser extends AbstractParser
|
||||
$selectionGroups = [];
|
||||
|
||||
$node->each(function (Crawler $groupNode) use (&$selectionGroups) {
|
||||
$groupId = (int) $groupNode->attr('idbuspro');
|
||||
$groupId = (int) $this->getRequiredAttrValue($groupNode, 'idbuspro', 'selection group node');
|
||||
$selectionGroup = new CrmSelectionGroup();
|
||||
$selectionGroup->id = $groupId;
|
||||
$selectionGroup->label = $groupNode->attr('bezeichnung');
|
||||
$selectionGroup->label = $this->getAttrOrNullValue($groupNode, 'bezeichnung');
|
||||
|
||||
$groupNode
|
||||
->filterXPath('//selektion')
|
||||
->filterXPath('.//selektion')
|
||||
->each(function (Crawler $selectionNode) use (&$selectionGroups, &$selectionGroup, $groupId) {
|
||||
$selectionId = (int) $selectionNode->attr('idbuspro');
|
||||
$selectionId = (int) $this->getRequiredAttrValue($selectionNode, 'idbuspro', 'selection node');
|
||||
$selection = new CrmSelection();
|
||||
$selection->id = $selectionId;
|
||||
$selection->label = $selectionNode->attr('bezeichnung');
|
||||
$selectionGroups[$groupId]['selections'][$selectionId] = $selectionNode->attr('bezeichnung');
|
||||
$selection->label = $this->getAttrOrNullValue($selectionNode, 'bezeichnung');
|
||||
$selectionGroups[$groupId]['selections'][$selectionId] = $this->getAttrOrNullValue($selectionNode, 'bezeichnung');
|
||||
$selectionGroup->selections[] = $selection;
|
||||
})
|
||||
;
|
||||
@@ -141,26 +145,26 @@ class TravelParser extends AbstractParser
|
||||
$additionalServices = [];
|
||||
|
||||
$node->each(function (Crawler $serviceNode) use (&$additionalServices) {
|
||||
$serviceId = (int) $serviceNode->attr('idbuspro');
|
||||
$serviceId = (int) $this->getRequiredAttrValue($serviceNode, 'idbuspro', 'additional service node');
|
||||
|
||||
$service = new Service();
|
||||
$service->source = Constants::SOURCE_TRAVEL;
|
||||
$service->category = Constants::CATEGORY_ADDITIONAL;
|
||||
$service->id = $serviceId;
|
||||
$service->subType = $serviceNode->attr('unterart');
|
||||
$service->mandatory = $this->stringToBool($serviceNode->attr('pflicht'));
|
||||
$autoBookValue = $serviceNode->attr('automatisch_buchen');
|
||||
$service->subType = $this->getAttrOrNullValue($serviceNode, 'unterart');
|
||||
$service->mandatory = $this->stringToBool($this->getAttrOrNullValue($serviceNode, 'pflicht'));
|
||||
$autoBookValue = $this->getAttrOrNullValue($serviceNode, 'automatisch_buchen');
|
||||
$service->autoBook = $this->stringToBool($autoBookValue)
|
||||
|| 'true' === strtolower((string) $autoBookValue);
|
||||
$service->dateFrom = $this->stringToDate($serviceNode->attr('termin'));
|
||||
$service->dateTo = $this->stringToDate($serviceNode->attr('bis'));
|
||||
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('//text'));
|
||||
$service->dateFrom = $this->stringToDate($this->getAttrOrNullValue($serviceNode, 'termin'));
|
||||
$service->dateTo = $this->stringToDate($this->getAttrOrNullValue($serviceNode, 'bis'));
|
||||
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('.//text'));
|
||||
$service->price = $this
|
||||
->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('//preis')));
|
||||
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('//status'));
|
||||
->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('.//preis')));
|
||||
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('.//status'));
|
||||
|
||||
// Parse optional description from hinweis node
|
||||
$description = $this->getStringOrNullValue($serviceNode->filterXPath('//hinweis'));
|
||||
$description = $this->getStringOrNullValue($serviceNode->filterXPath('.//hinweis'));
|
||||
if (null !== $description && '' !== trim($description)) {
|
||||
$service->description = $description;
|
||||
}
|
||||
@@ -169,7 +173,7 @@ class TravelParser extends AbstractParser
|
||||
$this->parseServiceAgeConstraints($serviceNode, $service);
|
||||
|
||||
// Parse insurance calculation flag
|
||||
$service->includeInInsuranceCalculation = $this->stringToBool($serviceNode->attr('versicherungsberechnung'));
|
||||
$service->includeInInsuranceCalculation = $this->stringToBool($this->getAttrOrNullValue($serviceNode, 'versicherungsberechnung'));
|
||||
|
||||
$additionalServices[$serviceId] = $service;
|
||||
});
|
||||
@@ -192,34 +196,34 @@ class TravelParser extends AbstractParser
|
||||
$transportationServices = [];
|
||||
|
||||
$node->each(function (Crawler $serviceNode) use (&$transportationServices) {
|
||||
$serviceId = (int) $serviceNode->attr('idbuspro');
|
||||
$serviceId = (int) $this->getRequiredAttrValue($serviceNode, 'idbuspro', 'transportation service node');
|
||||
|
||||
$service = new Service();
|
||||
$service->source = Constants::SOURCE_TRAVEL;
|
||||
$service->category = Constants::CATEGORY_TRANSPORTATION;
|
||||
$service->id = $serviceId;
|
||||
$service->subType = $serviceNode->attr('unterart');
|
||||
$service->dateFrom = $this->stringToDate($serviceNode->attr('termin'));
|
||||
$service->dateTo = $this->stringToDate($serviceNode->attr('bis'));
|
||||
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('//text'));
|
||||
$service->direction = $this->getStringOrNullValue($serviceNode->filterXPath('//richtung'));
|
||||
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('//status'));
|
||||
$service->subType = $this->getAttrOrNullValue($serviceNode, 'unterart');
|
||||
$service->dateFrom = $this->stringToDate($this->getAttrOrNullValue($serviceNode, 'termin'));
|
||||
$service->dateTo = $this->stringToDate($this->getAttrOrNullValue($serviceNode, 'bis'));
|
||||
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('.//text'));
|
||||
$service->direction = $this->getStringOrNullValue($serviceNode->filterXPath('.//richtung'));
|
||||
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('.//status'));
|
||||
$service->price = $this
|
||||
->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('//preis')));
|
||||
->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('.//preis')));
|
||||
|
||||
// Parse optional description from hinweis node
|
||||
$description = $this->getStringOrNullValue($serviceNode->filterXPath('//hinweis'));
|
||||
$description = $this->getStringOrNullValue($serviceNode->filterXPath('.//hinweis'));
|
||||
if (null !== $description && '' !== trim($description)) {
|
||||
$service->description = $description;
|
||||
}
|
||||
|
||||
if (null !== $timeFrom = $serviceNode->attr('uhrzeit_von')) {
|
||||
if (null !== $timeFrom = $this->getAttrOrNullValue($serviceNode, 'uhrzeit_von')) {
|
||||
$service->timeFrom = $timeFrom;
|
||||
$service->dayTime = (new DayTimeUtility())->mapTime($timeFrom);
|
||||
}
|
||||
|
||||
// Parse insurance calculation flag
|
||||
$service->includeInInsuranceCalculation = $this->stringToBool($serviceNode->attr('versicherungsberechnung'));
|
||||
$service->includeInInsuranceCalculation = $this->stringToBool($this->getAttrOrNullValue($serviceNode, 'versicherungsberechnung'));
|
||||
|
||||
$transportationServices[$serviceId] = $service;
|
||||
});
|
||||
@@ -246,8 +250,8 @@ class TravelParser extends AbstractParser
|
||||
$guideNode = $guideNodes->first();
|
||||
|
||||
$guide = new Guide();
|
||||
$guide->name = $this->getStringOrNullValue($guideNode->filterXPath('//name'));
|
||||
$guide->phone = $this->getStringOrNullValue($guideNode->filterXPath('//telefon'));
|
||||
$guide->name = $this->getStringOrNullValue($guideNode->filterXPath('.//name'));
|
||||
$guide->phone = $this->getStringOrNullValue($guideNode->filterXPath('.//telefon'));
|
||||
|
||||
return $guide;
|
||||
}
|
||||
@@ -321,10 +325,20 @@ class TravelParser extends AbstractParser
|
||||
{
|
||||
// In case not hotel id is provided, take the first hotel node (which is most probably the only one)
|
||||
if (null === $hotelId) {
|
||||
return $node->filterXPath('//hotel')->first();
|
||||
$hotelNode = $node->filterXPath('.//hotel');
|
||||
|
||||
if (0 === $hotelNode->count()) {
|
||||
throw new \InvalidArgumentException('No hotel node found in travel XML.');
|
||||
}
|
||||
|
||||
return $hotelNode->first();
|
||||
}
|
||||
|
||||
$hotelNode = $node->filterXPath(sprintf('//hotel[@idbuspro="%d"]', $hotelId));
|
||||
$hotelNode = $node->filterXPath(sprintf('.//hotel[@idbuspro="%d"]', $hotelId));
|
||||
|
||||
if (0 === $hotelNode->count()) {
|
||||
throw new \InvalidArgumentException(sprintf('No hotel node found for idbuspro %d.', $hotelId));
|
||||
}
|
||||
|
||||
return $hotelNode->first();
|
||||
}
|
||||
@@ -341,7 +355,7 @@ class TravelParser extends AbstractParser
|
||||
*/
|
||||
public function getRooms(Crawler $node): array
|
||||
{
|
||||
$roomNodes = $node->filterXPath('//zimmer/preis');
|
||||
$roomNodes = $node->filterXPath('.//zimmer/preis');
|
||||
|
||||
if (0 === $roomNodes->count()) {
|
||||
return [];
|
||||
@@ -350,27 +364,29 @@ class TravelParser extends AbstractParser
|
||||
$rooms = [];
|
||||
|
||||
$roomNodes->each(function (Crawler $roomNode) use (&$rooms) {
|
||||
$roomCode = $roomNode->attr('zimmercode');
|
||||
$roomCode = $this->getAttrOrNullValue($roomNode, 'zimmercode');
|
||||
|
||||
if (true === in_array($roomCode, $this->ignoredRoomCodes, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$roomId = (int) $roomNode->attr('idbuspro_zimmer');
|
||||
$roomId = (int) $this->getRequiredAttrValue($roomNode, 'idbuspro_zimmer', 'room price node');
|
||||
|
||||
$room = new Room();
|
||||
$room->id = $roomId;
|
||||
$room->code = $roomCode;
|
||||
$room->category = $roomNode->attr('kat');
|
||||
$room->boardId = (int) $roomNode->attr('idbuspro_vp');
|
||||
$room->label = $roomNode->attr('zimmertext');
|
||||
$room->minPax = (int) $roomNode->attr('minpax');
|
||||
$room->maxPax = (int) $roomNode->attr('maxpax');
|
||||
$room->nights = (int) $roomNode->attr('naechte');
|
||||
$room->price = $roomNode->attr('preis') ?
|
||||
$this->stringToFloat($roomNode->attr('preis')) : null;
|
||||
$room->status = $roomNode->attr('status');
|
||||
$room->available = (int) $roomNode->attr('verfuegbar');
|
||||
$room->category = $this->getAttrOrNullValue($roomNode, 'kat');
|
||||
$room->boardId = (int) $this->getAttrOrNullValue($roomNode, 'idbuspro_vp');
|
||||
$room->label = $this->getAttrOrNullValue($roomNode, 'zimmertext');
|
||||
$room->minPax = (int) $this->getAttrOrNullValue($roomNode, 'minpax');
|
||||
$room->maxPax = (int) $this->getAttrOrNullValue($roomNode, 'maxpax');
|
||||
$room->nights = (int) $this->getAttrOrNullValue($roomNode, 'naechte');
|
||||
$roomPrice = $this->getAttrOrNullValue($roomNode, 'preis');
|
||||
$room->price = null !== $roomPrice && '' !== $roomPrice
|
||||
? $this->stringToFloat($roomPrice)
|
||||
: null;
|
||||
$room->status = $this->getAttrOrNullValue($roomNode, 'status');
|
||||
$room->available = (int) $this->getAttrOrNullValue($roomNode, 'verfuegbar');
|
||||
|
||||
$rooms[$roomId] = $room;
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user