eterm_parser/
detr.rs

1use crate::util;
2use std::collections::HashMap;
3
4/// The result that detr text parsed.
5#[derive(Default, Debug)]
6pub struct Detr<'a> {
7    /// airline issued by.
8    pub issued_by: Option<&'a str>,
9    /// airport of departure
10    pub org: Option<&'a str>,
11    /// airport of arrival
12    pub dst: Option<&'a str>,
13    /// sale type of ticket,such as BSP-D,BSP-I,ARL-D,ARL-I.
14    pub et: Option<&'a str>,
15    /// remark/limit
16    pub er: Option<&'a str>,
17    /// code of tour
18    pub tour_code: Option<&'a str>,
19    /// show that whether the itinerary receipt has printed.
20    pub receipt_printed: bool,
21    /// the name of passenger.
22    pub passenger: Option<&'a str>,
23    /// proof of exchange.
24    pub exchange: Option<&'a str>,
25    /// conjoint ticket.
26    pub conj_tickets: Option<&'a str>,
27    /// the segments of flight.
28    pub flight_segs: Vec<DetrFlightSeg<'a>>,
29    /// fare of FC.
30    pub fc: Option<&'a str>,
31    /// face value of ticket.
32    pub fare: Option<DetrFareItem<'a>>,
33    /// tax value of ticket.
34    pub taxs: Option<HashMap<Option<&'a str>, DetrFareItem<'a>>>,
35    /// total value of ticket.
36    pub total: Option<DetrFareItem<'a>>,
37    /// pay method.
38    pub fop: Option<&'a str>,
39    pub oi: Option<&'a str>,
40    /// ticket number.
41    pub tktn: Option<&'a str>,
42}
43
44#[derive(Default, Debug, PartialEq)]
45pub struct DetrFareItem<'a> {
46    pub item_type: Option<&'a str>,
47    pub amount: Option<f32>,
48    pub currency: Option<&'a str>,
49    pub is_exempt: bool,
50}
51
52#[derive(Default, Debug)]
53pub struct DetrFlightSeg<'a> {
54    /// the flag that show how long to transit. such as O is more than 24 hours,X is less than 24 hours.
55    pub transit_flag: Option<&'a str>,
56    /// index of segment.
57    pub index: Option<i32>,
58    /// airport of departure
59    pub org: Option<&'a str>,
60    /// airport of arrival
61    pub dst: Option<&'a str>,
62    /// airline code
63    pub airline: Option<&'a str>,
64    /// the code of operator airline
65    pub carrier: Option<&'a str>,
66    /// the number of flight.
67    pub flight_no: Option<&'a str>,
68    /// departure date
69    pub flight_deptdate: Option<&'a str>,
70    /// arrival time
71    pub flight_depttime: Option<&'a str>,
72    /// the cabin of flight.
73    pub flight_class: Option<&'a str>,
74    /// the status of seat, such as
75    /// OK represent reserved (RR or HK),
76    /// RQ represent candidate,
77    /// NS represent no seat(like baby),
78    /// SA represent free seat(like free),
79    pub seat_status: Option<&'a str>,
80    /// the fare price of basis.
81    pub fare_basis: Option<&'a str>,
82    /// valid date.
83    pub nvb: Option<&'a str>,
84    /// valid date.
85    pub nva: Option<&'a str>,
86    /// baggage, such as
87    /// K:km
88    /// PC:piece
89    /// NIL:no free luggage
90    pub baggage: Option<&'a str>,
91    /// the status of ticket, such as
92    /// "OPEN FOR USE" represent unused,
93    /// USED/FLOWN,
94    /// VOID,
95    /// REFUNDED,
96    /// CHECK/IN,
97    /// LIFT/BOARDED,
98    /// SUSPENDED,
99    /// EXCHANGED,
100    /// AIRPORT CNTL,
101    /// CPN NOTE,
102    /// FIM EXCH.
103    pub ticket_status: Option<&'a str>,
104    /// the terminal of departure
105    pub org_term: Option<&'a str>,
106    /// the terminal of arrival
107    pub dst_term: Option<&'a str>,
108    /// big pnr
109    pub bpnr: Option<&'a str>,
110    /// pnr
111    pub cpnr: Option<&'a str>,
112    pub system: Option<&'a str>,
113    /// the status of passenger, such as
114    /// OFLK represent checkin and unboarded,
115    /// NOSH represent miss flight.
116    pub passenger_status_flag: Option<&'a str>,
117}
118
119impl<'a> Detr<'a> {
120    pub fn parse(text: &'a str) -> anyhow::Result<Self> {
121        if text.is_empty() {
122            return Err(anyhow::Error::msg(
123                "detr parameter shouldn't be empty.".to_owned(),
124            ));
125        }
126        //let finalDest = Self::regex_extact(r"\s+TO: ([A-Z]{3})\s", &text)?;
127        let re = regex::Regex::new(
128            r"(?<TRANSITFLAG>[O|X]) (FM|TO):(?<INDEX>\d)(?<ORG>[A-Z]{3}) (?<AIRLINE>\w{2}) (?<CARRIER>..{2}) *(?<FLIGHTNO>\d+|OPEN)\s+(?<CABIN>[A-Z]) (?<DETPDATE>\d{2}[A-Z]{3}|OPEN ) (?<DEPTTIME>.{4}) (?<SEATSTATUS>.{2}) (?<FAREBASIS>.{10}) (?<NVB>.{6}).(?<NVA>.{6}) (?<BAGGAGE>.{3}) (?<TICKETSTATUS>[^\r\n]+)(\r|\n)+.....(?<ORGTERMINAL>..)(?<DSTTERMINAL>..) RL:(?:(?<BPNR>\w{6})(\s+)/((?<CPNR>\w{6})(?<SYSTEM>\w{2}))?)?(\s*[\r|\n]+\s+)TO:\s+(?<DST>[A-Z]{3})",
129        )?;
130        let detr_flight_segs = re
131            .captures_iter(text)
132            .filter_map(|caps| {
133                match (
134                    caps.name("TRANSITFLAG"),
135                    caps.name("INDEX"),
136                    caps.name("ORG"),
137                    caps.name("AIRLINE"),
138                    caps.name("CARRIER"),
139                    caps.name("FLIGHTNO"),
140                    caps.name("CABIN"),
141                    caps.name("DETPDATE"),
142                    caps.name("DEPTTIME"),
143                    caps.name("SEATSTATUS"),
144                    caps.name("FAREBASIS"),
145                    caps.name("NVB"),
146                    caps.name("NVA"),
147                    caps.name("BAGGAGE"),
148                    caps.name("TICKETSTATUS"),
149                    caps.name("ORGTERMINAL"),
150                    caps.name("DSTTERMINAL"),
151                    caps.name("BPNR"),
152                    caps.name("CPNR"),
153                    caps.name("SYSTEM"),
154                    caps.name("DST"),
155                ) {
156                    (
157                        Some(cap_transit),
158                        Some(cap_index),
159                        cap_org,
160                        cap_airline,
161                        cap_carrier,
162                        cap_flightno,
163                        cap_cabin,
164                        cap_deptdate,
165                        cap_depttime,
166                        cap_seatstatus,
167                        cap_farebasis,
168                        cap_nvb,
169                        cap_nva,
170                        cap_baggage,
171                        cap_ticketstatus,
172                        cap_orgterminal,
173                        cap_dstterminal,
174                        cap_bpnr,
175                        cap_cpnr,
176                        cap_system,
177                        cap_dst,
178                    ) => {
179                        if cap_transit.as_str().contains("VOID") {
180                            Some(DetrFlightSeg {
181                                transit_flag: Some(cap_transit.as_str()),
182                                index: cap_index.as_str().parse::<i32>().ok(),
183                                org: util::regex_extact_text(cap_org),
184                                seat_status: Some("VOID"),
185                                ticket_status: Some("VOID"),
186                                ..Default::default()
187                            })
188                        } else {
189                            Some(DetrFlightSeg {
190                                transit_flag: Some(cap_transit.as_str()),
191                                index: cap_index.as_str().parse::<i32>().ok(),
192                                org: util::regex_extact_text(cap_org),
193                                airline: util::regex_extact_text(cap_airline),
194                                carrier: util::regex_extact_text(cap_carrier),
195                                flight_no: util::regex_extact_text(cap_flightno),
196                                flight_class: util::regex_extact_text(cap_cabin),
197                                flight_deptdate: util::regex_extact_text(cap_deptdate),
198                                flight_depttime: util::regex_extact_text(cap_depttime),
199                                seat_status: util::regex_extact_text(cap_seatstatus),
200                                fare_basis: util::regex_extact_text(cap_farebasis),
201                                nvb: util::regex_extact_text(cap_nvb),
202                                nva: util::regex_extact_text(cap_nva),
203                                baggage: util::regex_extact_text(cap_baggage),
204                                ticket_status: util::regex_extact_text(cap_ticketstatus),
205                                org_term: util::regex_extact_text(cap_orgterminal),
206                                dst_term: util::regex_extact_text(cap_dstterminal),
207                                bpnr: util::regex_extact_text(cap_bpnr),
208                                cpnr: util::regex_extact_text(cap_cpnr),
209                                system: util::regex_extact_text(cap_system),
210                                dst: util::regex_extact_text(cap_dst),
211                                ..Default::default()
212                            })
213                        }
214                    }
215                    _ => None,
216                }
217            })
218            .collect::<Vec<_>>();
219
220        let re = regex::Regex::new(r"FARE:\s+(?<CURRENCY>[A-Z]{3})\s*(?<AMOUNT>\d+\.\d{2})\|")?;
221        let fare = match re.captures(text) {
222            Some(caps) => match (caps.name("CURRENCY"), caps.name("AMOUNT")) {
223                (Some(cap2), Some(cap3)) => Some(DetrFareItem {
224                    amount: cap3.as_str().parse::<f32>().ok(),
225                    currency: Some(cap2.as_str()),
226                    is_exempt: false,
227                    ..Default::default()
228                }),
229                _ => None,
230            },
231            _ => None,
232        };
233
234        let re = regex::Regex::new(r"TOTAL:\s+(?<CURRENCY>[A-Z]{3})\s*(?<AMOUNT>\d+\.\d{2})\|")?;
235        let total = match re.captures(text) {
236            Some(caps) => match (caps.name("CURRENCY"), caps.name("AMOUNT")) {
237                (Some(cap2), Some(cap3)) => Some(DetrFareItem {
238                    amount: cap3.as_str().parse::<f32>().ok(),
239                    currency: Some(cap2.as_str()),
240                    is_exempt: false,
241                    ..Default::default()
242                }),
243                _ => None,
244            },
245            _ => None,
246        };
247
248        let re = regex::Regex::new(
249            r"TAX:\s+(?:(?<EXEMPT>EXEMPT)|(?<CURRENCY>[A-Z]{3})\s*(?<PRICE>\d+\.\d{2}))(?<TYPE>[A-Z]{2})\|",
250        )?;
251
252        let items = re
253            .captures_iter(text)
254            .filter_map(|caps| {
255                match (
256                    caps.name("TYPE"),
257                    caps.name("EXEMPT"),
258                    caps.name("CURRENCY"),
259                    caps.name("PRICE"),
260                ) {
261                    (Some(cap_type), cap_exempt, cap_curr, cap_price) => Some(DetrFareItem {
262                        amount: cap_price.map_or(None, |x| x.as_str().parse::<f32>().ok()),
263                        currency: util::regex_extact_text(cap_curr),
264                        is_exempt: cap_exempt.map_or(false, |x| x.as_str() == "EXEMPT"),
265                        item_type: Some(cap_type.as_str()),
266                    }),
267                    _ => None,
268                }
269            })
270            .collect::<Vec<_>>();
271        let taxs = if items.len() == 0 {
272            None
273        } else {
274            let mut map = HashMap::new();
275            for item in items {
276                map.insert(item.item_type, item);
277            }
278            Some(map)
279        };
280
281        Ok(Self {
282            issued_by: util::regex_extact(r"\bISSUED BY: ?(.*)ORG/DST:", text)?,
283            org: util::regex_extact(r"ORG/DST: ?([A-Z]{3})/[A-Z]{3}", text)?,
284            dst: util::regex_extact(r"ORG/DST: ?[A-Z]{3}/([A-Z]{3})", text)?,
285            et: util::regex_extact(r"ORG/DST: ?[A-Z]{3}/[A-Z]{3}\s+([A-Z\-]+)", text)?,
286            er: util::regex_extact(r"E/R: ?(.*?)(\r|\n)+", text)?,
287            tour_code: util::regex_extact(r"TOUR CODE: ?(\S[^\r]*?)(\r|\n)+", text)?,
288            receipt_printed: text.contains("RECEIPT PRINTED"),
289            passenger: util::regex_extact(r"PASSENGER: ?(\S[^\r]*?)(\r|\n)+", text)?,
290            exchange: util::regex_extact(r"EXCH: ?(\S[^\r]*?)\S", text)?,
291            conj_tickets: util::regex_extact(r"CONJ TKT: ?(\S[^\r]*?)(\r|\n)+", text)?,
292            flight_segs: detr_flight_segs,
293            fc: util::regex_extact(r"FC: ?(\S[^\r]*?)(\r|\n)+", text)?,
294            fare,
295            taxs,
296            total,
297            fop: util::regex_extact(r"\|FOP: ?(\S[^\r]*?)(\r|\n)+", text)?,
298            oi: util::regex_extact(r"\|OI: ?(\S[^\r]*?)(\r|\n)+", text)?,
299            tktn: util::regex_extact(r"\|TKTN: ?(\S[^\r]*?)(\r|\n)+", text)?,
300        })
301    }
302}