File indexing completed on 2024-12-29 04:51:04

0001 /*
0002    SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
0003    SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 function readRSP6String(data, start, len) {
0007     let result = "";
0008     for (let i = 0; i < len; ++i) {
0009         const c = data.readNumberMSB(i * 6 + start, 6) + 32;
0010         result += String.fromCharCode(c);
0011     }
0012     return result.trim() ? result : null;
0013 }
0014 
0015 function readRSP6DateTime(data, start) {
0016     // this is in local time, ie. we need to convince JS to not mess with the timezone
0017     const days = data.readNumberMSB(start, 14);
0018     const minutes = data.readNumberMSB(start + 14, 11);
0019     let dt = new Date(Date.UTC(1997, 0, 1));
0020     dt.setTime(dt.getTime() + days * 24 * 60 * 60 * 1000 + minutes * 1000 * 60);
0021     return dt.toJSON().substr(0, 19);
0022 }
0023 
0024 // see https://git.eta.st/eta/rsp6-decoder/src/branch/master/spec.pdf
0025 function parseRSP6(text) {
0026     const rsp6Data = ByteArray.decodeRsp6Ticket(text);
0027     const rsp6 = ByteArray.toBitArray(rsp6Data);
0028 
0029     let res = JsonLd.newTrainReservation();
0030     res.reservationNumber = text.substr(13, 2) + readRSP6String(rsp6, 8, 9);
0031     res.reservationFor.departureStation.name = readRSP6String(rsp6, 109, 4);
0032     res.reservationFor.departureStation.identifier = 'nlc:' + readRSP6String(rsp6, 109, 4);
0033     res.reservationFor.arrivalStation.name = readRSP6String(rsp6, 133, 4);
0034     res.reservationFor.arrivalStation.identifier = 'nlc:' + readRSP6String(rsp6, 133, 4);
0035 
0036     const departureTime = readRSP6DateTime(rsp6, 211);
0037     const departureTimeFlag = rsp6.readNumberMSB(236, 2);
0038     if (departureTimeFlag > 0) {
0039         res.reservationFor.departureTime = departureTime;
0040     } else {
0041         res.reservationFor.departureDay = departureTime;
0042         res.reservedTicket.validFrom = departureTime;
0043     }
0044 
0045     res.underName.name = readRSP6String(rsp6, 255, 12);
0046 
0047     console.log("version", rsp6.readNumberMSB(68, 4));
0048     console.log("ticket type", readRSP6String(rsp6, 73, 3));
0049     console.log("fare label", readRSP6String(rsp6, 91, 3));
0050     console.log("retailer", readRSP6String(rsp6,157, 4));
0051     console.log("coupon type", rsp6.readNumberMSB(182, 2));
0052     console.log("discount code", rsp6.readNumberMSB(184, 10));
0053     console.log("route code", rsp6.readNumberMSB(194, 17));
0054     console.log("passenger ID", rsp6.readNumberMSB(238, 17));
0055     console.log("passenger gender", rsp6.readNumberMSB(327, 2));
0056     console.log("restriction code", readRSP6String(329, 7));
0057     console.log("limited duration code", rsp6.readNumberMSB(379, 4));
0058 
0059     let offset = 0;
0060     if (rsp6.readNumberMSB(384, 1)) {
0061         console.log("purchase time", readRSP6DateTime(rsp6, 390));
0062         console.log("purchase ref", readRSP6String(rsp6, 449, 8));
0063         res.totalPrice = rsp6.readNumberMSB(415, 21) / 100.0;
0064         res.priceCurrency = 'GBP';
0065         const daysOfValidity = rsp6.readNumberMSB(497, 9);
0066         if (departureTimeFlag == 0) {
0067             let dt = new Date(departureTime);
0068             dt.setTime(dt.getTime() + Math.max(1, daysOfValidity) * 24 * 60 * 60 * 1000);
0069             if (daysOfValidity == 0) {
0070                 dt.setTime(dt.getTime() - 1000);
0071             }
0072             res.reservedTicket.validUntil = dt;
0073         }
0074         offset += 122;
0075     }
0076 
0077     // TODO handle more than one reservation properly
0078     for (let i = 0; i < rsp6.readNumberMSB(386, 4); ++i) {
0079         res.reservationFor.trainNumber = readRSP6String(rsp6, 390 + offset, 2) + rsp6.readNumberMSB(402 + offset, 14);
0080         res.reservedTicket.ticketedSeat.seatSection = readRSP6String(rsp6, 416 + offset, 1);
0081         if (res.reservedTicket.ticketedSeat.seatSection == '*') {
0082             res.reservedTicket.ticketedSeat.seatSection = null;
0083         }
0084         const seatLetter = readRSP6String(rsp6, 422 + offset, 1);
0085         const seatNum = rsp6.readNumberMSB(428 + offset, 7);
0086         if ((seatLetter && seatLetter != '*') || seatNum) {
0087             res.reservedTicket.ticketedSeat.seatNumber = (seatLetter ? seatLetter : "") + (seatNum ? seatNum : "");
0088         }
0089         offset += 45;
0090     }
0091 
0092     if (rsp6.readNumberMSB(385, 1)) {
0093         console.log("free text", readRSP6String(390 + offset, 51));
0094     }
0095 
0096     res.reservedTicket.ticketToken = 'azteccode:' + text;
0097     return res;
0098 }
0099 
0100 function parseTicket(pdf, node, triggerNode) {
0101     const text = pdf.pages[triggerNode.location].text;
0102     var res = triggerNode.result[0];
0103     const header = text.match(/ +(\d{2}[ -][A-Z][a-z]{2}[ -]\d{4}) +(?:Out: |Ret: )?([A-Z]{3}) ?- ?([A-Z]{3})\n(.*)  +(.*)/);
0104     const date = header[1].replace(/-/g, ' ');
0105     const itinerary = text.match(/Itinerary.*\n +(.*)\n +(\d{2}:\d{2})\n +([\S\s]*?)\n +(\d{2}:\d{2})\n +(.*)/);
0106 
0107     res.reservationFor.departureStation.identifier = 'uk:' + header[2];
0108     res.reservationFor.arrivalStation.identifier = 'uk:' + header[3];
0109     if (itinerary) {
0110         res.reservationFor.departureStation.name = itinerary[1];
0111         res.reservationFor.departureTime = JsonLd.toDateTime(date + ' ' + itinerary[2], 'dd MMM yyyy hh:mm', 'en');
0112         res.reservationFor.arrivalStation.name = itinerary[5];
0113         res.reservationFor.arrivalTime = JsonLd.toDateTime(date + ' ' + itinerary[4], 'dd MMM yyyy hh:mm', 'en');
0114         res.reservationFor.trainName = itinerary[3].replace(/(.*?)(?:  .*)?(?:\n +|$)/g, '$1 ');
0115     } else {
0116         // unbound ticket
0117         res.reservationFor.departureStation.name = header[4];
0118         res.reservationFor.arrivalStation.name = header[5];
0119         res.reservationFor.departureDay = JsonLd.toDateTime(date, 'dd MMM yyyy', 'en');
0120     }
0121 
0122     return res;
0123 }