File indexing completed on 2024-12-29 04:51:03
0001 /* 0002 SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 function isHeaderOrFooter(line) { 0008 return line.search(/(Ihre Reiseverbindung|Wichtige Nutzungshinweise|Hinweise:|Seite \d \/ \d)/) >= 0; 0009 } 0010 0011 function parseSeat(res, text) { 0012 var coach = text.match(/Wg. (\d+)/); 0013 if (coach) 0014 res.reservedTicket.ticketedSeat.seatSection = coach[1]; 0015 var seat = text.match(/Pl. ([\d ]+\d)/); 0016 if (seat) 0017 res.reservedTicket.ticketedSeat.seatNumber = seat[1]; 0018 } 0019 0020 function parseDeparture(res, line, year, compact) { 0021 var dep = line.match(/^(.+?) *([0-9]{2})\.([0-9]{2})\. +ab ([0-9]{2}:[0-9]{2})/); 0022 if (!dep) 0023 return false; 0024 0025 res.reservationFor.departureStation.name = dep[1]; 0026 res.reservationFor.departureTime = JsonLd.toDateTime(dep[2] + ' ' + dep[3] + ' ' + year + ' ' + dep[4], "dd MM yyyy hh:mm", "de"); 0027 var idx = dep.index + dep[0].length; 0028 var platform = line.substr(idx).match(/^ {1,3}(.*?)(?=( | IC|$))/); 0029 if (platform) { 0030 idx += platform.index + platform[0].length; 0031 res.reservationFor.departurePlatform = platform[1]; 0032 } 0033 var trainId = line.substr(idx).match(compact ? / +([^,]*?)(?=(,|$))/ : / +(.*?)(?=( |$))/); 0034 if (trainId) { 0035 idx += trainId.index + trainId[0].length 0036 res.reservationFor.trainNumber = trainId[1]; 0037 } 0038 parseSeat(res, line.substr(idx)); 0039 return true; 0040 } 0041 0042 function parseArrival(res, line, year) { 0043 var arr = line.match(/^(.+?) *([0-9]{2})\.([0-9]{2})\. +an ([0-9]{2}:[0-9]{2})/); 0044 if (!arr) 0045 return false; 0046 res.reservationFor.arrivalStation.name = arr[1]; 0047 res.reservationFor.arrivalTime = JsonLd.toDateTime(arr[2] + ' ' + arr[3] + ' ' + year + ' ' + arr[4], "dd MM yyyy hh:mm", "de"); 0048 var idx = arr.index + arr[0].length; 0049 var platform = line.substr(idx).match(/^ {1,3}(.*?)(?=( | IC| \d Fenster| \d Gang|$))/); 0050 if (platform) { 0051 idx += platform.index + platform[0].length; 0052 res.reservationFor.arrivalPlatform = platform[1]; 0053 } 0054 parseSeat(res, line.substr(idx)); 0055 return true; 0056 } 0057 0058 function parseLegs(text, year, compact) { 0059 var reservations = new Array(); 0060 var lines = text.split('\n'); 0061 var offset = lines[0].match(/^ */); 0062 for (var i = 0; compact && i < lines.length; ++i) 0063 lines[i] = lines[i].substr(offset[0].length); 0064 0065 for (var i = 0; i < lines.length;) { 0066 // stop when reaching the footer or the next itinerary header 0067 if (isHeaderOrFooter(lines[i])) 0068 return reservations; 0069 0070 var res = JsonLd.newTrainReservation(); 0071 while (i < lines.length && !isHeaderOrFooter(lines[i])) { 0072 if (parseDeparture(res, lines[i++], year, compact)) 0073 break; 0074 } 0075 while (i < lines.length && !isHeaderOrFooter(lines[i])) { 0076 if (parseArrival(res, lines[i], year)) { 0077 ++i; 0078 break; 0079 } 0080 0081 // continuation of departure line 0082 var depStation = lines[i].match(/^(\S.*?)(?: |\n|$)/) 0083 if (depStation) 0084 res.reservationFor.departureStation.name = res.reservationFor.departureStation.name + " " + depStation[1]; 0085 parseSeat(res, lines[i]); 0086 0087 ++i; 0088 } 0089 // handle continuations of the arrival line 0090 while (i < lines.length && !isHeaderOrFooter(lines[i])) { 0091 if (lines[i].match(/^\S.+? *[0-9]{2}\.[0-9]{2}\. +ab [0-9]{2}:[0-9]{2}/)) // next departure line 0092 break; 0093 0094 // continuation of arrival line 0095 var arrStation = lines[i].match(/^(\S.*?)(?: |\n|$)/) 0096 if (arrStation) 0097 res.reservationFor.arrivalStation.name = res.reservationFor.arrivalStation.name + " " + arrStation[1]; 0098 parseSeat(res, lines[i]); 0099 0100 ++i; 0101 } 0102 0103 if (res.reservationFor.arrivalStation != undefined) { 0104 reservations.push(res); 0105 } else { 0106 ++i; 0107 } 0108 } 0109 0110 return reservations; 0111 } 0112 0113 function parseText(text) { // used by unit tests 0114 return parseTicket(text, null); 0115 } 0116 0117 function parseTicket(text, uic918ticket) { 0118 var reservations = new Array(); 0119 var pos = 0; 0120 var returnResIndex = 0; 0121 while (true) { 0122 // find itinerary headers 0123 var header = text.substr(pos).match(/Ihre Reiseverbindung[\S ]+(Hin|Rück|Einfache )[fF]ahrt am [0-9]{2}.[0-9]{2}.([0-9]{4}).*\n/); 0124 if (!header) 0125 break; 0126 var idx = header.index + header[0].length; 0127 var year = header[2]; 0128 0129 // determine ticket type 0130 var domesticHeader = text.substr(pos + idx).match(/ Reservierung(?: \/ Hinweise)?\n/); 0131 var intlHeader = text.substr(pos + idx).match(/(Produkte\/Reservierung|Fahrt\/Reservierung).*\n/); 0132 if (domesticHeader) { 0133 idx += domesticHeader.index + domesticHeader[0].length; 0134 reservations = reservations.concat(parseLegs(text.substr(pos + idx), year, false)); 0135 } else if (intlHeader) { 0136 idx += intlHeader.index + intlHeader[0].length; 0137 reservations = reservations.concat(parseLegs(text.substr(pos + idx), year, true)); 0138 } else { 0139 break; 0140 } 0141 0142 // for outward journeys we have station ids from the UIC 918-3 code 0143 if (uic918ticket && header[1] !== "Rück") { 0144 reservations[0].reservationFor.departureStation = JsonLd.apply(JsonLd.toJson(uic918ticket.outboundDepartureStation), reservations[0].reservationFor.departureStation); 0145 reservations[reservations.length - 1].reservationFor.arrivalStation = JsonLd.apply(JsonLd.toJson(uic918ticket.outboundArrivalStation), reservations[reservations.length - 1].reservationFor.arrivalStation); 0146 returnResIndex = reservations.length; 0147 } else { 0148 // propagate station ids from outward to return journey 0149 for (var i = returnResIndex; i < reservations.length; ++i) { 0150 for (var j = 0; j < returnResIndex; ++j) { 0151 if (reservations[i].reservationFor.departureStation.name === reservations[j].reservationFor.arrivalStation.name) 0152 reservations[i].reservationFor.departureStation.identifier = reservations[j].reservationFor.arrivalStation.identifier; 0153 if (reservations[i].reservationFor.arrivalStation.name === reservations[j].reservationFor.departureStation.name) 0154 reservations[i].reservationFor.arrivalStation.identifier = reservations[j].reservationFor.departureStation.identifier; 0155 } 0156 } 0157 } 0158 0159 if (idx == 0) 0160 break; 0161 pos += idx; 0162 } 0163 0164 // international tickets have the booking reference somewhere on the side, so we don't really know 0165 // where it is relative to the itinerary 0166 const bookingRef = text.match(/(?:Auftragsnummer|Auftrag \(NVS\)):\s*([A-Z0-9]{6,9}|\d{12})\n/); 0167 const price = text.match(/(?:Summe|Gesamtpreis) *(\d+,\d{2} ?€)/)[1]; 0168 for (var i = 0; i < reservations.length; ++i) { 0169 if (bookingRef) { 0170 reservations[i].reservationNumber = bookingRef[1]; 0171 } 0172 0173 if (reservations[i].reservationFor.trainNumber && reservations[i].reservationFor.trainNumber.startsWith("Bus")) { 0174 reservations[i] = JsonLd.trainToBusReservation(reservations[i]); 0175 reservations[i].reservedTicket.ticketedSeat = undefined; 0176 } 0177 ExtractorEngine.extractPrice(price, reservations[i]); 0178 } 0179 return reservations; 0180 } 0181 0182 function parseReservation(pdf) { 0183 var text = pdf.text; 0184 var reservations = Array(); 0185 var idx = 0; 0186 0187 while (true) { 0188 var dep = text.substr(idx).match(/ (\S.*\S) +(\d\d.\d\d.\d\d) +ab (\d\d:\d\d) +(.*?) +([A-Z].*\S) +(.*)\n/); 0189 if (!dep) 0190 break; 0191 idx += dep.index + dep[0].length; 0192 var arr = text.substr(idx).match(/ (\S.*\S) +an (\d\d:\d\d) +(.*?) +(.*)\n/); 0193 if (!arr) 0194 break; 0195 0196 var res = JsonLd.newTrainReservation(); 0197 res.reservationFor.departureStation.name = dep[1]; 0198 res.reservationFor.departureTime = JsonLd.toDateTime(dep[2] + dep[3], "dd.MM.yyhh:mm", "de"); 0199 res.reservationFor.trainNumber = dep[5]; 0200 res.reservationFor.departurePlatform = dep[4]; 0201 res.reservationFor.arrivalStation.name = arr[1]; 0202 res.reservationFor.arrivalTime = JsonLd.toDateTime(dep[2] + arr[2], "dd.MM.yyhh:mm", "de"); 0203 res.reservationFor.arrivalPlatform = arr[3]; 0204 reservations.push(res); 0205 0206 seatText = dep[6] + " " + arr[4]; 0207 var seat = seatText.match(/(\d)\. Klasse, Wg\. (\d+), Pl. (.*?),/); 0208 if (seat) { 0209 res.reservedTicket.ticketedSeat.seatingType = seat[1]; 0210 res.reservedTicket.ticketedSeat.seatSection = seat[2]; 0211 res.reservedTicket.ticketedSeat.seatNumber = seat[3]; 0212 } 0213 0214 idx += arr.index + arr[0].length; 0215 } 0216 0217 ExtractorEngine.extractPrice(text, reservations); 0218 return reservations; 0219 } 0220 0221 function applyUic9183ToReservation(res, uicNode) 0222 { 0223 const uicCode = uicNode.content; 0224 if (!res.reservationNumber || res.reservationNumber == uicCode.pnr) 0225 res.reservationNumber = uicCode.pnr; 0226 else 0227 res.reservedTicket.ticketNumber = uicCode.pnr; 0228 res.reservationFor.provider = JsonLd.toJson(uicCode.issuer); 0229 if (uicNode.result.length > 0 && uicNode.result[0].programMembershipUsed) 0230 res.programMembershipUsed = uicNode.result[0].programMembershipUsed; 0231 const bl = uicCode.block('0080BL'); 0232 if (bl) { 0233 const sb = bl.findSubBlock('009'); 0234 if (sb) { 0235 const bc = sb.content.match(/\d+-\d+-(.*)/)[1]; 0236 switch (bc) { 0237 case "49": 0238 res.programMembershipUsed.programName = "BahnCard 25"; 0239 break; 0240 case "19": 0241 case "78": 0242 res.programMembershipUsed.programName = "BahnCard 50"; 0243 break; 0244 } 0245 } 0246 } 0247 res.reservedTicket.name = uicCode.name 0248 if (res.reservedTicket.ticketedSeat) 0249 res.reservedTicket.ticketedSeat.seatingType = uicCode.seatingType; 0250 res.underName = JsonLd.toJson(uicCode.person); 0251 } 0252 0253 function parsePdf(pdf, node, triggerNode) { 0254 var page = pdf.pages[triggerNode.location]; 0255 var uic918ticket = triggerNode.mimeType == "internal/uic9183" ? triggerNode.content : null; 0256 0257 var reservations = parseTicket(page.text, uic918ticket); 0258 for (var i = 0; i < reservations.length; ++i) { 0259 reservations[i].reservedTicket.ticketToken = "aztecbin:" + ByteArray.toBase64(triggerNode.content.rawData); 0260 if (triggerNode.mimeType == "internal/uic9183") { 0261 applyUic9183ToReservation(reservations[i], triggerNode); 0262 } else if (triggerNode.mimeType == "internal/vdv") { 0263 reservations[i].reservationFor.provider.identifier = "vdv:" + triggerNode.content.operatorId; 0264 if (reservations[i].reservedTicket.ticketedSeat) { 0265 const c = triggerNode.content.seatingClass; 0266 reservations[i].reservedTicket.ticketedSeat.seatingType = (c == 1 || c == 3) ? '1' : '2'; 0267 } 0268 reservations[i].underName = JsonLd.toJson(triggerNode.content.person); 0269 } 0270 } 0271 0272 return reservations; 0273 } 0274 0275 function parseCancellation(html) { 0276 var title = html.eval('//title')[0]; 0277 var pnr = title.content.match(/Stornierungsbestätigung \(Auftrag (.*)\)/); 0278 if (!pnr) 0279 return null; 0280 var res = JsonLd.newTrainReservation(); 0281 res.reservationNumber = pnr[1]; 0282 res.reservationStatus = "ReservationCancelled"; 0283 res.reservationFor = 1; 0284 return res; 0285 } 0286 0287 function parseUic9183(code, node) { 0288 // Bahncard code 0289 if (code.ticketLayout && code.ticketLayout.type == "RCT2" && code.ticketLayout.text(0, 12, 40, 1).match(/BAHNCARD/i)) { 0290 var bc = JsonLd.newObject("ProgramMembership"); 0291 bc.programName = code.ticketLayout.text(1, 12, 40, 1); 0292 bc.membershipNumber = code.ticketLayout.text(14, 11, 16, 1); 0293 bc.member = JsonLd.toJson(code.person); 0294 bc.token = 'aztecbin:' + ByteArray.toBase64(code.rawData); 0295 bc.validFrom = JsonLd.readQDateTime(code, 'validFrom'); 0296 bc.validUntil = JsonLd.readQDateTime(code, 'validUntil'); 0297 return bc.programName != undefined ? bc : undefined; 0298 } 0299 0300 // domestic ticket code 0301 const bl = code.block('0080BL'); 0302 if (bl && code.outboundDepartureStation.name && code.outboundArrivalStation.name) { 0303 let res = JsonLd.newTrainReservation(); 0304 res.reservedTicket = node.result[0]; 0305 applyUic9183ToReservation(res, node); 0306 res.reservationFor.departureDay = JsonLd.toDateTime(bl.findSubBlock('031').content, 'dd.MM.yyyy', 'de'); 0307 res.reservationFor.departureStation = JsonLd.toJson(code.outboundDepartureStation); 0308 res.reservationFor.arrivalStation = JsonLd.toJson(code.outboundArrivalStation); 0309 0310 if (!bl.findSubBlock('017')) { 0311 return res; 0312 } 0313 0314 let ret = JsonLd.newTrainReservation(); 0315 ret.reservedTicket = node.result[0]; 0316 applyUic9183ToReservation(ret, node); 0317 ret.reservationFor.departureDay = JsonLd.toDateTime(bl.findSubBlock('032').content, 'dd.MM.yyyy', 'de'); 0318 ret.reservationFor.departureStation = JsonLd.toJson(code.returnDepartureStation); 0319 ret.reservationFor.arrivalStation = JsonLd.toJson(code.returnArrivalStation); 0320 return [res, ret]; 0321 } 0322 } 0323 0324 function parseEvent(event) { 0325 let res = JsonLd.newTrainReservation(); 0326 const names = event.summary.match(/(.*) -> (.*)/); 0327 res.reservationFor.departureStation.name = names[1]; 0328 res.reservationFor.departureTime = JsonLd.readQDateTime(event, 'dtStart'); 0329 res.reservationFor.arrivalStation.name = names[2]; 0330 res.reservationFor.arrivalTime = JsonLd.readQDateTime(event, 'dtEnd'); 0331 0332 // search for more details in the description 0333 let reservations = []; 0334 let idx = 0; 0335 while (true) { 0336 const trip = event.description.substr(idx).match(/(\d{2}:\d{2}) (.*?)(?:- (?:Gleis|platform|voie|Vía|Spor|Kolej|binario|Peron) (.*?))?(?: \((.*\d+)\))?\n.* (\d{2}:\d{2}) (.*?)(?:\n| - (?:Gleis|platform|voie|Vía|Spor|Kolej|binario|Peron) (.*)\n)/); 0337 if (!trip) { 0338 break; 0339 } 0340 idx += trip.index + trip[0].length; 0341 0342 let res = JsonLd.newTrainReservation(); 0343 const date = JsonLd.readQDateTime(event, 'dtStart')['@value'].substr(0, 10); 0344 res.reservationFor.departureStation.name = trip[2]; 0345 res.reservationFor.departureTime = JsonLd.toDateTime(date + trip[1], 'yyyy-MM-ddhh:mm', 'de'); 0346 res.reservationFor.departurePlatform = trip[3]; 0347 res.reservationFor.trainName = trip[4]; 0348 res.reservationFor.arrivalStation.name = trip[6]; 0349 res.reservationFor.arrivalTime = JsonLd.toDateTime(date + trip[5], 'yyyy-MM-ddhh:mm', 'de'); 0350 res.reservationFor.arrivalPlatform = trip[7]; 0351 0352 if (trip[4] && trip[4].match(/^Bus[ \d]/)) { 0353 res = JsonLd.trainToBusReservation(res); 0354 } 0355 0356 reservations.push(res); 0357 } 0358 // recover full timezones for the begin/end 0359 if (reservations.length > 0) { 0360 reservations[0].reservationFor.departureTime = res.reservationFor.departureTime; 0361 reservations[reservations.length - 1].reservationFor.arrivalTime = res.reservationFor.arrivalTime; 0362 return reservations; 0363 } 0364 0365 return res; 0366 } 0367 0368 function parseDBRegioBusUic(uic, node) 0369 { 0370 let ticket = node.result[0]; 0371 if (uic.ticketLayout.type != 'PLAI' || ticket.name) 0372 return; 0373 0374 ticket.name = uic.ticketLayout.firstField.text; 0375 for (let f = uic.ticketLayout.firstField; f && !f.isNull; f = f.next) { 0376 const validFrom = f.text.match(/Erster Gültigkeitstag: (.*)/); 0377 if (validFrom) { 0378 ticket.validFrom = JsonLd.toDateTime(validFrom[1], 'dd.MM.yyyy', 'de'); 0379 } 0380 const name = f.text.match(/Name Fahrtberechtigter: (.*)/); 0381 if (name) { 0382 ticket.underName = JsonLd.newObject('Person'); 0383 ticket.underName.name = name[1]; 0384 } 0385 } 0386 if (ticket.name.includes("Deutschland-Ticket") && !ticket.validUntil) { 0387 ticket.validUntil = JsonLd.clone(ticket.validFrom) 0388 ticket.validUntil.setMonth(ticket.validUntil.getMonth() + 1) 0389 ticket.validUntil.setHours(3) 0390 } 0391 return ticket; 0392 }