File indexing completed on 2024-12-29 04:51:06
0001 /* 0002 SPDX-FileCopyrightText: 2017-2022 Volker Krause <vkrause@kde.org> 0003 SPDX-License-Identifier: LGPL-2.0-or-later 0004 */ 0005 0006 // see https://community.kde.org/KDE_PIM/KItinerary/SNCF_Barcodes#Tariff_Codes 0007 const tariffs = { 0008 'CF00': 'Ayant Droit Avec Fichet', 0009 'CF90': 'Ayant Droit Sans Fichet', 0010 'CJ11': 'Carte Jeune', 0011 'CW00': 'Carte Advantage Adulte', 0012 'CW11': 'Carte Advantage Adulte', 0013 'CW12': 'Carte Advantage Adulte', 0014 'CW25': 'Carte Advantage Adulte', 0015 'EF11': 'Carte Enfant+', 0016 'EF99': 'Carte Enfant+', 0017 'IR00': 'Interrail', 0018 'IR01': 'Interrail', 0019 'JE00': 'Carte Jeune', 0020 'SE11': 'Carte Advantage Senior', 0021 'SR50': 'Carte Senior' 0022 }; 0023 0024 function parseSncfPdfText(text) { 0025 var reservations = new Array(); 0026 const bookingRef = text.match(/(?:DOSSIER VOYAGE|BOOKING FILE REFERENCE|REFERENCE NUMBER|REISEREFERENZ) ?: +([A-Z0-9]{6})/); 0027 const price = text.match(/(\d+,\d\d EUR)/); 0028 0029 var pos = 0; 0030 while (true) { 0031 var header = text.substr(pos).match(/ +(?:Départ \/ Arrivée|Departure \/ Arrival|Abfahrt \/ Ankunft).*\n/); 0032 if (!header) 0033 break; 0034 var index = header.index + header[0].length; 0035 0036 var res = JsonLd.newTrainReservation(); 0037 res.reservationNumber = bookingRef[1]; 0038 0039 var depLine = text.substr(pos + index).match(/\n {2,3}([\w -]+?) +(\d{2}[\/\.]\d{2}) (?:à|at|um) (\d{2}[h:]\d{2})/); 0040 if (!depLine) 0041 break; 0042 index += depLine.index + depLine[0].length; 0043 res.reservationFor.departureStation.name = depLine[1]; 0044 res.reservationFor.departureTime = JsonLd.toDateTime(depLine[2] + " " + depLine[3], ["dd/MM hh'h'mm", "dd/MM hh:mm", "dd.MM hh:mm"], "fr"); 0045 0046 var arrLine = text.substr(pos + index).match(/\n {2,3}([\w -]+?) +(\d{2}[\/\.]\d{2}) (?:à|at|um) (\d{2}[h:]\d{2})/); 0047 if (!arrLine) 0048 break; 0049 index += arrLine.index + arrLine[0].length; 0050 res.reservationFor.arrivalStation.name = arrLine[1]; 0051 res.reservationFor.arrivalTime = JsonLd.toDateTime(arrLine[2] + " " + arrLine[3], ["dd/MM hh'h'mm", "dd/MM hh:mm", "dd.MM hh:mm"], "fr"); 0052 0053 // parse seat, train number, etc from the text for one leg 0054 // since the stations are vertically centered, the stuff we are looking for might be at different 0055 // positions relative to them 0056 var legText = text.substring(pos + header.index + header[0].length, pos + index); 0057 var trainNumber = legText.match(/(?:TRAIN N°|TRAIN NUMBER|ZUGNUMMER) ?(\d{3,5})/); 0058 if (trainNumber) 0059 res.reservationFor.trainNumber = trainNumber[1]; 0060 var seatRes = legText.match(/(?:VOITURE|COACH|WAGEN) (\d+) - (?:PLACE|SEAT|PLATZ) (\d+)/); 0061 if (seatRes) { 0062 res.reservedTicket.ticketedSeat.seatSection = seatRes[1]; 0063 res.reservedTicket.ticketedSeat.seatNumber = seatRes[2]; 0064 } 0065 0066 if (price) 0067 ExtractorEngine.extractPrice(price[1], res); 0068 0069 reservations.push(res); 0070 if (index == 0) 0071 break; 0072 pos += index; 0073 } 0074 0075 return reservations; 0076 } 0077 0078 function parseInouiPdfText(page) 0079 { 0080 var reservations = new Array(); 0081 const price = page.text.match(/(\d+,\d\d EUR)/); 0082 var text = page.textInRect(0.0, 0.0, 0.5, 1.0); 0083 0084 var date = text.match(/(\d+\.? [^ ]+ \d{4})\n/) 0085 if (!date) 0086 return reservations; 0087 var pos = date.index + date[0].length; 0088 while (true) { 0089 var dep = text.substr(pos).match(/(\d{2}[h:]\d{2}) +(.*)\n/); 0090 if (!dep) 0091 break; 0092 pos += dep.index + dep[0].length; 0093 0094 var res = JsonLd.newTrainReservation(); 0095 res.reservationFor.departureTime = JsonLd.toDateTime(date[1] + dep[1], ["d MMMM yyyyhh'h'mm", "dd MMMM yyyyhh:mm", "dd. MMMM yyyyhh:mm"], ["fr", "en", "de"]); 0096 res.reservationFor.departureStation.name = dep[2]; 0097 0098 var arr = text.substr(pos).match(/(\d{2}[h:]\d{2}) +(.*)\n/); 0099 if (!arr) 0100 break; 0101 var endPos = arr.index + arr[0].length; 0102 res.reservationFor.arrivalTime = JsonLd.toDateTime(date[1] + arr[1], ["d MMMM yyyyhh'h'mm", "dd MMMM yyyyhh:mm", "dd. MMMM yyyyhh:mm"], ["fr", "en", "de"]); 0103 res.reservationFor.arrivalStation.name = arr[2]; 0104 0105 var detailsText = text.substr(pos, endPos - arr[0].length); 0106 var train = detailsText.match(/^ *(.*?) *-/); 0107 res.reservationFor.trainNumber = train[1]; 0108 var seat = detailsText.match(/(?:Voiture|Coach|Wagen) *(\d+) *(?:Place|Seat|Platz) *(\d+)/); 0109 if (seat) { 0110 res.reservedTicket.ticketedSeat.seatSection = seat[1]; 0111 res.reservedTicket.ticketedSeat.seatNumber = seat[2]; 0112 } 0113 0114 if (price) 0115 ExtractorEngine.extractPrice(price[1], res); 0116 0117 reservations.push(res); 0118 if (endPos == 0) 0119 break; 0120 pos += endPos; 0121 } 0122 0123 return reservations; 0124 } 0125 0126 // see https://community.kde.org/KDE_PIM/KItinerary/SNCF_Barcodes 0127 function parseSncfBarcode(barcode) 0128 { 0129 var reservations = new Array(); 0130 0131 var res1 = JsonLd.newTrainReservation(); 0132 res1.reservationNumber = barcode.substr(4, 6); 0133 res1.underName.familyName = barcode.substr(72, 19); 0134 res1.underName.givenName = barcode.substr(91, 19); 0135 res1.reservationFor.departureStation.name = barcode.substr(33, 5); 0136 res1.reservationFor.departureStation.identifier = "sncf:" + barcode.substr(33, 5); 0137 res1.reservationFor.arrivalStation.name = barcode.substr(38, 5); 0138 res1.reservationFor.arrivalStation.identifier = "sncf:" + barcode.substr(38, 5); 0139 res1.reservationFor.departureDay = JsonLd.toDateTime(barcode.substr(48, 5), "dd/MM", "fr"); 0140 res1.reservationFor.trainNumber = barcode.substr(43, 5); 0141 res1.reservedTicket.ticketToken = "aztecCode:" + barcode; 0142 res1.reservedTicket.ticketNumber = barcode.substr(10, 9); 0143 res1.reservedTicket.ticketedSeat.seatingType = barcode.substr(110, 1); 0144 res1.programMembershipUsed.programName = tariffs[barcode.substr(111, 4)]; 0145 reservations.push(res1); 0146 0147 if (barcode.substr(115, 1) != '0') { 0148 var res2 = JsonLd.clone(res1); 0149 res2.reservationFor.departureStation.name = barcode.substr(116, 5); 0150 res2.reservationFor.departureStation.identifier = "sncf:" + barcode.substr(116, 5); 0151 res2.reservationFor.arrivalStation.name = barcode.substr(121, 5); 0152 res2.reservationFor.arrivalStation.identifier = "sncf:" + barcode.substr(121, 5); 0153 res2.reservationFor.trainNumber = barcode.substr(126, 5); 0154 res2.reservedTicket.ticketedSeat.seatingType = barcode.substr(115, 1); 0155 reservations.push(res2); 0156 } 0157 0158 return reservations; 0159 } 0160 0161 function parsePdf(pdf) { 0162 var reservations = new Array(); 0163 0164 var barcode = null; 0165 for (var i = 0; i < pdf.pageCount; ++i) { 0166 var page = pdf.pages[i]; 0167 var nextBarcode = null; 0168 var images = page.images; 0169 for (var j = 0; j < images.length && !nextBarcode; ++j) { 0170 nextBarcode = Barcode.decodeAztec(images[j]); 0171 if (nextBarcode.substr(0, 4) !== "i0CV") 0172 nextBarcode = null; 0173 } 0174 // Guard against tickets with 3 or more legs, with the second page for the 3rd and subsequent 0175 // leg repeating the barcode of the first two legs. One would expect the barcode for the following 0176 // legs there, but that doesn't even seem to exists in the sample documents I have for this... 0177 barcode = (nextBarcode && nextBarcode != barcode) ? nextBarcode : null; 0178 if (barcode) { 0179 var barcodeRes = barcode ? parseSncfBarcode(barcode) : null; 0180 } 0181 0182 var legs = parseSncfPdfText(page.text); 0183 if (legs.length == 0) { 0184 legs = parseInouiPdfText(page); 0185 } 0186 if (legs.length > 0) { 0187 for (var j = 0; j < legs.length; ++j) { 0188 if (barcode && j < barcodeRes.length) { 0189 legs[j] = JsonLd.apply(barcodeRes[j], legs[j]); 0190 } 0191 reservations.push(legs[j]); 0192 } 0193 } else { 0194 reservations = reservations.concat(barcodeRes); 0195 } 0196 } 0197 0198 return reservations; 0199 } 0200 0201 function parseSecutixPdfItineraryV1(text, res) 0202 { 0203 var reservations = new Array(); 0204 var pos = 0; 0205 while (true) { 0206 var dep = text.substr(pos).match(/Départ [^ ]+ (\d+\.\d+\.\d+) à (\d+:\d+) [^ ]+ (.*)\n/); 0207 if (!dep) 0208 break; 0209 pos += dep.index + dep[0].length; 0210 var arr = text.substr(pos).match(/Arrivée [^ ]+ (\d+\.\d+\.\d+) à (\d+:\d+) [^ ]+ (.*)\n\s+(.*)\n/); 0211 if (!arr) 0212 break; 0213 pos += arr.index + arr[0].length; 0214 0215 var leg = JsonLd.newTrainReservation(); 0216 leg.reservationFor.departureStation.name = dep[3]; 0217 leg.reservationFor.departureTime = JsonLd.toDateTime(dep[1] + dep[2], "dd.MM.yyyyhh:mm", "fr"); 0218 leg.reservationFor.arrivalStation.name = arr[3]; 0219 leg.reservationFor.arrivalTime = JsonLd.toDateTime(arr[1] + arr[2], "dd.MM.yyyyhh:mm", "fr"); 0220 leg.reservationFor.trainNumber = arr[4]; 0221 leg.underName = res.underName; 0222 leg.reservationNumber = res.reservationNumber; 0223 leg.reservedTicket = res.reservedTicket; 0224 leg.programMembershipUsed = res.programMembershipUsed; 0225 0226 reservations.push(leg); 0227 } 0228 return reservations; 0229 } 0230 0231 function parseSecutixPdfItineraryV2(text, res) 0232 { 0233 var reservations = new Array(); 0234 var pos = 0; 0235 while (true) { 0236 var data = text.substr(pos).match(/ *(\d+h\d+)\n *(.*)\n *(.*)\n(?: *Voiture (\d+) - Place (\d+)\n.*\n.*\n)? *(\d+h\d+)\n(.*)\n/); 0237 if (!data) 0238 break; 0239 pos += data.index + data[0].length; 0240 0241 var leg = JsonLd.newTrainReservation(); 0242 leg.reservationFor.departureStation.name = data[2]; 0243 leg.reservationFor.departureDay = res.reservationFor.departureDay; 0244 leg.reservationFor.departureTime = JsonLd.toDateTime(data[1], "hh'h'mm", "fr"); 0245 leg.reservationFor.arrivalStation.name = data[7]; 0246 leg.reservationFor.arrivalTime = JsonLd.toDateTime(data[6], "hh'h'mm", "fr"); 0247 leg.reservationFor.trainNumber = data[3]; 0248 leg.underName = res.underName; 0249 leg.reservationNumber = res.reservationNumber; 0250 leg.reservedTicket = res.reservedTicket; 0251 leg.reservedTicket.ticketedSeat.seatSection = data[4]; 0252 leg.reservedTicket.ticketedSeat.seatNumber = data[5]; 0253 leg.programMembershipUsed = res.programMembershipUsed; 0254 0255 reservations.push(leg); 0256 } 0257 return reservations; 0258 } 0259 0260 function parseSecutix(barcode) 0261 { 0262 // see https://community.kde.org/KDE_PIM/KItinerary/SNCF_Barcodes#SNCF_Secutix_Tickets 0263 let res = JsonLd.newTrainReservation(); 0264 const code = ByteArray.decodeLatin1(barcode.slice(260)); 0265 res.reservationFor.provider.identifier = 'uic:' + code.substr(4, 4); 0266 res.reservationNumber = code.substr(8, 9); 0267 res.reservationFor.departureStation.name = code.substr(17, 5); 0268 res.reservationFor.departureStation.identifier = "sncf:" + code.substr(17, 5); 0269 res.reservationFor.arrivalStation.name = code.substr(22, 5); 0270 res.reservationFor.arrivalStation.identifier = "sncf:" + code.substr(22, 5); 0271 res.reservationFor.departureDay = JsonLd.toDateTime(code.substr(83, 8), "ddMMyyyy", "fr"); 0272 res.reservedTicket.ticketedSeat.seatingType = code.substr(91, 1); 0273 res.reservedTicket.ticketNumber = code.substr(8, 9); 0274 res.reservedTicket.ticketToken = "aztecbin:" + ByteArray.toBase64(barcode); 0275 res.underName.familyName = code.substr(116, 19); 0276 res.underName.givenName = code.substr(135, 19); 0277 res.programMembershipUsed.programName = tariffs[code.substr(92, 4)]; 0278 res.reservedTicket.totalPrice = code.substr(226, 10) / 100; 0279 res.reservedTicket.priceCurrency = 'EUR'; 0280 return res; 0281 } 0282 0283 function parseSecutixPdf(pdf, node, triggerNode) 0284 { 0285 let res = triggerNode.result[0]; 0286 const text = pdf.pages[triggerNode.location].text; 0287 let pnr = text.match(res.reservationNumber + '[^\n]* ([A-Z0-9]{6})\n'); 0288 let layoutVersion = 1; 0289 if (!pnr) { 0290 pnr = text.match(/(?:PAO|REF)\s*:\s*([A-Z0-9]{6,8})\n/); 0291 layoutVersion = 2; 0292 } 0293 res.reservationNumber = pnr[1]; 0294 0295 var itineraryText = pdf.pages[triggerNode.location].textInRect(0.0, 0.0, 0.5, 1.0); 0296 var reservations = layoutVersion == 1 ? parseSecutixPdfItineraryV1(itineraryText, res) : parseSecutixPdfItineraryV2(itineraryText, res); 0297 if (reservations.length == 0) 0298 return res; 0299 0300 reservations[0].reservationFor.departureStation.identifier = res.reservationFor.departureStation.identifier; 0301 reservations[reservations.length - 1].reservationFor.arrivalStation.identifier = res.reservationFor.arrivalStation.identifier; 0302 for (r of reservations) { 0303 r.reservationFor.provider = res.reservationFor.provider; 0304 } 0305 0306 return reservations; 0307 } 0308 0309 function parseOuiEmail(html) 0310 { 0311 if (html.eval('//*[@data-select="travel-summary-reference"]').length > 0) { 0312 return parseOuiSummary(html); 0313 } else { 0314 return parseOuiConfirmation(html); 0315 } 0316 } 0317 0318 function parseOuiSummaryTime(htmlElem) 0319 { 0320 var timeStr = htmlElem[0].recursiveContent; 0321 var time = timeStr.match(/(\d+ [^ ]+ \d+) +[^ ]+ (\d+:\d+)/); 0322 if (time) { 0323 return JsonLd.toDateTime(time[1] + time[2], "d MMMM yyyyhh:mm", "fr"); 0324 } 0325 time = timeStr.match(/(\d+\.? [^ ]+(?: \d{4})?) +[^ ]+ +(\d+[:h]\d+)/); 0326 return JsonLd.toDateTime(time[1] + ' ' + time[2].replace('h', ':'), ["d MMMM hh:mm", "d. MMMM yyyy hh:mm"], ["fr", "en", "de"]); 0327 } 0328 0329 function parseOuiSummary(html) 0330 { 0331 // TODO extract passenger names 0332 var res = JsonLd.newTrainReservation(); 0333 const origins = html.eval('//*[@data-select="travel-summary-origin"]'); 0334 res.reservationFor.departureStation.name = origins[0].content; 0335 const destinations = html.eval('//*[@data-select="travel-summary-destination"]'); 0336 res.reservationFor.arrivalStation.name = destinations[0].content; 0337 res.reservationNumber = html.eval('//*[@data-select="travel-summary-reference"]')[0].content; 0338 0339 res.reservationFor.departureTime = parseOuiSummaryTime(html.eval('//*[@data-select="travel-departureDate"]')); 0340 0341 var trainNum = html.eval('//*[@data-select="passenger-detail-outwardFares"]//*[@class="passenger-detail__equipment"]'); 0342 if (trainNum.length == 2 || trainNum[1].content == trainNum[3].content) { 0343 // can occur multiple times for multi-leg journeys or multiple passengers 0344 // we don't have information about connections on multi-leg journeys, so omit the train number in that case 0345 res.reservationFor.trainNumber = trainNum[0].content + " " + trainNum[1].content; 0346 } 0347 0348 const price = html.eval('//*[@class="transaction__total-amount-value"]'); 0349 if (price) 0350 ExtractorEngine.extractPrice(price[0].recursiveContent, res); 0351 0352 // check if this is a return ticket 0353 var retourTime = html.eval('//*[@data-select="travel-returnDate"]'); 0354 if (retourTime.length == 0) { 0355 return res; 0356 } 0357 var retour = JsonLd.newTrainReservation(); 0358 retour.reservationFor.departureStation.name = origins[1] ? origins[1].content : res.reservationFor.arrivalStation.name; 0359 retour.reservationFor.arrivalStation.name = destinations[1] ? destinations[1].content : res.reservationFor.departureStation.name; 0360 retour.reservationFor.departureTime = parseOuiSummaryTime(retourTime); 0361 trainNum = html.eval('//*[@data-select="passenger-detail-inwardFares"]//*[@class="passenger-detail__equipment"]'); 0362 if (trainNum.length == 2 || trainNum[1].content == trainNum[3].content) { 0363 retour.reservationFor.trainNumber = trainNum[0].content + " " + trainNum[1].content; 0364 } 0365 if (price) 0366 ExtractorEngine.extractPrice(price[0].recursiveContent, retour); 0367 0368 return [res, retour]; 0369 } 0370 0371 function parseOuiConfirmation(html) 0372 { 0373 var reservations = new Array(); 0374 0375 var pnr = html.eval('//*[@class="pnr-ref"]/*[@class="pnr-info"]'); 0376 var pnrOuigo = html.eval('//*[@class="pnr-info-digital pnr-info-digital-ouigo"]'); 0377 var passengerName = html.eval('//*[@class="passenger"]/*[@class="name"]'); 0378 0379 var productDts = html.eval('//*[@class="product-travel-date"]'); 0380 var productDetails = html.eval('//table[@class="product-details"]'); 0381 var passengerDetails = html.eval('//table[@class="passengers"]'); 0382 for (productDetailIdx in productDetails) { 0383 // date is in the table before us 0384 var dt = productDts[productDetailIdx].content.replace(/\S+ (.*)/, "$1"); 0385 0386 var segmentDetail = productDetails[productDetailIdx].eval(".//td")[0]; 0387 var placement = passengerDetails[productDetailIdx].eval('.//td[@class="placement "]'); // yes, there is a space behind placement there... 0388 var seat = placement[0].content.match(/Voiture (.*?) - Place (.*?) /); 0389 var res = null; 0390 while (segmentDetail && !segmentDetail.isNull) { 0391 var cls = segmentDetail.attribute("class"); 0392 if (cls.includes("segment-departure")) { 0393 res = JsonLd.newTrainReservation(); 0394 res.reservationFor.departureTime = JsonLd.toDateTime(dt + segmentDetail.content, "d MMMMhh'h'mm", "fr"); 0395 segmentDetail = segmentDetail.nextSibling; 0396 res.reservationFor.departureStation.name = segmentDetail.content; 0397 if (seat) { 0398 res.reservedTicket.ticketedSeat.seatSection = seat[1]; 0399 res.reservedTicket.ticketedSeat.seatNumber = seat[2]; 0400 } 0401 } 0402 else if (cls.includes("segment-arrival")) { 0403 res.reservationFor.arrivalTime = JsonLd.toDateTime(dt + segmentDetail.content, "d MMMMhh'h'mm", "fr"); 0404 segmentDetail = segmentDetail.nextSibling; 0405 res.reservationFor.arrivalStation.name = segmentDetail.content; 0406 0407 if (res.reservationFor.trainName == "OUIGO" && pnrOuigo.length) { 0408 res.reservationNumber = pnrOuigo[0].content; 0409 } else if (pnr.length) { 0410 res.reservationNumber = pnr[0].content; 0411 } 0412 if (passengerName.length) { 0413 res.underName.name = passengerName[0].content; 0414 } 0415 0416 // HACK drop invalid elements so the structured fallback kicks in correctly 0417 // this should be done automatically in the engine 0418 if (res.reservationFor.departureTime > 0) 0419 reservations.push(res); 0420 } 0421 else if (cls === "segment") { 0422 res.reservationFor.trainName = segmentDetail.content; 0423 } 0424 else if (cls === "segment-ref-train") { 0425 res.reservationFor.trainNumber = segmentDetail.content; 0426 } 0427 0428 segmentDetail = segmentDetail.nextSibling.isNull ? segmentDetail.parent.nextSibling.firstChild : segmentDetail.nextSibling; 0429 } 0430 } 0431 0432 return reservations; 0433 } 0434 0435 function parseOuigoTicket(pdf, node) { 0436 var text = pdf.pages[0].textInRect(0, 0, 0.5, 1); 0437 0438 var res = JsonLd.newTrainReservation(); 0439 res.reservationNumber = text.match(/numéro de réservation est\s*:\s*([\w]{6})\n/)[1]; 0440 var trip = text.match(/(\d{2} .+ \d{4})\n\s*(\d{2}h\d{2})\s*(.*?)\n\s*(\d{2}h\d{2})\s*(.*?)\n/); 0441 res.reservationFor.departureStation.name = trip[3]; 0442 res.reservationFor.departureTime = JsonLd.toDateTime(trip[1] + trip[2], "dd MMMM yyyyhh'h'mm", "fr"); 0443 res.reservationFor.arrivalStation.name = trip[5]; 0444 res.reservationFor.arrivalTime = JsonLd.toDateTime(trip[1] + trip[4], "dd MMMM yyyyhh'h'mm", "fr"); 0445 0446 res.reservationFor.trainNumber = text.match(/N°\s*(\S+)/)[1]; 0447 0448 var seat = text.match(/Voiture\s*(\S+)\s*Place\s*(\S+)/); 0449 res.reservedTicket.ticketedSeat.seatSection = seat[1]; 0450 res.reservedTicket.ticketedSeat.seatNumber = seat[2]; 0451 0452 var barcodes = node.findChildNodes({ scope: "Descendants", mimeType: "text/plain", match: ".*" }); 0453 for (barcode of barcodes) { 0454 if (barcode.location != undefined) { 0455 res.reservedTicket.ticketToken = "azteccode:" + barcodes[0].content; 0456 break; 0457 } 0458 } 0459 0460 const price = text.match(/ (\d+\.\d\d)€/); 0461 if (price) { 0462 res.totalPrice = price[1]; 0463 res.priceCurrency = 'EUR'; 0464 } 0465 return res; 0466 } 0467 0468 function parseTerConfirmation(html) { 0469 var reservations = new Array(); 0470 const refNum = html.eval('//td[@id="referenceContainer"]')[0].content; 0471 const name = html.eval('//td[@id="nomReferenceContainer"]')[0].content; 0472 const journeys = html.eval('//table[@id ="emailTrajet" or @id="emailTrajetRetour"]'); 0473 for (const journey of journeys) { 0474 var res = JsonLd.newTrainReservation(); 0475 const dt = journey.eval('.//h2')[0].content.match(/ (\d.*)$/)[1]; 0476 res.reservationFor.departureDay = JsonLd.toDateTime(dt, "dd MMMM yyyy", "fr"); 0477 const ps = journey.eval('.//p'); 0478 res.reservationFor.departureStation.name = ps[0].content; 0479 res.reservationFor.departureTime = JsonLd.toDateTime(ps[1].content.match(/ (\d.*)/)[1], "hh'h'mm", "fr"); 0480 res.reservationFor.arrivalStation.name = ps[2].content; 0481 res.reservationFor.arrivalTime = JsonLd.toDateTime(ps[3].content.match(/ (\d.*)/)[1], "hh'h'mm", "fr"); 0482 res.reservationNumber = refNum; 0483 res.underName.name = name; 0484 reservations.push(res); 0485 } 0486 return reservations; 0487 } 0488 0489 function parseOuigoConfirmation(html) { 0490 var reservations = new Array(); 0491 const refNum = html.eval('//strong')[1].content; 0492 const tabs = html.eval('//table//table//table[@class = "rsz_320"]'); 0493 for (const tab of tabs) { 0494 const text = tab.recursiveContent; 0495 if (!text.match(/TRAJET/)) { 0496 continue; 0497 } 0498 0499 var idx = 0; 0500 while (true) { 0501 const date = text.substr(idx).match(/\w+ (\d{1,2} \w+ \d{4})/); 0502 if (!date) { 0503 break; 0504 } 0505 const leg = text.substr(idx).match(/(\d{2}h\d{2})\s+(.*?)\n\s*(\d{2}h\d{2})\s*(.*?)\n\s*TRAIN N° *(.*)\n/); 0506 var res = JsonLd.newTrainReservation(); 0507 res.reservationNumber = refNum; 0508 res.reservationFor.departureTime = JsonLd.toDateTime(date[1] + leg[1], "d MMMM yyyyhh'h'mm", "fr"); 0509 res.reservationFor.departureStation.name = leg[2]; 0510 res.reservationFor.arrivalStation.name = leg[4]; 0511 res.reservationFor.arrivalTime = JsonLd.toDateTime(date[1] + leg[3], "d MMMM yyyyhh'h'mm", "fr"); 0512 res.reservationFor.trainNumber = leg[5]; 0513 reservations.push(res); 0514 0515 idx += leg[0].length + leg.index; 0516 } 0517 } 0518 return reservations; 0519 } 0520 0521 // see https://community.kde.org/KDE_PIM/KItinerary/SNCF_Barcodes#Carte_Advantage 0522 function parseSncfCarte(code) { 0523 var carte = JsonLd.newObject("ProgramMembership"); 0524 carte.programName = tariffs[code.substr(111, 4)]; 0525 carte.membershipNumber = code.substr(53, 17); 0526 carte.token = 'aztec:' + code; 0527 return carte.programName != undefined ? carte : undefined; 0528 } 0529 0530 function parseSncfCartePdf(pdf, node, barcode) { 0531 const text = pdf.pages[barcode.location].text; 0532 var carte = node.result[0]; 0533 carte.member = JsonLd.newObject("Person"); 0534 carte.member.familyName = text.match(/(?:Nom|Name)\s*:\s*(.*)/)[1]; 0535 carte.member.givenName = text.match(/(?:Prénom|Vorname)\s*:\s*(.*)/)[1]; 0536 const validity = text.match(/(?:Du|Vom)\s+(\d{2}[\/\.]\d{2}[\/\.]\d{4})\s+(?:au|bis zum)\s+(\d{2}[\/\.]\d{2}[\/\.]\d{4})/); 0537 carte.validFrom = JsonLd.toDateTime(validity[1], ['dd/MM/yyyy', 'dd.MM.yyyy'], 'fr'); 0538 carte.validUntil = JsonLd.toDateTime(validity[2] + ' 23:59:59', ['dd/MM/yyyy hh:mm:ss', 'dd.MM.yyyy hh:mm:ss'], 'fr'); 0539 return carte; 0540 } 0541 0542 // see https://community.kde.org/KDE_PIM/KItinerary/SNCF_Barcodes#SNCF_Normandie_Tickets 0543 // PDF layout matches that of the "secutix" v2 0544 function parseSncfNormandie(pdf, node, triggerNode) { 0545 let res = JsonLd.newTrainReservation(); 0546 res.reservedTicket.ticketToken = "aztecbin:" + ByteArray.toBase64(triggerNode.content); 0547 0548 const page = pdf.pages[triggerNode.location]; 0549 const textRight = page.textInRect(0.5, 0.0, 1.0, 1.0); 0550 const pnr = textRight.match(/(.*)\n(.*)\n\d{2}\/\d{2}\/\d{4} +(?:PAO|REF)\s*:\s*([A-Z0-9]{6,8})\n/); 0551 res.reservationNumber = pnr[3]; 0552 res.underName.givenName = pnr[2]; 0553 res.underName.familyName = pnr[1]; 0554 res.reservedTicket.ticketedSeat.seatingType = textRight.match(/Classe (.*)\n/)[1]; 0555 0556 const textLeft = pdf.pages[triggerNode.location].textInRect(0.0, 0.0, 0.5, 1.0); 0557 const date = textLeft.match(/(\d{1,2} \S+ \d{4})/)[1]; 0558 res.reservationFor.departureDay = JsonLd.toDateTime(date, 'd MMMM yyyy', 'fr'); 0559 let reservations = parseSecutixPdfItineraryV2(textLeft, res); 0560 return reservations; 0561 } 0562 0563 function parseEvent(ev) 0564 { 0565 let res = JsonLd.newTrainReservation(); 0566 const names = ev.description.match(/ +(.*) -> (.*)\n/); 0567 res.reservationFor.departureStation.name = names[1]; 0568 res.reservationFor.departureTime = JsonLd.readQDateTime(ev, 'dtStart'); 0569 res.reservationFor.arrivalStation.name = names[2]; 0570 res.reservationFor.arrivalTime = JsonLd.readQDateTime(ev, 'dtEnd'); 0571 res.reservationFor.trainNumber = ev.description.match(/ +([A-Z ]+ \d+)\n/i)[1]; 0572 const seat = ev.description.match(/ +(?:VOITURE|COACH|WAGEN) (\d+) - (?:PLACE|SEAT|PLATZ) (\d+)/i); 0573 if (seat) { 0574 res.reservedTicket.ticketedSeat.seatSection = seat[1]; 0575 res.reservedTicket.ticketedSeat.seatNumber = seat[2]; 0576 } 0577 res.reservationNumber = ev.uid.substr(0, 6); 0578 res.url = ev.url; 0579 return res; 0580 }